summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-11-17 16:38:15 -0500
committerJustin Klaassen <justinklaassen@google.com>2017-11-17 16:38:15 -0500
commit6a65f2da209bff03cb0eb6da309710ac6ee5026d (patch)
tree48e2090e716d4178378cb0599fc5d9cffbcf3f63
parent46c77c203439b3b37c99d09e326df4b1fe08c10b (diff)
downloadandroid-28-6a65f2da209bff03cb0eb6da309710ac6ee5026d.tar.gz
Import Android SDK Platform P [4456821]
/google/data/ro/projects/android/fetch_artifact \ --bid 4456821 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4456821.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I2d206b200d7952f899a5d1647ab532638cc8dd43
-rw-r--r--android/animation/ValueAnimator.java31
-rw-r--r--android/app/Activity.java91
-rw-r--r--android/app/ActivityManager.java40
-rw-r--r--android/app/ActivityManagerInternal.java21
-rw-r--r--android/app/ActivityOptions.java22
-rw-r--r--android/app/ActivityThread.java35
-rw-r--r--android/app/AppOpsManager.java12
-rw-r--r--android/app/DexLoadReporter.java24
-rw-r--r--android/app/DialogFragment.java3
-rw-r--r--android/app/Fragment.java12
-rw-r--r--android/app/FragmentBreadCrumbs.java3
-rw-r--r--android/app/FragmentContainer.java3
-rw-r--r--android/app/FragmentController.java3
-rw-r--r--android/app/FragmentHostCallback.java3
-rw-r--r--android/app/FragmentManager.java12
-rw-r--r--android/app/FragmentManagerNonConfig.java3
-rw-r--r--android/app/FragmentTransaction.java3
-rw-r--r--android/app/Instrumentation.java44
-rw-r--r--android/app/ListFragment.java3
-rw-r--r--android/app/LoaderManager.java6
-rw-r--r--android/app/Notification.java311
-rw-r--r--android/app/NotificationManager.java6
-rw-r--r--android/app/SystemServiceRegistry.java41
-rw-r--r--android/app/TimePickerDialog.java3
-rw-r--r--android/app/VrManager.java16
-rw-r--r--android/app/WindowConfiguration.java9
-rw-r--r--android/app/admin/DevicePolicyManager.java7
-rw-r--r--android/app/job/JobInfo.java229
-rw-r--r--android/app/slice/Slice.java85
-rw-r--r--android/app/slice/SliceProvider.java90
-rw-r--r--android/app/slice/widget/GridView.java6
-rw-r--r--android/app/slice/widget/LargeTemplateView.java15
-rw-r--r--android/app/slice/widget/ShortcutView.java99
-rw-r--r--android/app/slice/widget/SliceView.java32
-rw-r--r--android/app/slice/widget/SliceViewUtil.java16
-rw-r--r--android/app/usage/UsageStatsManager.java3
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java10
-rw-r--r--android/arch/lifecycle/AndroidViewModel.java1
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/LiveData.java410
-rw-r--r--android/arch/lifecycle/LiveDataReactiveStreams.java244
-rw-r--r--android/arch/lifecycle/LiveDataReactiveStreamsTest.java89
-rw-r--r--android/arch/lifecycle/LiveDataTest.java260
-rw-r--r--android/arch/lifecycle/ViewModelProvider.java1
-rw-r--r--android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java1
-rw-r--r--android/arch/persistence/room/Room.java1
-rw-r--r--android/arch/persistence/room/testing/MigrationTestHelper.java2
-rw-r--r--android/bluetooth/BluetoothSocket.java4
-rw-r--r--android/content/AsyncTaskLoader.java3
-rw-r--r--android/content/ContentProviderClient.java13
-rw-r--r--android/content/Context.java17
-rw-r--r--android/content/CursorLoader.java3
-rw-r--r--android/content/Intent.java16
-rw-r--r--android/content/Loader.java12
-rw-r--r--android/content/SharedPreferences.java5
-rw-r--r--android/content/pm/ActivityInfo.java11
-rw-r--r--android/content/pm/LauncherApps.java20
-rw-r--r--android/content/pm/PackageInstaller.java10
-rw-r--r--android/content/pm/PackageParser.java25
-rw-r--r--android/content/pm/ShortcutServiceInternal.java3
-rw-r--r--android/content/pm/crossprofile/CrossProfileApps.java90
-rw-r--r--android/content/res/AssetManager.java33
-rw-r--r--android/content/res/Configuration.java1
-rw-r--r--android/content/res/FontResourcesParser.java12
-rw-r--r--android/content/res/ResourcesImpl.java66
-rw-r--r--android/content/res/Resources_Delegate.java33
-rw-r--r--android/database/MergeCursor.java2
-rw-r--r--android/database/sqlite/SQLiteConnection.java26
-rw-r--r--android/database/sqlite/SQLiteCursor.java30
-rw-r--r--android/database/sqlite/SQLiteDatabase.java83
-rw-r--r--android/database/sqlite/SQLiteDatabaseConfiguration.java24
-rw-r--r--android/database/sqlite/SQLiteGlobal.java10
-rw-r--r--android/database/sqlite/SQLiteOpenHelper.java51
-rw-r--r--android/ext/services/notification/Assistant.java190
-rw-r--r--android/ext/services/notification/ChannelImpressions.java49
-rw-r--r--android/graphics/Paint.java16
-rw-r--r--android/graphics/Paint_Delegate.java2
-rw-r--r--android/graphics/Rect.java13
-rw-r--r--android/graphics/Typeface.java4
-rw-r--r--android/graphics/Typeface_Delegate.java75
-rw-r--r--android/graphics/drawable/AnimatedVectorDrawable.java5
-rw-r--r--android/graphics/drawable/RippleBackground.java137
-rw-r--r--android/graphics/drawable/RippleComponent.java244
-rw-r--r--android/graphics/drawable/RippleDrawable.java130
-rw-r--r--android/graphics/drawable/RippleForeground.java344
-rw-r--r--android/graphics/drawable/VectorDrawable.java84
-rw-r--r--android/hardware/camera2/CameraCaptureSession.java40
-rw-r--r--android/hardware/camera2/impl/CameraCaptureSessionImpl.java14
-rw-r--r--android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java7
-rw-r--r--android/hardware/camera2/impl/CameraDeviceImpl.java18
-rw-r--r--android/hardware/camera2/impl/ICameraDeviceUserWrapper.java10
-rw-r--r--android/hardware/camera2/legacy/CameraDeviceUserShim.java5
-rw-r--r--android/hardware/camera2/params/OutputConfiguration.java102
-rw-r--r--android/hardware/display/BrightnessChangeEvent.java103
-rw-r--r--android/hardware/display/DisplayManager.java16
-rw-r--r--android/hardware/display/DisplayManagerGlobal.java30
-rw-r--r--android/hardware/location/ContextHubManager.java55
-rw-r--r--android/hardware/location/ContextHubTransaction.java152
-rw-r--r--android/hardware/location/NanoAppBinary.java56
-rw-r--r--android/hardware/sidekick/SidekickInternal.java56
-rw-r--r--android/hardware/usb/AccessoryFilter.java145
-rw-r--r--android/hardware/usb/DeviceFilter.java313
-rw-r--r--android/hardware/usb/UsbManager.java4
-rw-r--r--android/location/Location.java4
-rw-r--r--android/media/AudioAttributes.java1
-rw-r--r--android/media/AudioManager.java26
-rw-r--r--android/media/BufferingParams.java384
-rw-r--r--android/media/ExifInterface.java83
-rw-r--r--android/media/MediaDrm.java2
-rw-r--r--android/media/MediaFormat.java90
-rw-r--r--android/media/MediaMetadataRetriever.java162
-rw-r--r--android/media/MediaPlayer.java180
-rw-r--r--android/net/ConnectivityManager.java29
-rw-r--r--android/net/ConnectivityMetricsEvent.java3
-rw-r--r--android/net/IpSecAlgorithm.java6
-rw-r--r--android/net/IpSecManager.java2
-rw-r--r--android/net/LocalSocketImpl.java18
-rw-r--r--android/net/MacAddress.java274
-rw-r--r--android/net/Network.java9
-rw-r--r--android/net/NetworkCapabilities.java119
-rw-r--r--android/net/NetworkIdentity.java4
-rw-r--r--android/net/NetworkInfo.java13
-rw-r--r--android/net/NetworkWatchlistManager.java75
-rw-r--r--android/net/SSLCertificateSocketFactory.java7
-rw-r--r--android/net/Uri.java6
-rw-r--r--android/net/apf/ApfFilter.java31
-rw-r--r--android/net/ip/IpClient.java18
-rw-r--r--android/net/ip/IpManager.java10
-rw-r--r--android/net/metrics/ConnectStats.java3
-rw-r--r--android/net/metrics/DefaultNetworkEvent.java66
-rw-r--r--android/net/metrics/DnsEvent.java3
-rw-r--r--android/net/metrics/NetworkEvent.java14
-rw-r--r--android/net/metrics/WakeupEvent.java33
-rw-r--r--android/net/metrics/WakeupStats.java67
-rw-r--r--android/net/wifi/WifiInfo.java3
-rw-r--r--android/net/wifi/WifiManager.java55
-rw-r--r--android/net/wifi/rtt/RangingResultCallback.java10
-rw-r--r--android/net/wifi/rtt/WifiRttManager.java87
-rw-r--r--android/os/BatteryManager.java42
-rw-r--r--android/os/BatteryStatsInternal.java35
-rw-r--r--android/os/Binder.java13
-rw-r--r--android/os/Binder_Delegate.java54
-rw-r--r--android/os/ConfigUpdate.java7
-rw-r--r--android/os/Debug.java66
-rw-r--r--android/os/Environment.java5
-rw-r--r--android/os/FileUtils.java13
-rw-r--r--android/os/HidlSupport.java23
-rw-r--r--android/os/HwBinder.java13
-rw-r--r--android/os/HwBlob.java20
-rw-r--r--android/os/HwRemoteBinder.java5
-rw-r--r--android/os/LocaleList.java6
-rw-r--r--android/os/Parcel.java2
-rw-r--r--android/os/ParcelFileDescriptor.java2
-rw-r--r--android/os/PowerManager.java52
-rw-r--r--android/os/PowerManagerInternal.java19
-rw-r--r--android/os/PowerSaveState.java2
-rw-r--r--android/os/RemoteCallbackList.java8
-rw-r--r--android/os/ShellCallback.java23
-rw-r--r--android/os/ShellCommand.java6
-rw-r--r--android/os/StrictMode.java964
-rw-r--r--android/os/SystemClock.java56
-rw-r--r--android/os/TokenWatcher.java30
-rw-r--r--android/os/UpdateEngine.java1
-rw-r--r--android/os/UserHandle.java55
-rw-r--r--android/os/UserManager.java63
-rw-r--r--android/os/storage/StorageManager.java29
-rw-r--r--android/os/strictmode/CleartextNetworkViolation.java (renamed from android/telephony/ims/feature/IRcsFeature.java)17
-rw-r--r--android/os/strictmode/ContentUriWithoutPermissionViolation.java30
-rw-r--r--android/os/strictmode/CustomViolation.java23
-rw-r--r--android/os/strictmode/DiskReadViolation.java23
-rw-r--r--android/os/strictmode/DiskWriteViolation.java23
-rw-r--r--android/os/strictmode/FileUriExposedViolation.java23
-rw-r--r--android/os/strictmode/InstanceCountViolation.java36
-rw-r--r--android/os/strictmode/IntentReceiverLeakedViolation.java24
-rw-r--r--android/os/strictmode/LeakedClosableViolation.java24
-rw-r--r--android/os/strictmode/NetworkViolation.java23
-rw-r--r--android/os/strictmode/ResourceMismatchViolation.java23
-rw-r--r--android/os/strictmode/ServiceConnectionLeakedViolation.java24
-rw-r--r--android/os/strictmode/SqliteObjectLeakedViolation.java25
-rw-r--r--android/os/strictmode/UnbufferedIoViolation.java28
-rw-r--r--android/os/strictmode/UntaggedSocketViolation.java28
-rw-r--r--android/os/strictmode/Violation.java24
-rw-r--r--android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java24
-rw-r--r--android/preference/PreferenceFragment.java8
-rw-r--r--android/provider/MediaStore.java19
-rw-r--r--android/provider/Settings.java156
-rw-r--r--android/provider/Telephony.java85
-rw-r--r--android/security/KeyStore.java54
-rw-r--r--android/service/autofill/FieldsDetection.java127
-rw-r--r--android/service/autofill/FillEventHistory.java57
-rw-r--r--android/service/autofill/FillResponse.java45
-rw-r--r--android/service/dreams/DreamService.java26
-rw-r--r--android/service/euicc/EuiccService.java9
-rw-r--r--android/service/notification/ConditionProviderService.java10
-rw-r--r--android/service/notification/NotificationListenerService.java3
-rw-r--r--android/service/voice/VoiceInteractionSession.java19
-rw-r--r--android/support/LibraryGroups.java30
-rw-r--r--android/support/LibraryVersions.java2
-rw-r--r--android/support/SourceJarTaskHelper.java60
-rw-r--r--android/support/car/drawer/CarDrawerActivity.java2
-rw-r--r--android/support/car/drawer/CarDrawerController.java55
-rw-r--r--android/support/car/utils/ColumnCalculator.java2
-rw-r--r--android/support/car/widget/CarItemAnimator.java4
-rw-r--r--android/support/car/widget/CarRecyclerView.java10
-rw-r--r--android/support/car/widget/PagedLayoutManager.java (renamed from android/support/car/widget/CarLayoutManager.java)65
-rw-r--r--android/support/car/widget/PagedListView.java267
-rw-r--r--android/support/car/widget/PagedScrollBarView.java13
-rw-r--r--android/support/mediacompat/testlib/IntentConstants.java34
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java1
-rw-r--r--android/support/mediacompat/testlib/VersionConstants.java25
-rw-r--r--android/support/mediacompat/testlib/util/IntentUtil.java131
-rw-r--r--android/support/mediacompat/testlib/util/PollingCheck.java98
-rw-r--r--android/support/mediacompat/testlib/util/TestUtil.java41
-rw-r--r--android/support/text/emoji/widget/EmojiAppCompatEditText.java8
-rw-r--r--android/support/text/emoji/widget/EmojiEditText.java8
-rw-r--r--android/support/text/emoji/widget/EmojiExtractEditText.java8
-rw-r--r--android/support/transition/Transition.java4
-rw-r--r--android/support/v17/leanback/app/BaseFragment.java2
-rw-r--r--android/support/v17/leanback/app/BaseRowFragment.java14
-rw-r--r--android/support/v17/leanback/app/BaseRowSupportFragment.java12
-rw-r--r--android/support/v17/leanback/app/BrandedFragment.java2
-rw-r--r--android/support/v17/leanback/app/BrowseFragment.java153
-rw-r--r--android/support/v17/leanback/app/BrowseSupportFragment.java133
-rw-r--r--android/support/v17/leanback/app/DetailsFragment.java2
-rw-r--r--android/support/v17/leanback/app/DetailsFragmentBackgroundController.java2
-rw-r--r--android/support/v17/leanback/app/ErrorFragment.java2
-rw-r--r--android/support/v17/leanback/app/GuidedStepFragment.java17
-rw-r--r--android/support/v17/leanback/app/GuidedStepSupportFragment.java15
-rw-r--r--android/support/v17/leanback/app/HeadersFragment.java6
-rw-r--r--android/support/v17/leanback/app/ListRowDataAdapter.java16
-rw-r--r--android/support/v17/leanback/app/OnboardingFragment.java2
-rw-r--r--android/support/v17/leanback/app/PlaybackFragment.java4
-rw-r--r--android/support/v17/leanback/app/PlaybackFragmentGlueHost.java2
-rw-r--r--android/support/v17/leanback/app/RowsFragment.java4
-rw-r--r--android/support/v17/leanback/app/SearchFragment.java2
-rw-r--r--android/support/v17/leanback/app/VerticalGridFragment.java2
-rw-r--r--android/support/v17/leanback/app/VideoFragment.java2
-rw-r--r--android/support/v17/leanback/app/VideoFragmentGlueHost.java2
-rw-r--r--android/support/v17/leanback/widget/ArrayObjectAdapter.java60
-rw-r--r--android/support/v17/leanback/widget/BaseGridView.java4
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java422
-rw-r--r--android/support/v17/leanback/widget/GuidedActionAdapter.java71
-rw-r--r--android/support/v17/leanback/widget/GuidedActionDiffCallback.java65
-rw-r--r--android/support/v17/leanback/widget/ObjectAdapter.java15
-rw-r--r--android/support/v4/app/FragmentActivity.java22
-rw-r--r--android/support/v4/graphics/TypefaceCompat.java5
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi26Impl.java156
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi28Impl.java68
-rw-r--r--android/support/v4/media/MediaBrowserCompat.java30
-rw-r--r--android/support/v4/media/MediaBrowserServiceCompat.java12
-rw-r--r--android/support/v4/media/MediaMetadataCompat.java2
-rw-r--r--android/support/v4/view/ViewCompat.java4
-rw-r--r--android/support/v7/app/AppCompatDelegateImplV9.java21
-rw-r--r--android/support/v7/app/AppCompatViewInflater.java138
-rw-r--r--android/support/v7/util/SortedList.java44
-rw-r--r--android/support/v7/util/SortedListBatchedCallbackTest.java10
-rw-r--r--android/support/v7/util/SortedListTest.java124
-rw-r--r--android/support/v7/widget/AppCompatTextHelper.java6
-rw-r--r--android/support/v7/widget/TooltipCompatHandler.java39
-rw-r--r--android/support/v7/widget/util/SortedListAdapterCallback.java5
-rw-r--r--android/support/wear/ambient/AmbientDelegate.java42
-rw-r--r--android/support/wear/ambient/AmbientMode.java15
-rw-r--r--android/support/wear/ambient/SharedLibraryVersion.java4
-rw-r--r--android/support/wear/ambient/WearableControllerProvider.java2
-rw-r--r--android/support/wear/internal/widget/ResourcesUtil.java2
-rw-r--r--android/support/wear/internal/widget/drawer/MultiPagePresenter.java2
-rw-r--r--android/support/wear/internal/widget/drawer/MultiPageUi.java27
-rw-r--r--android/support/wear/internal/widget/drawer/SinglePagePresenter.java2
-rw-r--r--android/support/wear/internal/widget/drawer/SinglePageUi.java7
-rw-r--r--android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java2
-rw-r--r--android/support/wear/utils/MetadataConstants.java3
-rw-r--r--android/support/wear/widget/BezierSCurveInterpolator.java5
-rw-r--r--android/support/wear/widget/BoxInsetLayout.java30
-rw-r--r--android/support/wear/widget/CircledImageView.java15
-rw-r--r--android/support/wear/widget/CurvingLayoutCallback.java2
-rw-r--r--android/support/wear/widget/ProgressDrawable.java5
-rw-r--r--android/support/wear/widget/RoundedDrawable.java3
-rw-r--r--android/support/wear/widget/ScrollManager.java8
-rw-r--r--android/support/wear/widget/SimpleAnimatorListener.java2
-rw-r--r--android/support/wear/widget/SwipeDismissLayout.java5
-rw-r--r--android/support/wear/widget/WearableRecyclerView.java3
-rw-r--r--android/support/wear/widget/drawer/AbsListViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/FlingWatcherFactory.java2
-rw-r--r--android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/PageIndicatorView.java2
-rw-r--r--android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/ScrollViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/WearableActionDrawerMenu.java3
-rw-r--r--android/support/wear/widget/drawer/WearableActionDrawerView.java23
-rw-r--r--android/support/wear/widget/drawer/WearableDrawerLayout.java43
-rw-r--r--android/support/wear/widget/drawer/WearableDrawerView.java9
-rw-r--r--android/support/wear/widget/drawer/WearableNavigationDrawerView.java7
-rw-r--r--android/support/wearable/watchface/decomposition/package-info.java2
-rw-r--r--android/system/Int32Ref.java28
-rw-r--r--android/system/Int64Ref.java28
-rw-r--r--android/system/Os.java101
-rw-r--r--android/system/UnixSocketAddressTest.java51
-rw-r--r--android/telecom/Call.java66
-rw-r--r--android/telecom/CallAudioState.java90
-rw-r--r--android/telecom/Connection.java50
-rw-r--r--android/telecom/ConnectionService.java77
-rw-r--r--android/telecom/ConnectionServiceAdapter.java9
-rw-r--r--android/telecom/ConnectionServiceAdapterServant.java10
-rw-r--r--android/telecom/InCallAdapter.java35
-rw-r--r--android/telecom/InCallService.java18
-rw-r--r--android/telecom/Phone.java14
-rw-r--r--android/telecom/PhoneAccount.java10
-rw-r--r--android/telecom/RemoteConnectionService.java3
-rw-r--r--android/telecom/TelecomManager.java35
-rw-r--r--android/telephony/CarrierConfigManager.java88
-rw-r--r--android/telephony/PhoneStateListener.java27
-rw-r--r--android/telephony/SignalStrength.java61
-rw-r--r--android/telephony/SmsManager.java253
-rw-r--r--android/telephony/SmsMessage.java26
-rw-r--r--android/telephony/SubscriptionManager.java36
-rw-r--r--android/telephony/TelephonyManager.java55
-rw-r--r--android/telephony/TelephonyScanManager.java4
-rw-r--r--android/telephony/euicc/DownloadableSubscription.java19
-rw-r--r--android/telephony/ims/feature/IMMTelFeature.java187
-rw-r--r--android/telephony/ims/feature/MMTelFeature.java135
-rw-r--r--android/telephony/ims/feature/RcsFeature.java4
-rw-r--r--android/telephony/mbms/DownloadStateCallback.java2
-rw-r--r--android/telephony/mbms/FileInfo.java23
-rw-r--r--android/telephony/mbms/FileServiceInfo.java2
-rw-r--r--android/telephony/mbms/MbmsDownloadReceiver.java11
-rw-r--r--android/telephony/mbms/UriPathPair.java2
-rw-r--r--android/telephony/mbms/vendor/MbmsDownloadServiceBase.java2
-rw-r--r--android/telephony/mbms/vendor/VendorUtils.java2
-rw-r--r--android/util/FeatureFlagUtils.java3
-rw-r--r--android/util/KeyValueListParser.java14
-rw-r--r--android/util/Log.java295
-rw-r--r--android/util/StatsManager.java134
-rw-r--r--android/util/TimeUtils.java8
-rw-r--r--android/util/apk/ApkSignatureSchemeV2Verifier.java192
-rw-r--r--android/util/apk/ApkVerityBuilder.java351
-rw-r--r--android/util/apk/ByteBufferDataSource.java66
-rw-r--r--android/util/apk/ByteBufferFactory.java28
-rw-r--r--android/util/apk/DataDigester.java28
-rw-r--r--android/util/apk/DataSource.java38
-rw-r--r--android/util/apk/MemoryMappedFileDataSource.java105
-rw-r--r--android/util/apk/SignatureInfo.java49
-rw-r--r--android/util/proto/ProtoOutputStream.java82
-rw-r--r--android/view/Display.java29
-rw-r--r--android/view/DisplayFrames.java189
-rw-r--r--android/view/FocusFinder.java2
-rw-r--r--android/view/IWindowManagerImpl.java6
-rw-r--r--android/view/NotificationHeaderView.java14
-rw-r--r--android/view/RenderNode.java7
-rw-r--r--android/view/RenderNodeAnimator.java10
-rw-r--r--android/view/SurfaceControl.java34
-rw-r--r--android/view/ThreadedRenderer.java7
-rw-r--r--android/view/View.java22
-rw-r--r--android/view/ViewConfiguration.java2
-rw-r--r--android/view/ViewRootImpl.java2
-rw-r--r--android/view/WindowManager.java17
-rw-r--r--android/view/WindowManagerGlobal.java3
-rw-r--r--android/view/WindowManagerInternal.java33
-rw-r--r--android/view/WindowManagerPolicy.java65
-rw-r--r--android/view/accessibility/AccessibilityInteractionClient.java91
-rw-r--r--android/view/accessibility/AccessibilityManager.java7
-rw-r--r--android/view/autofill/AutofillManager.java52
-rw-r--r--android/view/textclassifier/TextClassification.java28
-rw-r--r--android/view/textclassifier/TextClassifier.java84
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java275
-rw-r--r--android/view/textclassifier/TextLinks.java252
-rw-r--r--android/view/textclassifier/TextSelection.java53
-rw-r--r--android/view/textclassifier/logging/SmartSelectionEventTracker.java179
-rw-r--r--android/webkit/ServiceWorkerClient.java6
-rw-r--r--android/webkit/WebView.java43
-rw-r--r--android/webkit/WebViewClient.java57
-rw-r--r--android/webkit/WebViewFragment.java3
-rw-r--r--android/webkit/WebViewLibraryLoader.java4
-rw-r--r--android/webkit/WebViewProvider.java2
-rw-r--r--android/widget/AbsListView.java9
-rw-r--r--android/widget/Editor.java14
-rw-r--r--android/widget/PopupWindow.java1
-rw-r--r--android/widget/SelectionActionModeHelper.java59
-rw-r--r--androidx/app/slice/builders/MessagingSliceBuilder.java110
-rw-r--r--androidx/app/slice/builders/SliceHints.java50
-rw-r--r--androidx/app/slice/builders/TemplateSliceBuilder.java127
-rw-r--r--androidx/app/slice/core/SliceQuery.java289
-rw-r--r--androidx/app/slice/widget/ActionRow.java204
-rw-r--r--androidx/app/slice/widget/GridView.java185
-rw-r--r--androidx/app/slice/widget/LargeSliceAdapter.java211
-rw-r--r--androidx/app/slice/widget/LargeTemplateView.java121
-rw-r--r--androidx/app/slice/widget/MessageView.java79
-rw-r--r--androidx/app/slice/widget/RemoteInputView.java423
-rw-r--r--androidx/app/slice/widget/ShortcutView.java174
-rw-r--r--androidx/app/slice/widget/SliceLiveData.java76
-rw-r--r--androidx/app/slice/widget/SliceView.java312
-rw-r--r--androidx/app/slice/widget/SliceViewUtil.java199
-rw-r--r--androidx/app/slice/widget/SmallTemplateView.java367
-rw-r--r--androidx/recyclerview/selection/ActivationCallbacks.java55
-rw-r--r--androidx/recyclerview/selection/AutoScroller.java42
-rw-r--r--androidx/recyclerview/selection/BandPredicate.java131
-rw-r--r--androidx/recyclerview/selection/BandSelectionHelper.java355
-rw-r--r--androidx/recyclerview/selection/ContentLock.java98
-rw-r--r--androidx/recyclerview/selection/DefaultBandHost.java153
-rw-r--r--androidx/recyclerview/selection/DefaultSelectionHelper.java475
-rw-r--r--androidx/recyclerview/selection/EventBridge.java137
-rw-r--r--androidx/recyclerview/selection/FocusCallbacks.java78
-rw-r--r--androidx/recyclerview/selection/GestureRouter.java100
-rw-r--r--androidx/recyclerview/selection/GestureSelectionHelper.java287
-rw-r--r--androidx/recyclerview/selection/GridModel.java786
-rw-r--r--androidx/recyclerview/selection/ItemDetailsLookup.java149
-rw-r--r--androidx/recyclerview/selection/ItemKeyProvider.java92
-rw-r--r--androidx/recyclerview/selection/MotionEvents.java108
-rw-r--r--androidx/recyclerview/selection/MotionInputHandler.java111
-rw-r--r--androidx/recyclerview/selection/MouseCallbacks.java48
-rw-r--r--androidx/recyclerview/selection/MouseInputHandler.java223
-rw-r--r--androidx/recyclerview/selection/MutableSelection.java54
-rw-r--r--androidx/recyclerview/selection/Range.java186
-rw-r--r--androidx/recyclerview/selection/Selection.java259
-rw-r--r--androidx/recyclerview/selection/SelectionHelper.java251
-rw-r--r--androidx/recyclerview/selection/SelectionHelperBuilder.java342
-rw-r--r--androidx/recyclerview/selection/SelectionPredicates.java84
-rw-r--r--androidx/recyclerview/selection/SelectionStorage.java181
-rw-r--r--androidx/recyclerview/selection/Shared.java28
-rw-r--r--androidx/recyclerview/selection/StableIdKeyProvider.java99
-rw-r--r--androidx/recyclerview/selection/ToolHandlerRegistry.java68
-rw-r--r--androidx/recyclerview/selection/TouchCallbacks.java55
-rw-r--r--androidx/recyclerview/selection/TouchEventRouter.java105
-rw-r--r--androidx/recyclerview/selection/TouchInputHandler.java149
-rw-r--r--androidx/recyclerview/selection/ViewAutoScroller.java271
-rw-r--r--com/android/car/setupwizardlib/CarSetupWizardLayout.java140
-rw-r--r--com/android/commands/am/Am.java9
-rw-r--r--com/android/commands/am/Instrument.java145
-rw-r--r--com/android/commands/pm/Pm.java3
-rw-r--r--com/android/commands/sm/Sm.java53
-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.java266
-rw-r--r--com/android/ims/ImsServiceProxy.java (renamed from android/telephony/ims/ImsServiceProxy.java)45
-rw-r--r--com/android/ims/ImsServiceProxyCompat.java (renamed from android/telephony/ims/ImsServiceProxyCompat.java)29
-rw-r--r--com/android/internal/app/ColorDisplayController.java (renamed from com/android/internal/app/NightDisplayController.java)27
-rw-r--r--com/android/internal/app/LocaleHelper.java11
-rw-r--r--com/android/internal/app/LocalePicker.java15
-rw-r--r--com/android/internal/app/LocaleStore.java32
-rw-r--r--com/android/internal/colorextraction/types/Tonal.java8
-rw-r--r--com/android/internal/net/NetworkStatsFactory.java2
-rw-r--r--com/android/internal/os/BaseCommand.java8
-rw-r--r--com/android/internal/os/BatteryStatsImpl.java24
-rw-r--r--com/android/internal/os/KernelUidCpuFreqTimeReader.java7
-rw-r--r--com/android/internal/os/KernelUidCpuTimeReader.java7
-rw-r--r--com/android/internal/policy/DividerSnapAlgorithm.java23
-rw-r--r--com/android/internal/telephony/BaseCommands.java12
-rw-r--r--com/android/internal/telephony/CommandsInterface.java10
-rw-r--r--com/android/internal/telephony/DefaultPhoneNotifier.java9
-rw-r--r--com/android/internal/telephony/GsmCdmaPhone.java17
-rw-r--r--com/android/internal/telephony/IccSmsInterfaceManager.java181
-rw-r--r--com/android/internal/telephony/ImsSMSDispatcher.java18
-rw-r--r--com/android/internal/telephony/InboundSmsHandler.java4
-rw-r--r--com/android/internal/telephony/MccTable.java24
-rw-r--r--com/android/internal/telephony/NitzData.java228
-rw-r--r--com/android/internal/telephony/OemHookIndication.java53
-rw-r--r--com/android/internal/telephony/OemHookResponse.java59
-rw-r--r--com/android/internal/telephony/Phone.java53
-rw-r--r--com/android/internal/telephony/PhoneInternalInterface.java10
-rw-r--r--com/android/internal/telephony/PhoneNotifier.java2
-rw-r--r--com/android/internal/telephony/RIL.java109
-rw-r--r--com/android/internal/telephony/RadioResponse.java3
-rw-r--r--com/android/internal/telephony/SMSDispatcher.java92
-rw-r--r--com/android/internal/telephony/ServiceStateTracker.java296
-rw-r--r--com/android/internal/telephony/SubscriptionController.java99
-rw-r--r--com/android/internal/telephony/SubscriptionInfoUpdater.java38
-rw-r--r--com/android/internal/telephony/UiccSmsController.java31
-rw-r--r--com/android/internal/telephony/cat/CommandParamsFactory.java24
-rw-r--r--com/android/internal/telephony/cdma/CdmaSMSDispatcher.java17
-rw-r--r--com/android/internal/telephony/cdma/SmsMessage.java60
-rw-r--r--com/android/internal/telephony/dataconnection/ApnContext.java19
-rw-r--r--com/android/internal/telephony/dataconnection/DataConnection.java79
-rw-r--r--com/android/internal/telephony/euicc/EuiccController.java9
-rw-r--r--com/android/internal/telephony/euicc/EuiccOperation.java38
-rw-r--r--com/android/internal/telephony/gsm/GsmSMSDispatcher.java18
-rw-r--r--com/android/internal/telephony/gsm/SmsMessage.java113
-rw-r--r--com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java4
-rw-r--r--com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java8
-rw-r--r--com/android/internal/telephony/sip/SipCommandInterface.java8
-rw-r--r--com/android/internal/telephony/test/SimulatedCommands.java18
-rw-r--r--com/android/internal/telephony/test/SimulatedCommandsVerifier.java20
-rw-r--r--com/android/internal/telephony/uicc/CarrierTestOverride.java13
-rw-r--r--com/android/internal/telephony/uicc/IccRecords.java70
-rw-r--r--com/android/internal/telephony/uicc/IccUtils.java8
-rw-r--r--com/android/internal/telephony/uicc/IsimUiccRecords.java3
-rw-r--r--com/android/internal/telephony/uicc/RuimRecords.java3
-rw-r--r--com/android/internal/telephony/uicc/SIMRecords.java52
-rw-r--r--com/android/internal/telephony/uicc/UiccCard.java19
-rw-r--r--com/android/internal/telephony/uicc/UiccProfile.java751
-rw-r--r--com/android/internal/util/Predicate.java1
-rw-r--r--com/android/internal/util/RingBuffer.java11
-rw-r--r--com/android/internal/util/StateMachine.java2
-rw-r--r--com/android/internal/widget/ImageFloatingTextView.java4
-rw-r--r--com/android/internal/widget/MessagingGroup.java393
-rw-r--r--com/android/internal/widget/MessagingLayout.java444
-rw-r--r--com/android/internal/widget/MessagingLinearLayout.java217
-rw-r--r--com/android/internal/widget/MessagingMessage.java197
-rw-r--r--com/android/internal/widget/MessagingPropertyAnimator.java233
-rw-r--r--com/android/internal/widget/NotificationActionListLayout.java30
-rw-r--r--com/android/internal/widget/RemeasuringLinearLayout.java68
-rw-r--r--com/android/internal/widget/ViewClippingUtil.java108
-rw-r--r--com/android/keyguard/KeyguardSliceView.java160
-rw-r--r--com/android/keyguard/KeyguardStatusView.java44
-rw-r--r--com/android/keyguard/KeyguardUpdateMonitor.java12
-rw-r--r--com/android/layout/remote/api/RemoteLayoutlibCallback.java6
-rw-r--r--com/android/layoutlib/bridge/impl/ResourceHelper.java54
-rw-r--r--com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java27
-rw-r--r--com/android/layoutlib/bridge/remote/server/ServerMain.java111
-rw-r--r--com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java143
-rw-r--r--com/android/printspooler/ui/SelectPrinterActivity.java2
-rw-r--r--com/android/providers/settings/DatabaseHelper.java21
-rw-r--r--com/android/providers/settings/SettingsProtoDumpUtil.java378
-rw-r--r--com/android/providers/settings/SettingsProvider.java4
-rw-r--r--com/android/providers/settings/SettingsState.java8
-rw-r--r--com/android/server/BatteryService.java461
-rw-r--r--com/android/server/BluetoothManagerService.java4
-rw-r--r--com/android/server/ConnectivityService.java51
-rw-r--r--com/android/server/MountServiceIdler.java10
-rw-r--r--com/android/server/NetworkManagementService.java43
-rw-r--r--com/android/server/RescueParty.java8
-rw-r--r--com/android/server/ServiceThread.java8
-rw-r--r--com/android/server/ServiceWatcher.java25
-rw-r--r--com/android/server/StorageManagerService.java109
-rw-r--r--com/android/server/SystemServer.java655
-rw-r--r--com/android/server/TelephonyRegistry.java30
-rw-r--r--com/android/server/VibratorService.java2
-rw-r--r--com/android/server/Watchdog.java20
-rw-r--r--com/android/server/accessibility/AccessibilityManagerService.java2
-rw-r--r--com/android/server/am/ActivityDisplay.java87
-rw-r--r--com/android/server/am/ActivityManagerDebugConfig.java1
-rw-r--r--com/android/server/am/ActivityManagerService.java650
-rw-r--r--com/android/server/am/ActivityManagerShellCommand.java13
-rw-r--r--com/android/server/am/ActivityMetricsLogger.java91
-rw-r--r--com/android/server/am/ActivityRecord.java40
-rw-r--r--com/android/server/am/ActivityStack.java231
-rw-r--r--com/android/server/am/ActivityStackSupervisor.java110
-rw-r--r--com/android/server/am/ActivityStarter.java30
-rw-r--r--com/android/server/am/AssistDataReceiverProxy.java102
-rw-r--r--com/android/server/am/AssistDataRequester.java355
-rw-r--r--com/android/server/am/BatteryStatsService.java16
-rw-r--r--com/android/server/am/KeyguardController.java47
-rw-r--r--com/android/server/am/LockTaskController.java225
-rw-r--r--com/android/server/am/RecentTasks.java14
-rw-r--r--com/android/server/am/RunningTasks.java6
-rw-r--r--com/android/server/am/TaskChangeNotificationController.java8
-rw-r--r--com/android/server/am/TaskRecord.java37
-rw-r--r--com/android/server/am/UserController.java8
-rw-r--r--com/android/server/audio/AudioService.java200
-rw-r--r--com/android/server/autofill/AutofillManagerService.java11
-rw-r--r--com/android/server/autofill/AutofillManagerServiceImpl.java53
-rw-r--r--com/android/server/autofill/Helper.java9
-rw-r--r--com/android/server/autofill/Session.java179
-rw-r--r--com/android/server/autofill/ViewState.java6
-rw-r--r--com/android/server/autofill/ui/FillUi.java5
-rw-r--r--com/android/server/backup/BackupManagerService.java2
-rw-r--r--com/android/server/backup/RefactoredBackupManagerService.java2
-rw-r--r--com/android/server/backup/restore/ActiveRestoreSession.java21
-rw-r--r--com/android/server/connectivity/DefaultNetworkMetrics.java117
-rw-r--r--com/android/server/connectivity/IpConnectivityEventBuilder.java36
-rw-r--r--com/android/server/connectivity/IpConnectivityMetrics.java113
-rw-r--r--com/android/server/connectivity/NetdEventListenerService.java110
-rw-r--r--com/android/server/connectivity/NetworkMonitor.java6
-rw-r--r--com/android/server/connectivity/Vpn.java63
-rw-r--r--com/android/server/content/ContentService.java4
-rw-r--r--com/android/server/coverage/CoverageService.java2
-rw-r--r--com/android/server/devicepolicy/NetworkLogger.java6
-rw-r--r--com/android/server/display/BrightnessTracker.java639
-rw-r--r--com/android/server/display/ColorDisplayService.java (renamed from com/android/server/display/NightDisplayService.java)20
-rw-r--r--com/android/server/display/DisplayManagerService.java41
-rw-r--r--com/android/server/display/DisplayPowerController.java21
-rw-r--r--com/android/server/display/DisplayTransformManager.java8
-rw-r--r--com/android/server/display/LocalDisplayAdapter.java37
-rw-r--r--com/android/server/ethernet/EthernetNetworkFactory.java2
-rw-r--r--com/android/server/job/JobPackageTracker.java30
-rw-r--r--com/android/server/job/JobSchedulerService.java48
-rw-r--r--com/android/server/job/JobServiceContext.java19
-rw-r--r--com/android/server/job/JobStore.java103
-rw-r--r--com/android/server/job/controllers/AppIdleController.java2
-rw-r--r--com/android/server/job/controllers/BatteryController.java9
-rw-r--r--com/android/server/job/controllers/ConnectivityController.java45
-rw-r--r--com/android/server/job/controllers/IdleController.java9
-rw-r--r--com/android/server/job/controllers/JobStatus.java95
-rw-r--r--com/android/server/job/controllers/StorageController.java7
-rw-r--r--com/android/server/job/controllers/TimeController.java15
-rw-r--r--com/android/server/location/ActivityRecognitionProxy.java90
-rw-r--r--com/android/server/location/FusedProxy.java22
-rw-r--r--com/android/server/location/GeocoderProxy.java50
-rw-r--r--com/android/server/location/GeofenceManager.java3
-rw-r--r--com/android/server/location/GeofenceProxy.java18
-rw-r--r--com/android/server/location/GnssLocationProvider.java57
-rw-r--r--com/android/server/location/LocationProviderProxy.java237
-rw-r--r--com/android/server/locksettings/SyntheticPasswordCrypto.java21
-rw-r--r--com/android/server/locksettings/SyntheticPasswordManager.java35
-rw-r--r--com/android/server/media/AudioPlaybackMonitor.java289
-rw-r--r--com/android/server/media/AudioPlayerStateMonitor.java327
-rw-r--r--com/android/server/media/MediaRouterService.java74
-rw-r--r--com/android/server/media/MediaSessionService.java28
-rw-r--r--com/android/server/media/MediaSessionStack.java14
-rw-r--r--com/android/server/net/NetworkPolicyManagerService.java4
-rw-r--r--com/android/server/net/watchlist/DigestUtils.java54
-rw-r--r--com/android/server/net/watchlist/HarmfulDigests.java55
-rw-r--r--com/android/server/net/watchlist/NetworkWatchlistService.java267
-rw-r--r--com/android/server/net/watchlist/ReportWatchlistJobService.java76
-rw-r--r--com/android/server/net/watchlist/WatchlistLoggingHandler.java298
-rw-r--r--com/android/server/net/watchlist/WatchlistReportDbHelper.java203
-rw-r--r--com/android/server/net/watchlist/WatchlistSettings.java284
-rw-r--r--com/android/server/notification/NotificationManagerService.java51
-rw-r--r--com/android/server/notification/RankingHelper.java40
-rw-r--r--com/android/server/notification/ZenModeHelper.java11
-rw-r--r--com/android/server/pm/KeySetManagerService.java37
-rw-r--r--com/android/server/pm/PackageDexOptimizer.java18
-rw-r--r--com/android/server/pm/PackageInstallerService.java8
-rw-r--r--com/android/server/pm/PackageManagerService.java553
-rw-r--r--com/android/server/pm/PackageManagerServiceUtils.java342
-rw-r--r--com/android/server/pm/PackageManagerShellCommand.java78
-rw-r--r--com/android/server/pm/ShortcutPackage.java10
-rw-r--r--com/android/server/pm/ShortcutService.java49
-rw-r--r--com/android/server/pm/ShortcutUser.java44
-rw-r--r--com/android/server/pm/UserDataPreparer.java4
-rw-r--r--com/android/server/pm/UserManagerService.java17
-rw-r--r--com/android/server/pm/UserRestrictionsUtils.java15
-rw-r--r--com/android/server/pm/crossprofile/CrossProfileAppsService.java34
-rw-r--r--com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java264
-rw-r--r--com/android/server/pm/permission/DefaultPermissionGrantPolicy.java7
-rw-r--r--com/android/server/pm/permission/PermissionManagerService.java101
-rw-r--r--com/android/server/policy/BurnInProtectionHelper.java3
-rw-r--r--com/android/server/policy/GlobalActions.java2
-rw-r--r--com/android/server/policy/PhoneWindowManager.java1319
-rw-r--r--com/android/server/power/BatterySaverPolicy.java320
-rw-r--r--com/android/server/power/PowerManagerService.java79
-rw-r--r--com/android/server/power/ShutdownThread.java10
-rw-r--r--com/android/server/power/batterysaver/BatterySaverController.java239
-rw-r--r--com/android/server/power/batterysaver/FileUpdater.java55
-rw-r--r--com/android/server/print/UserState.java11
-rw-r--r--com/android/server/soundtrigger/SoundTriggerHelper.java2
-rw-r--r--com/android/server/stats/StatsCompanionService.java308
-rw-r--r--com/android/server/updates/TzDataInstallReceiver.java58
-rw-r--r--com/android/server/usage/AppIdleHistory.java9
-rw-r--r--com/android/server/usage/AppStandbyController.java69
-rw-r--r--com/android/server/usb/UsbDeviceManager.java2
-rw-r--r--com/android/server/usb/UsbProfileGroupSettingsManager.java403
-rw-r--r--com/android/server/voiceinteraction/VoiceInteractionManagerService.java39
-rw-r--r--com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java8
-rw-r--r--com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java309
-rw-r--r--com/android/server/vr/VrManagerService.java26
-rw-r--r--com/android/server/wifi/HalDeviceManager.java171
-rw-r--r--com/android/server/wifi/SoftApManager.java137
-rw-r--r--com/android/server/wifi/SupplicantStaIfaceHal.java7
-rw-r--r--com/android/server/wifi/VelocityBasedConnectedScore.java64
-rw-r--r--com/android/server/wifi/WifiConfigManager.java39
-rw-r--r--com/android/server/wifi/WifiInjector.java16
-rw-r--r--com/android/server/wifi/WifiMetrics.java212
-rw-r--r--com/android/server/wifi/WifiNative.java37
-rw-r--r--com/android/server/wifi/WifiScoreReport.java34
-rw-r--r--com/android/server/wifi/WifiServiceImpl.java141
-rw-r--r--com/android/server/wifi/WifiStateMachine.java46
-rw-r--r--com/android/server/wifi/WifiStateMachinePrime.java61
-rw-r--r--com/android/server/wifi/WifiVendorHal.java76
-rw-r--r--com/android/server/wifi/WificondControl.java34
-rw-r--r--com/android/server/wifi/aware/WifiAwareDataPathStateManager.java1
-rw-r--r--com/android/server/wifi/aware/WifiAwareNativeManager.java16
-rw-r--r--com/android/server/wifi/aware/WifiAwareStateManager.java2
-rw-r--r--com/android/server/wifi/p2p/WifiP2pServiceImpl.java2
-rw-r--r--com/android/server/wifi/rtt/RttNative.java23
-rw-r--r--com/android/server/wifi/rtt/RttService.java1
-rw-r--r--com/android/server/wifi/rtt/RttServiceImpl.java222
-rw-r--r--com/android/server/wifi/scanner/HalWifiScannerImpl.java2
-rw-r--r--com/android/server/wifi/scanner/NoBandChannelHelper.java182
-rw-r--r--com/android/server/wifi/scanner/WifiScannerImpl.java4
-rw-r--r--com/android/server/wifi/scanner/WifiScanningServiceImpl.java112
-rw-r--r--com/android/server/wifi/scanner/WificondChannelHelper.java (renamed from com/android/server/wifi/scanner/HalChannelHelper.java)16
-rw-r--r--com/android/server/wifi/scanner/WificondScannerImpl.java6
-rw-r--r--com/android/server/wifi/util/ApConfigUtil.java12
-rw-r--r--com/android/server/wm/AppWindowToken.java100
-rw-r--r--com/android/server/wm/DimLayer.java4
-rw-r--r--com/android/server/wm/DisplayContent.java78
-rw-r--r--com/android/server/wm/DockedStackDividerController.java64
-rw-r--r--com/android/server/wm/DragDropController.java521
-rw-r--r--com/android/server/wm/DragInputEventReceiver.java113
-rw-r--r--com/android/server/wm/DragState.java270
-rw-r--r--com/android/server/wm/InputConsumerImpl.java53
-rw-r--r--com/android/server/wm/InputMonitor.java26
-rw-r--r--com/android/server/wm/PinnedStackController.java3
-rw-r--r--com/android/server/wm/RootWindowContainer.java6
-rw-r--r--com/android/server/wm/ScreenRotationAnimation.java8
-rw-r--r--com/android/server/wm/Session.java8
-rw-r--r--com/android/server/wm/StackWindowController.java12
-rw-r--r--com/android/server/wm/Task.java4
-rw-r--r--com/android/server/wm/TaskPositioner.java12
-rw-r--r--com/android/server/wm/TaskStack.java75
-rw-r--r--com/android/server/wm/TaskWindowContainerController.java4
-rw-r--r--com/android/server/wm/WindowAnimator.java2
-rw-r--r--com/android/server/wm/WindowManagerService.java296
-rw-r--r--com/android/server/wm/WindowManagerShellCommand.java57
-rw-r--r--com/android/server/wm/WindowState.java183
-rw-r--r--com/android/server/wm/WindowStateAnimator.java4
-rw-r--r--com/android/server/wm/WindowSurfaceController.java8
-rw-r--r--com/android/server/wm/WindowSurfacePlacer.java2
-rw-r--r--com/android/server/wm/WindowTracing.java197
-rw-r--r--com/android/settingslib/CustomEditTextPreference.java9
-rw-r--r--com/android/settingslib/DeviceInfoUtils.java61
-rw-r--r--com/android/settingslib/RestrictedLockUtils.java4
-rw-r--r--com/android/settingslib/TwoTargetPreference.java25
-rw-r--r--com/android/settingslib/Utils.java2
-rw-r--r--com/android/settingslib/bluetooth/PanProfile.java2
-rw-r--r--com/android/settingslib/bluetooth/Utils.java82
-rw-r--r--com/android/settingslib/core/AbstractPreferenceController.java118
-rw-r--r--com/android/settingslib/core/lifecycle/Lifecycle.java87
-rw-r--r--com/android/settingslib/core/lifecycle/LifecycleObserver.java5
-rw-r--r--com/android/settingslib/core/lifecycle/ObservableActivity.java28
-rw-r--r--com/android/settingslib/core/lifecycle/ObservableDialogFragment.java38
-rw-r--r--com/android/settingslib/core/lifecycle/ObservableFragment.java37
-rw-r--r--com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java38
-rw-r--r--com/android/settingslib/core/lifecycle/events/OnAttach.java4
-rw-r--r--com/android/settingslib/core/lifecycle/events/OnCreate.java6
-rw-r--r--com/android/settingslib/core/lifecycle/events/OnDestroy.java7
-rw-r--r--com/android/settingslib/core/lifecycle/events/OnPause.java7
-rw-r--r--com/android/settingslib/core/lifecycle/events/OnResume.java7
-rw-r--r--com/android/settingslib/core/lifecycle/events/OnStart.java8
-rw-r--r--com/android/settingslib/core/lifecycle/events/OnStop.java8
-rw-r--r--com/android/settingslib/datetime/ZoneGetter.java17
-rw-r--r--com/android/settingslib/development/AbstractEnableAdbPreferenceController.java11
-rw-r--r--com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java6
-rw-r--r--com/android/settingslib/drawer/UserAdapter.java2
-rw-r--r--com/android/settingslib/graph/BatteryMeterDrawableBase.java3
-rw-r--r--com/android/settingslib/wifi/AccessPointPreference.java19
-rw-r--r--com/android/setupwizardlib/GlifLayout.java11
-rw-r--r--com/android/setupwizardlib/GlifLayoutTest.java27
-rw-r--r--com/android/setupwizardlib/template/IconMixin.java2
-rw-r--r--com/android/setupwizardlib/template/IconMixinTest.java11
-rw-r--r--com/android/setupwizardlib/util/WizardManagerHelper.java17
-rw-r--r--com/android/setupwizardlib/util/WizardManagerHelperTest.java3
-rw-r--r--com/android/systemui/DemoMode.java1
-rw-r--r--com/android/systemui/Dependency.java8
-rw-r--r--com/android/systemui/OverviewProxyService.java224
-rw-r--r--com/android/systemui/RecentsComponent.java2
-rw-r--r--com/android/systemui/doze/DozeScreenStatePreventingAdapter.java4
-rw-r--r--com/android/systemui/doze/DozeService.java1
-rw-r--r--com/android/systemui/globalactions/GlobalActionsDialog.java2
-rw-r--r--com/android/systemui/keyguard/KeyguardSliceProvider.java159
-rw-r--r--com/android/systemui/keyguard/KeyguardViewMediator.java22
-rw-r--r--com/android/systemui/keyguard/WorkLockActivityController.java32
-rw-r--r--com/android/systemui/pip/phone/InputConsumerController.java8
-rw-r--r--com/android/systemui/pip/phone/PipManager.java11
-rw-r--r--com/android/systemui/pip/tv/PipManager.java7
-rw-r--r--com/android/systemui/qs/QSFooterImpl.java33
-rw-r--r--com/android/systemui/qs/QuickQSPanel.java27
-rw-r--r--com/android/systemui/qs/QuickStatusBarHeader.java25
-rw-r--r--com/android/systemui/qs/tiles/BluetoothTile.java15
-rw-r--r--com/android/systemui/qs/tiles/NightDisplayTile.java13
-rw-r--r--com/android/systemui/recents/Recents.java18
-rw-r--r--com/android/systemui/recents/RecentsActivity.java22
-rw-r--r--com/android/systemui/recents/RecentsActivityLaunchState.java20
-rw-r--r--com/android/systemui/recents/RecentsImpl.java122
-rw-r--r--com/android/systemui/recents/RecentsImplProxy.java4
-rw-r--r--com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java35
-rw-r--r--com/android/systemui/recents/misc/SystemServicesProxy.java162
-rw-r--r--com/android/systemui/recents/views/DockState.java12
-rw-r--r--com/android/systemui/recents/views/RecentsTransitionComposer.java176
-rw-r--r--com/android/systemui/recents/views/RecentsTransitionHelper.java458
-rw-r--r--com/android/systemui/recents/views/RecentsView.java202
-rw-r--r--com/android/systemui/recents/views/RecentsViewTouchHandler.java3
-rw-r--r--com/android/systemui/recents/views/SystemBarScrimViews.java4
-rw-r--r--com/android/systemui/recents/views/TaskStackAnimationHelper.java6
-rw-r--r--com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java6
-rw-r--r--com/android/systemui/recents/views/TaskStackView.java66
-rw-r--r--com/android/systemui/recents/views/TaskStackViewTouchHandler.java6
-rw-r--r--com/android/systemui/recents/views/TaskView.java3
-rw-r--r--com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java2
-rw-r--r--com/android/systemui/recents/views/grid/GridTaskView.java2
-rw-r--r--com/android/systemui/recents/views/grid/TaskViewFocusFrame.java2
-rw-r--r--com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java27
-rw-r--r--com/android/systemui/shared/recents/model/RecentsTaskLoader.java8
-rw-r--r--com/android/systemui/shared/recents/model/TaskFilter.java2
-rw-r--r--com/android/systemui/shared/recents/model/TaskStack.java26
-rw-r--r--com/android/systemui/shared/recents/view/AnimateableViewBounds.java (renamed from com/android/systemui/recents/views/AnimateableViewBounds.java)36
-rw-r--r--com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java41
-rw-r--r--com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java89
-rw-r--r--com/android/systemui/shared/recents/view/RecentsTransition.java119
-rw-r--r--com/android/systemui/shared/system/ActivityManagerWrapper.java219
-rw-r--r--com/android/systemui/shared/system/AssistDataReceiverCompat.java28
-rw-r--r--com/android/systemui/shared/system/BackgroundExecutor.java53
-rw-r--r--com/android/systemui/shared/system/TaskStackChangeListener.java (renamed from com/android/systemui/recents/misc/TaskStackChangeListener.java)29
-rw-r--r--com/android/systemui/shared/system/TaskStackChangeListeners.java (renamed from com/android/systemui/recents/misc/TaskStackChangeListeners.java)14
-rw-r--r--com/android/systemui/shared/system/WindowManagerWrapper.java45
-rw-r--r--com/android/systemui/shortcut/ShortcutKeyDispatcher.java8
-rw-r--r--com/android/systemui/stackdivider/DividerView.java2
-rw-r--r--com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java7
-rw-r--r--com/android/systemui/statusbar/ExpandableNotificationRow.java38
-rw-r--r--com/android/systemui/statusbar/NotificationGuts.java27
-rw-r--r--com/android/systemui/statusbar/NotificationGutsManager.java355
-rw-r--r--com/android/systemui/statusbar/NotificationInfo.java4
-rw-r--r--com/android/systemui/statusbar/NotificationMediaManager.java245
-rw-r--r--com/android/systemui/statusbar/NotificationPresenter.java79
-rw-r--r--com/android/systemui/statusbar/OperatorNameView.java155
-rw-r--r--com/android/systemui/statusbar/ViewTransformationHelper.java13
-rw-r--r--com/android/systemui/statusbar/car/CarStatusBar.java13
-rw-r--r--com/android/systemui/statusbar/notification/ImageTransformState.java4
-rw-r--r--com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java411
-rw-r--r--com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java53
-rw-r--r--com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java2
-rw-r--r--com/android/systemui/statusbar/notification/NotificationUtils.java10
-rw-r--r--com/android/systemui/statusbar/notification/NotificationViewWrapper.java4
-rw-r--r--com/android/systemui/statusbar/notification/TextViewTransformState.java4
-rw-r--r--com/android/systemui/statusbar/notification/TransformState.java137
-rw-r--r--com/android/systemui/statusbar/phone/AutoTileManager.java18
-rw-r--r--com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java23
-rw-r--r--com/android/systemui/statusbar/phone/LightBarController.java3
-rw-r--r--com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java59
-rw-r--r--com/android/systemui/statusbar/phone/NavigationBarView.java25
-rw-r--r--com/android/systemui/statusbar/phone/NotificationPanelView.java2
-rw-r--r--com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java7
-rw-r--r--com/android/systemui/statusbar/phone/StatusBar.java641
-rw-r--r--com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java28
-rw-r--r--com/android/systemui/tuner/PluginFragment.java19
-rw-r--r--com/android/systemui/usb/UsbConfirmActivity.java12
-rw-r--r--com/android/systemui/usb/UsbPermissionActivity.java139
-rw-r--r--com/android/systemui/volume/SystemUIInterpolators.java78
-rw-r--r--com/android/systemui/volume/VolumeDialogComponent.java6
-rw-r--r--com/android/systemui/volume/VolumeDialogImpl.java431
-rw-r--r--com/android/systemui/volume/VolumeDialogMotion.java317
-rw-r--r--com/android/systemui/volume/ZenFooter.java180
-rw-r--r--java/lang/invoke/ByteArrayViewVarHandle.java (renamed from java/lang/invoke/ByteArrayVarHandle.java)12
-rw-r--r--java/lang/invoke/ByteBufferViewVarHandle.java4
-rw-r--r--java/lang/invoke/CallSite.java350
-rw-r--r--java/lang/invoke/MethodHandle.java1347
-rw-r--r--java/lang/invoke/MethodHandles.java3430
-rw-r--r--java/lang/invoke/MethodType.java1205
-rw-r--r--java/lang/invoke/VarHandle.java62
-rw-r--r--java/net/URL.java4
-rw-r--r--java/text/DecimalFormat.java9
-rw-r--r--javax/obex/ObexHelper.java18
-rw-r--r--org/json/JSONArrayTest.java567
-rw-r--r--org/json/JSONObjectTest.java1047
-rw-r--r--org/json/JSONStringerTest.java353
-rw-r--r--org/json/JSONTokenerTest.java619
-rw-r--r--org/json/ParsingTest.java270
-rw-r--r--org/json/SelfUseTest.java229
-rw-r--r--tck/java/time/zone/TCKZoneRules.java16
838 files changed, 46947 insertions, 18662 deletions
diff --git a/android/animation/ValueAnimator.java b/android/animation/ValueAnimator.java
index ee89ca8d..cc95eb6f 100644
--- a/android/animation/ValueAnimator.java
+++ b/android/animation/ValueAnimator.java
@@ -254,6 +254,11 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
HashMap<String, PropertyValuesHolder> mValuesMap;
/**
+ * If set to non-negative value, this will override {@link #sDurationScale}.
+ */
+ private float mDurationScale = -1f;
+
+ /**
* Public constants
*/
@@ -579,8 +584,23 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
return this;
}
+ /**
+ * Overrides the global duration scale by a custom value.
+ *
+ * @param durationScale The duration scale to set; or {@code -1f} to use the global duration
+ * scale.
+ * @hide
+ */
+ public void overrideDurationScale(float durationScale) {
+ mDurationScale = durationScale;
+ }
+
+ private float resolveDurationScale() {
+ return mDurationScale >= 0f ? mDurationScale : sDurationScale;
+ }
+
private long getScaledDuration() {
- return (long)(mDuration * sDurationScale);
+ return (long)(mDuration * resolveDurationScale());
}
/**
@@ -735,7 +755,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
if (mSeekFraction >= 0) {
return (long) (mDuration * mSeekFraction);
}
- float durationScale = sDurationScale == 0 ? 1 : sDurationScale;
+ float durationScale = resolveDurationScale();
+ if (durationScale == 0f) {
+ durationScale = 1f;
+ }
return (long) ((AnimationUtils.currentAnimationTimeMillis() - mStartTime) / durationScale);
}
@@ -1397,7 +1420,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
- mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
+ mStartTime = mReversing
+ ? frameTime
+ : frameTime + (long) (mStartDelay * resolveDurationScale());
}
// Handle pause/resume
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 9d331a02..99f3dee7 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -16,8 +16,6 @@
package android.app;
-import static android.os.Build.VERSION_CODES.O_MR1;
-
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
@@ -136,6 +134,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+
/**
* An activity is a single, focused thing that the user can do. Almost all
* activities interact with the user, so the Activity class takes care of
@@ -194,10 +193,13 @@ import java.util.List;
* <a name="Fragments"></a>
* <h3>Fragments</h3>
*
- * <p>Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, Activity
- * implementations can make use of the {@link Fragment} class to better
+ * <p>The {@link android.support.v4.app.FragmentActivity} subclass
+ * can make use of the {@link android.support.v4.app.Fragment} class to better
* modularize their code, build more sophisticated user interfaces for larger
- * screens, and help scale their application between small and large screens.
+ * screens, and help scale their application between small and large screens.</p>
+ *
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
*
* <a name="ActivityLifecycle"></a>
* <h3>Activity Lifecycle</h3>
@@ -916,7 +918,10 @@ public class Activity extends ContextThemeWrapper
/**
* Return the LoaderManager for this activity, creating it if needed.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportLoaderManager()}
*/
+ @Deprecated
public LoaderManager getLoaderManager() {
return mFragments.getLoaderManager();
}
@@ -991,17 +996,6 @@ public class Activity extends ContextThemeWrapper
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
- if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
- final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
- final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
- ta.recycle();
-
- if (isTranslucentOrFloating) {
- throw new IllegalStateException(
- "Only fullscreen opaque activities can request orientation");
- }
- }
-
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
@@ -2407,7 +2401,10 @@ public class Activity extends ContextThemeWrapper
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
*/
+ @Deprecated
public FragmentManager getFragmentManager() {
return mFragments.getFragmentManager();
}
@@ -2416,7 +2413,11 @@ public class Activity extends ContextThemeWrapper
* Called when a Fragment is being attached to this activity, immediately
* after the call to its {@link Fragment#onAttach Fragment.onAttach()}
* method and before {@link Fragment#onCreate Fragment.onCreate()}.
+ *
+ * @deprecated Use {@link
+ * android.support.v4.app.FragmentActivity#onAttachFragment(android.support.v4.app.Fragment)}
*/
+ @Deprecated
public void onAttachFragment(Fragment fragment) {
}
@@ -5118,7 +5119,11 @@ public class Activity extends ContextThemeWrapper
*
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+ * android.support.v4.app.Fragment,Intent,int)}
*/
+ @Deprecated
public void startActivityFromFragment(@NonNull Fragment fragment,
@RequiresPermission Intent intent, int requestCode) {
startActivityFromFragment(fragment, intent, requestCode, null);
@@ -5143,7 +5148,11 @@ public class Activity extends ContextThemeWrapper
*
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+ * android.support.v4.app.Fragment,Intent,int,Bundle)}
*/
+ @Deprecated
public void startActivityFromFragment(@NonNull Fragment fragment,
@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
startActivityForResult(fragment.mWho, intent, requestCode, options);
@@ -7304,24 +7313,25 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Request to put this Activity in a mode where the user is locked to the
- * current task.
+ * Request to put this activity in a mode where the user is locked to a restricted set of
+ * applications.
*
- * This will prevent the user from launching other apps, going to settings, or reaching the
- * home screen. This does not include those apps whose {@link android.R.attr#lockTaskMode}
- * values permit launching while locked.
+ * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true}
+ * for this component, the current task will be launched directly into LockTask mode. Only apps
+ * whitelisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can
+ * be launched while LockTask mode is active. The user will not be able to leave this mode
+ * until this activity calls {@link #stopLockTask()}. Calling this method while the device is
+ * already in LockTask mode has no effect.
*
- * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true or
- * lockTaskMode=lockTaskModeAlways for this component then the app will go directly into
- * Lock Task mode. The user will not be able to exit this mode until
- * {@link Activity#stopLockTask()} is called.
+ * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the
+ * system will prompt the user with a dialog requesting permission to use this mode.
+ * The user can exit at any time through instructions shown on the request dialog. Calling
+ * {@link #stopLockTask()} will also terminate this mode.
*
- * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
- * then the system will prompt the user with a dialog requesting permission to enter
- * this mode. When entered through this method the user can exit at any time through
- * an action described by the request dialog. Calling stopLockTask will also exit the
- * mode.
+ * <p><strong>Note:</strong> this method can only be called when the activity is foreground.
+ * That is, between {@link #onResume()} and {@link #onPause()}.
*
+ * @see #stopLockTask()
* @see android.R.attr#lockTaskMode
*/
public void startLockTask() {
@@ -7332,25 +7342,24 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Allow the user to switch away from the current task.
+ * Stop the current task from being locked.
*
- * Called to end the mode started by {@link Activity#startLockTask}. This
- * can only be called by activities that have successfully called
- * startLockTask previously.
+ * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}.
+ * This can only be called by activities that have called {@link #startLockTask()} previously.
*
- * This will allow the user to exit this app and move onto other activities.
- * <p>Note: This method should only be called when the activity is user-facing. That is,
- * between onResume() and onPause().
- * <p>Note: If there are other tasks below this one that are also locked then calling this
- * method will immediately finish this task and resume the previous locked one, remaining in
- * lockTask mode.
+ * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started
+ * by this activity, then calling this method will not terminate the LockTask mode, but only
+ * finish its own task. The device will remain in LockTask mode, until the activity which
+ * started the LockTask mode calls this method, or until its whitelist authorization is revoked
+ * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
*
+ * @see #startLockTask()
* @see android.R.attr#lockTaskMode
* @see ActivityManager#getLockTaskModeState()
*/
public void stopLockTask() {
try {
- ActivityManager.getService().stopLockTaskMode();
+ ActivityManager.getService().stopLockTaskModeByToken(mToken);
} catch (RemoteException e) {
}
}
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 8d9dc1fa..064e9782 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -682,20 +682,23 @@ public class ActivityManager {
}
/**
- * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
- * specifies the position of the created docked stack at the top half of the screen if
+ * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+ * which specifies the position of the created docked stack at the top half of the screen if
* in portrait mode or at the left half of the screen if in landscape mode.
* @hide
*/
- public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0;
+ @TestApi
+ public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
/**
- * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
+ * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+ * which
* specifies the position of the created docked stack at the bottom half of the screen if
* in portrait mode or at the right half of the screen if in landscape mode.
* @hide
*/
- public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
+ @TestApi
+ public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
/**
* Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
@@ -1925,6 +1928,33 @@ public class ActivityManager {
}
/**
+ * Moves the input task to the primary-split-screen stack.
+ * @param taskId Id of task to move.
+ * @param createMode The mode the primary split screen stack should be created in if it doesn't
+ * exist already. See
+ * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
+ * and
+ * {@link android.app.ActivityManager
+ * #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
+ * @param toTop If the task and stack should be moved to the top.
+ * @param animate Whether we should play an animation for the moving the task
+ * @param initialBounds If the primary stack gets created, it will use these bounds for the
+ * docked stack. Pass {@code null} to use default bounds.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+ boolean animate, Rect initialBounds) throws SecurityException {
+ try {
+ getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate,
+ initialBounds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Resizes the input stack id to the given bounds.
* @param stackId Id of the stack to resize.
* @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
index 9d14f616..a46b3c72 100644
--- a/android/app/ActivityManagerInternal.java
+++ b/android/app/ActivityManagerInternal.java
@@ -64,6 +64,27 @@ public abstract class ActivityManagerInternal {
public static final int APP_TRANSITION_SNAPSHOT = 4;
/**
+ * The bundle key to extract the assist data.
+ */
+ public static final String ASSIST_KEY_DATA = "data";
+
+ /**
+ * The bundle key to extract the assist structure.
+ */
+ public static final String ASSIST_KEY_STRUCTURE = "structure";
+
+ /**
+ * The bundle key to extract the assist content.
+ */
+ public static final String ASSIST_KEY_CONTENT = "content";
+
+ /**
+ * The bundle key to extract the assist receiver extras.
+ */
+ public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras";
+
+
+ /**
* Grant Uri permissions from one app to another. This method only extends
* permission grants if {@code callingUid} has permission to them.
*/
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index b62e4c2d..4a21f5c4 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -16,7 +16,7 @@
package android.app;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
@@ -203,10 +203,11 @@ public class ActivityOptions {
"android.activity.taskOverlayCanResume";
/**
- * Where the docked stack should be positioned.
+ * Where the split-screen-primary stack should be positioned.
* @hide
*/
- private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
+ private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
+ "android:activity.splitScreenCreateMode";
/**
* Determines whether to disallow the outgoing activity from entering picture-in-picture as the
@@ -292,7 +293,7 @@ public class ActivityOptions {
@WindowConfiguration.ActivityType
private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
private int mLaunchTaskId = -1;
- private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
@@ -884,7 +885,8 @@ public class ActivityOptions {
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
- mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+ mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
if (opts.containsKey(KEY_ANIM_SPECS)) {
@@ -1194,13 +1196,13 @@ public class ActivityOptions {
}
/** @hide */
- public int getDockCreateMode() {
- return mDockCreateMode;
+ public int getSplitScreenCreateMode() {
+ return mSplitScreenCreateMode;
}
/** @hide */
- public void setDockCreateMode(int dockCreateMode) {
- mDockCreateMode = dockCreateMode;
+ public void setSplitScreenCreateMode(int splitScreenCreateMode) {
+ mSplitScreenCreateMode = splitScreenCreateMode;
}
/** @hide */
@@ -1369,7 +1371,7 @@ public class ActivityOptions {
b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
- b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+ b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
mDisallowEnterPictureInPictureWhileLaunching);
if (mAnimSpecs != null) {
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 2516a3e9..21e454f1 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -5533,32 +5533,8 @@ public final class ActivityThread {
View.mDebugViewAttributes =
mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
- /**
- * For system applications on userdebug/eng builds, log stack
- * traces of disk and network access to dropbox for analysis.
- */
- if ((data.appInfo.flags &
- (ApplicationInfo.FLAG_SYSTEM |
- ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
- StrictMode.conditionallyEnableDebugLogging();
- }
-
- /**
- * For apps targetting Honeycomb or later, we don't allow network usage
- * on the main event loop / UI thread. This is what ultimately throws
- * {@link NetworkOnMainThreadException}.
- */
- if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
- StrictMode.enableDeathOnNetwork();
- }
-
- /**
- * For apps targetting N or later, we don't allow file:// Uri exposure.
- * This is what ultimately throws {@link FileUriExposedException}.
- */
- if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
- StrictMode.enableDeathOnFileUriExposure();
- }
+ StrictMode.initThreadDefaults(data.appInfo);
+ StrictMode.initVmDefaults(data.appInfo);
// We deprecated Build.SERIAL and only apps that target pre NMR1
// SDK can see it. Since access to the serial is now behind a
@@ -5655,7 +5631,12 @@ public final class ActivityThread {
mResourcesManager.getConfiguration().getLocales());
if (!Process.isIsolated()) {
- setupGraphicsSupport(appContext);
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try {
+ setupGraphicsSupport(appContext);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
}
// If we use profiles, setup the dex reporter to notify package manager
diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java
index 4bd85ae9..b6fb1201 100644
--- a/android/app/AppOpsManager.java
+++ b/android/app/AppOpsManager.java
@@ -254,8 +254,10 @@ public class AppOpsManager {
public static final int OP_ANSWER_PHONE_CALLS = 69;
/** @hide Run jobs when in background */
public static final int OP_RUN_ANY_IN_BACKGROUND = 70;
+ /** @hide Change Wi-Fi connectivity state */
+ public static final int OP_CHANGE_WIFI_STATE = 71;
/** @hide */
- public static final int _NUM_OP = 71;
+ public static final int _NUM_OP = 72;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -496,6 +498,7 @@ public class AppOpsManager {
OP_INSTANT_APP_START_FOREGROUND,
OP_ANSWER_PHONE_CALLS,
OP_RUN_ANY_IN_BACKGROUND,
+ OP_CHANGE_WIFI_STATE,
};
/**
@@ -574,6 +577,7 @@ public class AppOpsManager {
OPSTR_INSTANT_APP_START_FOREGROUND,
OPSTR_ANSWER_PHONE_CALLS,
null, // OP_RUN_ANY_IN_BACKGROUND
+ null, // OP_CHANGE_WIFI_STATE
};
/**
@@ -652,6 +656,7 @@ public class AppOpsManager {
"INSTANT_APP_START_FOREGROUND",
"ANSWER_PHONE_CALLS",
"RUN_ANY_IN_BACKGROUND",
+ "CHANGE_WIFI_STATE",
};
/**
@@ -730,6 +735,7 @@ public class AppOpsManager {
Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
Manifest.permission.ANSWER_PHONE_CALLS,
null, // no permission for OP_RUN_ANY_IN_BACKGROUND
+ Manifest.permission.CHANGE_WIFI_STATE,
};
/**
@@ -809,6 +815,7 @@ public class AppOpsManager {
null, // INSTANT_APP_START_FOREGROUND
null, // ANSWER_PHONE_CALLS
null, // OP_RUN_ANY_IN_BACKGROUND
+ null, // OP_CHANGE_WIFI_STATE
};
/**
@@ -887,6 +894,7 @@ public class AppOpsManager {
false, // INSTANT_APP_START_FOREGROUND
false, // ANSWER_PHONE_CALLS
false, // OP_RUN_ANY_IN_BACKGROUND
+ false, // OP_CHANGE_WIFI_STATE
};
/**
@@ -964,6 +972,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND
+ AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE
};
/**
@@ -1045,6 +1054,7 @@ public class AppOpsManager {
false,
false, // ANSWER_PHONE_CALLS
false, // OP_RUN_ANY_IN_BACKGROUND
+ false, // OP_CHANGE_WIFI_STATE
};
/**
diff --git a/android/app/DexLoadReporter.java b/android/app/DexLoadReporter.java
index f99d1a8e..06434147 100644
--- a/android/app/DexLoadReporter.java
+++ b/android/app/DexLoadReporter.java
@@ -19,7 +19,6 @@ package android.app;
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.system.ErrnoException;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -27,8 +26,6 @@ import com.android.internal.annotations.GuardedBy;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;
-import libcore.io.Libcore;
-
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -155,23 +152,12 @@ import java.util.Set;
return;
}
- File realDexPath;
- try {
- // Secondary dex profiles are stored in the oat directory, next to the real dex file
- // and have the same name with 'cur.prof' appended. We use the realpath because that
- // is what installd is using when processing the dex file.
- // NOTE: Keep in sync with installd.
- realDexPath = new File(Libcore.os.realpath(dexPath));
- } catch (ErrnoException ex) {
- Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath
- + ":" + ex.getMessage());
- // Do not continue with registration if we could not retrieve the real path.
- return;
- }
-
+ // Secondary dex profiles are stored in the oat directory, next to dex file
+ // and have the same name with 'cur.prof' appended.
// NOTE: Keep this in sync with installd expectations.
- File secondaryProfileDir = new File(realDexPath.getParent(), "oat");
- File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof");
+ File dexPathFile = new File(dexPath);
+ File secondaryProfileDir = new File(dexPathFile.getParent(), "oat");
+ File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof");
// Create the profile if not already there.
// Returns true if the file was created, false if the file already exists.
diff --git a/android/app/DialogFragment.java b/android/app/DialogFragment.java
index 7e0e4d82..a0fb6eeb 100644
--- a/android/app/DialogFragment.java
+++ b/android/app/DialogFragment.java
@@ -136,7 +136,10 @@ import java.io.PrintWriter;
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
* embed}
+ *
+ * @deprecated Use {@link android.support.v4.app.DialogFragment}
*/
+@Deprecated
public class DialogFragment extends Fragment
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
diff --git a/android/app/Fragment.java b/android/app/Fragment.java
index 93773454..a92684b5 100644
--- a/android/app/Fragment.java
+++ b/android/app/Fragment.java
@@ -256,7 +256,10 @@ import java.lang.reflect.InvocationTargetException;
* <p>After each call to this function, a new entry is on the stack, and
* pressing back will pop it to return the user to whatever previous state
* the activity UI was in.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment}
*/
+@Deprecated
public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
private static final ArrayMap<String, Class<?>> sClassMap =
new ArrayMap<String, Class<?>>();
@@ -414,7 +417,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* State information that has been retrieved from a fragment instance
* through {@link FragmentManager#saveFragmentInstanceState(Fragment)
* FragmentManager.saveFragmentInstanceState}.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment.SavedState}
*/
+ @Deprecated
public static class SavedState implements Parcelable {
final Bundle mState;
@@ -458,7 +464,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
/**
* Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when
* there is an instantiation failure.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment.InstantiationException}
*/
+ @Deprecated
static public class InstantiationException extends AndroidRuntimeException {
public InstantiationException(String msg, Exception cause) {
super(msg, cause);
@@ -1031,7 +1040,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
/**
* Return the LoaderManager for this fragment, creating it if needed.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment#getLoaderManager()}
*/
+ @Deprecated
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
diff --git a/android/app/FragmentBreadCrumbs.java b/android/app/FragmentBreadCrumbs.java
index d0aa0fd6..e3e47ae6 100644
--- a/android/app/FragmentBreadCrumbs.java
+++ b/android/app/FragmentBreadCrumbs.java
@@ -65,7 +65,10 @@ public class FragmentBreadCrumbs extends ViewGroup
/**
* Interface to intercept clicks on the bread crumbs.
+ *
+ * @deprecated This widget is no longer supported.
*/
+ @Deprecated
public interface OnBreadCrumbClickListener {
/**
* Called when a bread crumb is clicked.
diff --git a/android/app/FragmentContainer.java b/android/app/FragmentContainer.java
index f8836bc8..a1dd32ff 100644
--- a/android/app/FragmentContainer.java
+++ b/android/app/FragmentContainer.java
@@ -24,7 +24,10 @@ import android.view.View;
/**
* Callbacks to a {@link Fragment}'s container.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentContainer}
*/
+@Deprecated
public abstract class FragmentContainer {
/**
* Return the view with the given resource ID. May return {@code null} if the
diff --git a/android/app/FragmentController.java b/android/app/FragmentController.java
index cff94d8c..cbb58d40 100644
--- a/android/app/FragmentController.java
+++ b/android/app/FragmentController.java
@@ -37,7 +37,10 @@ import java.util.List;
* <p>
* It is the responsibility of the host to take care of the Fragment's lifecycle.
* The methods provided by {@link FragmentController} are for that purpose.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentController}
*/
+@Deprecated
public class FragmentController {
private final FragmentHostCallback<?> mHost;
diff --git a/android/app/FragmentHostCallback.java b/android/app/FragmentHostCallback.java
index 5ef23e63..1edc68ed 100644
--- a/android/app/FragmentHostCallback.java
+++ b/android/app/FragmentHostCallback.java
@@ -37,7 +37,10 @@ import java.io.PrintWriter;
* Fragments may be hosted by any object; such as an {@link Activity}. In order to
* host fragments, implement {@link FragmentHostCallback}, overriding the methods
* applicable to the host.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
*/
+@Deprecated
public abstract class FragmentHostCallback<E> extends FragmentContainer {
private final Activity mActivity;
final Context mContext;
diff --git a/android/app/FragmentManager.java b/android/app/FragmentManager.java
index 0d5cd021..12e60b87 100644
--- a/android/app/FragmentManager.java
+++ b/android/app/FragmentManager.java
@@ -74,7 +74,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
* {@link android.support.v4.app.FragmentActivity}. See the blog post
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager}
*/
+@Deprecated
public abstract class FragmentManager {
/**
* Representation of an entry on the fragment back stack, as created
@@ -86,7 +89,10 @@ public abstract class FragmentManager {
* <p>Note that you should never hold on to a BackStackEntry object;
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
*/
+ @Deprecated
public interface BackStackEntry {
/**
* Return the unique identifier for the entry. This is the only
@@ -129,7 +135,10 @@ public abstract class FragmentManager {
/**
* Interface to watch for changes to the back stack.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
*/
+ @Deprecated
public interface OnBackStackChangedListener {
/**
* Called whenever the contents of the back stack change.
@@ -428,7 +437,10 @@ public abstract class FragmentManager {
/**
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
*/
+ @Deprecated
public abstract static class FragmentLifecycleCallbacks {
/**
* Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
diff --git a/android/app/FragmentManagerNonConfig.java b/android/app/FragmentManagerNonConfig.java
index 50d3797d..beb1a15a 100644
--- a/android/app/FragmentManagerNonConfig.java
+++ b/android/app/FragmentManagerNonConfig.java
@@ -27,7 +27,10 @@ import java.util.List;
* and passed to the state save and restore process for fragments in
* {@link FragmentController#retainNonConfig()} and
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
*/
+@Deprecated
public class FragmentManagerNonConfig {
private final List<Fragment> mFragments;
private final List<FragmentManagerNonConfig> mChildNonConfigs;
diff --git a/android/app/FragmentTransaction.java b/android/app/FragmentTransaction.java
index c910e903..0f4a7fb5 100644
--- a/android/app/FragmentTransaction.java
+++ b/android/app/FragmentTransaction.java
@@ -21,7 +21,10 @@ import java.lang.annotation.RetentionPolicy;
* <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer
* guide.</p>
* </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
*/
+@Deprecated
public abstract class FragmentTransaction {
/**
* Calls {@link #add(int, Fragment, String)} with a 0 containerViewId.
diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java
index e260967f..d49e11f4 100644
--- a/android/app/Instrumentation.java
+++ b/android/app/Instrumentation.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -418,22 +419,51 @@ public class Instrumentation {
* different process. In addition, if the given Intent resolves to
* multiple activities, instead of displaying a dialog for the user to
* select an activity, an exception will be thrown.
- *
+ *
* <p>The function returns as soon as the activity goes idle following the
* call to its {@link Activity#onCreate}. Generally this means it has gone
* through the full initialization including {@link Activity#onResume} and
* drawn and displayed its initial window.
- *
+ *
* @param intent Description of the activity to start.
- *
+ *
* @see Context#startActivity
+ * @see #startActivitySync(Intent, Bundle)
*/
public Activity startActivitySync(Intent intent) {
+ return startActivitySync(intent, null /* options */);
+ }
+
+ /**
+ * Start a new activity and wait for it to begin running before returning.
+ * In addition to being synchronous, this method as some semantic
+ * differences from the standard {@link Context#startActivity} call: the
+ * activity component is resolved before talking with the activity manager
+ * (its class name is specified in the Intent that this method ultimately
+ * starts), and it does not allow you to start activities that run in a
+ * different process. In addition, if the given Intent resolves to
+ * multiple activities, instead of displaying a dialog for the user to
+ * select an activity, an exception will be thrown.
+ *
+ * <p>The function returns as soon as the activity goes idle following the
+ * call to its {@link Activity#onCreate}. Generally this means it has gone
+ * through the full initialization including {@link Activity#onResume} and
+ * drawn and displayed its initial window.
+ *
+ * @param intent Description of the activity to start.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
+ *
+ * @see Context#startActivity(Intent, Bundle)
+ */
+ public Activity startActivitySync(Intent intent, @Nullable Bundle options) {
validateNotAppThread();
synchronized (mSync) {
intent = new Intent(intent);
-
+
ActivityInfo ai = intent.resolveActivityInfo(
getTargetContext().getPackageManager(), 0);
if (ai == null) {
@@ -447,7 +477,7 @@ public class Instrumentation {
+ myProc + " resolved to different process "
+ ai.processName + ": " + intent);
}
-
+
intent.setComponent(new ComponentName(
ai.applicationInfo.packageName, ai.name));
final ActivityWaiter aw = new ActivityWaiter(intent);
@@ -457,7 +487,7 @@ public class Instrumentation {
}
mWaitingActivities.add(aw);
- getTargetContext().startActivity(intent);
+ getTargetContext().startActivity(intent, options);
do {
try {
@@ -465,7 +495,7 @@ public class Instrumentation {
} catch (InterruptedException e) {
}
} while (mWaitingActivities.contains(aw));
-
+
return aw.activity;
}
}
diff --git a/android/app/ListFragment.java b/android/app/ListFragment.java
index 0b96d84d..90b77b39 100644
--- a/android/app/ListFragment.java
+++ b/android/app/ListFragment.java
@@ -144,7 +144,10 @@ import android.widget.TextView;
*
* @see #setListAdapter
* @see android.widget.ListView
+ *
+ * @deprecated Use {@link android.support.v4.app.ListFragment}
*/
+@Deprecated
public class ListFragment extends Fragment {
final private Handler mHandler = new Handler();
diff --git a/android/app/LoaderManager.java b/android/app/LoaderManager.java
index 56dfc589..7969684a 100644
--- a/android/app/LoaderManager.java
+++ b/android/app/LoaderManager.java
@@ -54,11 +54,17 @@ import java.lang.reflect.Modifier;
* <p>For more information about using loaders, read the
* <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
* </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.LoaderManager}
*/
+@Deprecated
public abstract class LoaderManager {
/**
* Callback interface for a client to interact with the manager.
+ *
+ * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
*/
+ @Deprecated
public interface LoaderCallbacks<D> {
/**
* Instantiate and return a new Loader for the given ID.
diff --git a/android/app/Notification.java b/android/app/Notification.java
index 8226e0fb..d5d95fb8 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -22,6 +22,7 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@ public class Notification implements Parcelable
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
- bindLargeIcon(contentView);
+ bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@ public class Notification implements Parcelable
}
}
- private void bindLargeIcon(RemoteViews contentView) {
+ private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+ boolean alwaysShowReply) {
if (mN.mLargeIcon == null && mN.largeIcon != null) {
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
- if (mN.mLargeIcon != null) {
+ boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+ if (showLargeIcon) {
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@ public class Notification implements Parcelable
contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
- // Bind the reply action
- Action action = findReplyAction();
- contentView.setViewVisibility(R.id.reply_icon_action, action != null
- ? View.VISIBLE
- : View.GONE);
-
- if (action != null) {
- int contrastColor = resolveContrastColor();
+ }
+ // Bind the reply action
+ Action action = findReplyAction();
+
+ boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+ int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+ if (actionVisible) {
+ // We're only showing the icon as big if we're hiding the large icon
+ int contrastColor = resolveContrastColor();
+ int iconColor;
+ if (showLargeIcon) {
contentView.setDrawableTint(R.id.reply_icon_action,
true /* targetBackground */,
contrastColor, PorterDuff.Mode.SRC_ATOP);
- int iconColor = NotificationColorUtil.isColorLight(contrastColor)
- ? Color.BLACK : Color.WHITE;
- contentView.setDrawableTint(R.id.reply_icon_action,
- false /* targetBackground */,
- iconColor, PorterDuff.Mode.SRC_ATOP);
contentView.setOnClickPendingIntent(R.id.right_icon,
action.actionIntent);
- contentView.setOnClickPendingIntent(R.id.reply_icon_action,
- action.actionIntent);
contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
- contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+ iconColor = NotificationColorUtil.isColorLight(contrastColor)
+ ? Color.BLACK : Color.WHITE;
+ } else {
+ contentView.setImageViewResource(R.id.right_icon,
+ R.drawable.ic_reply_notification_large);
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ iconColor = contrastColor;
}
+ contentView.setDrawableTint(replyId,
+ false /* targetBackground */,
+ iconColor,
+ PorterDuff.Mode.SRC_ATOP);
+ contentView.setOnClickPendingIntent(replyId,
+ action.actionIntent);
+ contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+ } else {
+ contentView.setRemoteInputs(R.id.right_icon, null);
}
- contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+ contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+ ? View.VISIBLE
+ : View.GONE);
+ contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
? View.VISIBLE
: View.GONE);
}
@@ -6055,18 +6071,12 @@ public class Notification implements Parcelable
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
- mMessages.clear();
- mHistoricMessages.clear();
mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (messages != null && messages instanceof Parcelable[]) {
- mMessages = Message.getMessagesFromBundleArray(messages);
- }
+ mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
- if (histMessages != null && histMessages instanceof Parcelable[]) {
- mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
- }
+ mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
}
/**
@@ -6074,38 +6084,34 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (!increasedHeight) {
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
- } else {
- mBuilder.mOriginalActions = mBuilder.mActions;
- mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView();
- mBuilder.mActions = mBuilder.mOriginalActions;
- mBuilder.mOriginalActions = null;
- return remoteViews;
- }
+ mBuilder.mOriginalActions = mBuilder.mActions;
+ mBuilder.mActions = new ArrayList<>();
+ RemoteViews remoteViews = makeBigContentView();
+ mBuilder.mActions = mBuilder.mOriginalActions;
+ mBuilder.mOriginalActions = null;
+ return remoteViews;
}
private Message findLatestIncomingMessage() {
- for (int i = mMessages.size() - 1; i >= 0; i--) {
- Message m = mMessages.get(i);
+ return findLatestIncomingMessage(mMessages);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static Message findLatestIncomingMessage(
+ List<Message> messages) {
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ Message m = messages.get(i);
// Incoming messages have a non-empty sender.
if (!TextUtils.isEmpty(m.mSender)) {
return m;
}
}
- if (!mMessages.isEmpty()) {
+ if (!messages.isEmpty()) {
// No incoming messages, fall back to outgoing message
- return mMessages.get(mMessages.size() - 1);
+ return messages.get(messages.size() - 1);
}
return null;
}
@@ -6115,118 +6121,82 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
- CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+ CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
- boolean hasTitle = !TextUtils.isEmpty(title);
-
- if (mMessages.size() == 1) {
- // Special case for a single message: Use the big text style
- // so the collapsed and expanded versions match nicely.
- CharSequence bigTitle;
- CharSequence text;
- if (hasTitle) {
- bigTitle = title;
- text = makeMessageLine(mMessages.get(0), mBuilder);
- } else {
- bigTitle = mMessages.get(0).mSender;
- text = mMessages.get(0).mText;
- }
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getBigTextLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
- BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
- return contentView;
- }
-
+ boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+ if (isOneToOne) {
+ // Let's add the conversationTitle in case we didn't have one before and all
+ // messages are from the same sender
+ conversationTitle = createConversationTitleFromMessages();
+ } else if (hasOnlyWhiteSpaceSenders()) {
+ isOneToOne = true;
+ }
+ boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
- int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
- R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
- // Make sure all rows are gone in case we reuse a view.
- for (int rowId : rowIds) {
- contentView.setViewVisibility(rowId, View.GONE);
- }
+ mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+ .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+ addExtras(mBuilder.mN.extras);
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.resolveContrastColor());
+ contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+ mBuilder.mN.mLargeIcon);
+ contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+ isOneToOne);
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+ mBuilder.mN.extras);
+ return contentView;
+ }
- int i=0;
- contentView.setViewLayoutMarginBottomDimen(R.id.line1,
- hasTitle ? R.dimen.notification_messaging_spacing : 0);
- contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
- !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
- int contractedChildId = View.NO_ID;
- Message contractedMessage = findLatestIncomingMessage();
- int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
- - (rowIds.length - mMessages.size()));
- while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
- Message m = mHistoricMessages.get(firstHistoricMessage + i);
- int rowId = rowIds[i];
-
- contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
+ private boolean hasOnlyWhiteSpaceSenders() {
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (!isWhiteSpace(sender)) {
+ return false;
}
-
- i++;
}
+ return true;
+ }
- int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
- while (firstMessage + i < mMessages.size() && i < rowIds.length) {
- Message m = mMessages.get(firstMessage + i);
- int rowId = rowIds[i];
-
- contentView.setViewVisibility(rowId, View.VISIBLE);
- contentView.setTextViewText(rowId, mBuilder.processTextSpans(
- makeMessageLine(m, mBuilder)));
- mBuilder.setTextViewColorSecondary(contentView, rowId);
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
- }
-
- i++;
+ private boolean isWhiteSpace(CharSequence sender) {
+ if (TextUtils.isEmpty(sender)) {
+ return true;
}
- // Clear the remaining views for reapply. Ensures that historic message views can
- // reliably be identified as being GONE and having non-null text.
- while (i < rowIds.length) {
- int rowId = rowIds[i];
- contentView.setTextViewText(rowId, null);
- i++;
+ if (sender.toString().matches("^\\s*$")) {
+ return true;
}
-
- // Record this here to allow transformation between the contracted and expanded views.
- contentView.setInt(R.id.notification_messaging, "setContractedChildId",
- contractedChildId);
- return contentView;
+ // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
+ // For the presentation that we had.
+ for (int i = 0; i < sender.length(); i++) {
+ char c = sender.charAt(i);
+ if (c != '\u200B') {
+ return false;
+ }
+ }
+ return true;
}
- private CharSequence makeMessageLine(Message m, Builder builder) {
- BidiFormatter bidi = BidiFormatter.getInstance();
- SpannableStringBuilder sb = new SpannableStringBuilder();
- boolean colorize = builder.isColorized();
- TextAppearanceSpan colorSpan;
- CharSequence messageName;
- if (TextUtils.isEmpty(m.mSender)) {
- CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
- sb.append(bidi.unicodeWrap(replyName),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : mBuilder.resolveContrastColor()),
- 0 /* flags */);
- } else {
- sb.append(bidi.unicodeWrap(m.mSender),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : Color.BLACK),
- 0 /* flags */);
+ private CharSequence createConversationTitleFromMessages() {
+ ArraySet<CharSequence> names = new ArraySet<>();
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (sender != null) {
+ names.add(sender);
+ }
+ }
+ SpannableStringBuilder title = new SpannableStringBuilder();
+ int size = names.size();
+ for (int i = 0; i < size; i++) {
+ CharSequence name = names.valueAt(i);
+ if (!TextUtils.isEmpty(title)) {
+ title.append(", ");
+ }
+ title.append(BidiFormatter.getInstance().unicodeWrap(name));
}
- CharSequence text = m.mText == null ? "" : m.mText;
- sb.append(" ").append(bidi.unicodeWrap(text));
- return sb;
+ return title;
}
/**
@@ -6234,19 +6204,9 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (increasedHeight) {
- return makeBigContentView();
- }
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+ RemoteViews remoteViews = makeBigContentView();
+ remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
+ return remoteViews;
}
private static TextAppearanceSpan makeFontColorSpan(int color) {
@@ -6394,7 +6354,15 @@ public class Notification implements Parcelable
return bundles;
}
- static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ /**
+ * @return A list of messages read from the bundles.
+ *
+ * @hide
+ */
+ public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ if (bundles == null) {
+ return new ArrayList<>();
+ }
List<Message> messages = new ArrayList<>(bundles.length);
for (int i = 0; i < bundles.length; i++) {
if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8455,8 @@ public class Notification implements Parcelable
boolean ambient = false;
CharSequence title;
CharSequence text;
+ boolean hideLargeIcon;
+ public boolean alwaysShowReply;
final StandardTemplateParams reset() {
hasProgress = true;
@@ -8511,6 +8481,16 @@ public class Notification implements Parcelable
return this;
}
+ final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+ this.alwaysShowReply = alwaysShowReply;
+ return this;
+ }
+
+ final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+ this.hideLargeIcon = hideLargeIcon;
+ return this;
+ }
+
final StandardTemplateParams ambient(boolean ambient) {
Preconditions.checkState(title == null && text == null, "must set ambient before text");
this.ambient = ambient;
@@ -8527,7 +8507,6 @@ public class Notification implements Parcelable
text = extras.getCharSequence(EXTRA_TEXT);
}
this.text = b.processLegacyText(text, ambient);
-
return this;
}
}
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index a52dc1e4..f931589b 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -758,10 +758,10 @@ public class NotificationManager {
}
/**
- * Checks the ability to read/modify notification do not disturb policy for the calling package.
+ * Checks the ability to modify notification do not disturb policy for the calling package.
*
* <p>
- * Returns true if the calling package can read/modify notification policy.
+ * Returns true if the calling package can modify notification policy.
*
* <p>
* Apps can request policy access by sending the user to the activity that matches the system
@@ -839,8 +839,6 @@ public class NotificationManager {
* Gets the current notification policy.
*
* <p>
- * Only available if policy access is granted to this package.
- * See {@link #isNotificationPolicyAccessGranted}.
*/
public Policy getNotificationPolicy() {
INotificationManager service = getService();
diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java
index 50f1f364..e48946f2 100644
--- a/android/app/SystemServiceRegistry.java
+++ b/android/app/SystemServiceRegistry.java
@@ -41,6 +41,8 @@ import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
+import android.content.pm.crossprofile.CrossProfileApps;
+import android.content.pm.crossprofile.ICrossProfileApps;
import android.content.res.Resources;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -81,6 +83,7 @@ import android.net.INetworkPolicyManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
+import android.net.NetworkWatchlistManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -134,6 +137,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
+import android.util.StatsManager;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.WindowManager;
@@ -150,6 +154,7 @@ import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.net.INetworkWatchlistManager;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
@@ -304,14 +309,14 @@ final class SystemServiceRegistry {
}});
registerService(Context.BATTERY_SERVICE, BatteryManager.class,
- new StaticServiceFetcher<BatteryManager>() {
+ new CachedServiceFetcher<BatteryManager>() {
@Override
- public BatteryManager createService() throws ServiceNotFoundException {
+ public BatteryManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBatteryStats stats = IBatteryStats.Stub.asInterface(
ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME));
IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub
.asInterface(ServiceManager.getServiceOrThrow("batteryproperties"));
- return new BatteryManager(stats, registrar);
+ return new BatteryManager(ctx, stats, registrar);
}});
registerService(Context.NFC_SERVICE, NfcManager.class,
@@ -448,6 +453,13 @@ final class SystemServiceRegistry {
ctx.mMainThread.getHandler().getLooper());
}});
+ registerService(Context.STATS_MANAGER, StatsManager.class,
+ new StaticServiceFetcher<StatsManager>() {
+ @Override
+ public StatsManager createService() throws ServiceNotFoundException {
+ return new StatsManager();
+ }});
+
registerService(Context.STATUS_BAR_SERVICE, StatusBarManager.class,
new CachedServiceFetcher<StatusBarManager>() {
@Override
@@ -862,6 +874,17 @@ final class SystemServiceRegistry {
return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b));
}});
+ registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class,
+ new CachedServiceFetcher<NetworkWatchlistManager>() {
+ @Override
+ public NetworkWatchlistManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b =
+ ServiceManager.getServiceOrThrow(Context.NETWORK_WATCHLIST_SERVICE);
+ return new NetworkWatchlistManager(ctx,
+ INetworkWatchlistManager.Stub.asInterface(b));
+ }});
+
registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class,
new CachedServiceFetcher<SystemHealthManager>() {
@Override
@@ -909,6 +932,18 @@ final class SystemServiceRegistry {
public RulesManager createService(ContextImpl ctx) {
return new RulesManager(ctx.getOuterContext());
}});
+
+ registerService(Context.CROSS_PROFILE_APPS_SERVICE, CrossProfileApps.class,
+ new CachedServiceFetcher<CrossProfileApps>() {
+ @Override
+ public CrossProfileApps createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.CROSS_PROFILE_APPS_SERVICE);
+ return new CrossProfileApps(ctx.getOuterContext(),
+ ICrossProfileApps.Stub.asInterface(b));
+ }
+ });
}
/**
diff --git a/android/app/TimePickerDialog.java b/android/app/TimePickerDialog.java
index 0f006b66..8686944b 100644
--- a/android/app/TimePickerDialog.java
+++ b/android/app/TimePickerDialog.java
@@ -152,6 +152,9 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
public void onClick(View view) {
if (mTimePicker.validateInput()) {
TimePickerDialog.this.onClick(TimePickerDialog.this, BUTTON_POSITIVE);
+ // Clearing focus forces the dialog to commit any pending
+ // changes, e.g. typed text in a NumberPicker.
+ mTimePicker.clearFocus();
dismiss();
}
}
diff --git a/android/app/VrManager.java b/android/app/VrManager.java
index 5c6ffa39..392387a9 100644
--- a/android/app/VrManager.java
+++ b/android/app/VrManager.java
@@ -198,4 +198,20 @@ public class VrManager {
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the current standby status of the VR device. Standby mode is only used on standalone vr
+ * devices. Standby mode is a deep sleep state where it's appropriate to turn off vr mode.
+ *
+ * @param standby True if the device is entering standby, false if it's exiting standby.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_VR_MANAGER)
+ public void setStandbyEnabled(boolean standby) {
+ try {
+ mService.setStandbyEnabled(standby);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index de27b4fd..2c1fad1c 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -500,15 +500,12 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
* @hide
*/
public boolean supportSplitScreenWindowingMode() {
- return supportSplitScreenWindowingMode(mWindowingMode, mActivityType);
+ return supportSplitScreenWindowingMode(mActivityType);
}
/** @hide */
- public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) {
- if (activityType == ACTIVITY_TYPE_ASSISTANT) {
- return false;
- }
- return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+ public static boolean supportSplitScreenWindowingMode(int activityType) {
+ return activityType != ACTIVITY_TYPE_ASSISTANT;
}
/** @hide */
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 772c6d60..f0226b7e 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -3246,6 +3246,7 @@ public class DevicePolicyManager {
* that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
*/
public void wipeData(int flags) {
+ throwIfParentInstance("wipeData");
final String wipeReasonForUser = mContext.getString(
R.string.work_profile_deleted_description_dpm_wipe);
wipeDataInternal(flags, wipeReasonForUser);
@@ -3270,6 +3271,7 @@ public class DevicePolicyManager {
* @throws IllegalArgumentException if the input reason string is null or empty.
*/
public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
+ throwIfParentInstance("wipeDataWithReason");
Preconditions.checkNotNull(reason, "CharSequence is null");
wipeDataInternal(flags, reason.toString());
}
@@ -3283,7 +3285,6 @@ public class DevicePolicyManager {
* @hide
*/
private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
- throwIfParentInstance("wipeDataWithReason");
if (mService != null) {
try {
mService.wipeDataWithReason(flags, wipeReasonForUser);
@@ -6096,8 +6097,8 @@ public class DevicePolicyManager {
/**
* Flag used by {@link #createAndManageUser} to specify that the user should be created
- * ephemeral.
- * @hide
+ * ephemeral. Ephemeral users will be removed after switching to another user or rebooting the
+ * device.
*/
public static final int MAKE_USER_EPHEMERAL = 0x0002;
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index b640bd5b..530d84b4 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -16,6 +16,12 @@
package android.app.job;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.util.TimeUtils.formatDuration;
import android.annotation.BytesLong;
@@ -25,6 +31,8 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.ClipData;
import android.content.ComponentName;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.Uri;
import android.os.BaseBundle;
import android.os.Bundle;
@@ -56,6 +64,7 @@ public class JobInfo implements Parcelable {
NETWORK_TYPE_ANY,
NETWORK_TYPE_UNMETERED,
NETWORK_TYPE_NOT_ROAMING,
+ NETWORK_TYPE_CELLULAR,
NETWORK_TYPE_METERED,
})
@Retention(RetentionPolicy.SOURCE)
@@ -69,8 +78,21 @@ public class JobInfo implements Parcelable {
public static final int NETWORK_TYPE_UNMETERED = 2;
/** This job requires network connectivity that is not roaming. */
public static final int NETWORK_TYPE_NOT_ROAMING = 3;
- /** This job requires metered connectivity such as most cellular data networks. */
- public static final int NETWORK_TYPE_METERED = 4;
+ /** This job requires network connectivity that is a cellular network. */
+ public static final int NETWORK_TYPE_CELLULAR = 4;
+
+ /**
+ * This job requires metered connectivity such as most cellular data
+ * networks.
+ *
+ * @deprecated Cellular networks may be unmetered, or Wi-Fi networks may be
+ * metered, so this isn't a good way of selecting a specific
+ * transport. Instead, use {@link #NETWORK_TYPE_CELLULAR} or
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)}
+ * if your job requires a specific network transport.
+ */
+ @Deprecated
+ public static final int NETWORK_TYPE_METERED = NETWORK_TYPE_CELLULAR;
/** Sentinel value indicating that bytes are unknown. */
public static final int NETWORK_BYTES_UNKNOWN = -1;
@@ -253,7 +275,7 @@ public class JobInfo implements Parcelable {
private final long triggerContentMaxDelay;
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
- private final int networkType;
+ private final NetworkRequest networkRequest;
private final long networkBytes;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
@@ -385,10 +407,37 @@ public class JobInfo implements Parcelable {
}
/**
- * The kind of connectivity requirements that the job has.
+ * Return the basic description of the kind of network this job requires.
+ *
+ * @deprecated This method attempts to map {@link #getRequiredNetwork()}
+ * into the set of simple constants, which results in a loss of
+ * fidelity. Callers should move to using
+ * {@link #getRequiredNetwork()} directly.
+ * @see Builder#setRequiredNetworkType(int)
*/
+ @Deprecated
public @NetworkType int getNetworkType() {
- return networkType;
+ if (networkRequest == null) {
+ return NETWORK_TYPE_NONE;
+ } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+ return NETWORK_TYPE_UNMETERED;
+ } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+ return NETWORK_TYPE_NOT_ROAMING;
+ } else if (networkRequest.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ return NETWORK_TYPE_CELLULAR;
+ } else {
+ return NETWORK_TYPE_ANY;
+ }
+ }
+
+ /**
+ * Return the detailed description of the kind of network this job requires,
+ * or {@code null} if no specific kind of network is required.
+ *
+ * @see Builder#setRequiredNetwork(NetworkRequest)
+ */
+ public @Nullable NetworkRequest getRequiredNetwork() {
+ return networkRequest;
}
/**
@@ -438,8 +487,7 @@ public class JobInfo implements Parcelable {
* job does not recur periodically.
*/
public long getIntervalMillis() {
- final long minInterval = getMinPeriodMillis();
- return intervalMillis >= minInterval ? intervalMillis : minInterval;
+ return intervalMillis;
}
/**
@@ -447,10 +495,7 @@ public class JobInfo implements Parcelable {
* execute at any time in a window of flex length at the end of the period.
*/
public long getFlexMillis() {
- long interval = getIntervalMillis();
- long percentClamp = 5 * interval / 100;
- long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
- return clampedFlex <= interval ? clampedFlex : interval;
+ return flexMillis;
}
/**
@@ -459,8 +504,7 @@ public class JobInfo implements Parcelable {
* to 30 seconds, minimum is currently 10 seconds.
*/
public long getInitialBackoffMillis() {
- final long minBackoff = getMinBackoffMillis();
- return initialBackoffMillis >= minBackoff ? initialBackoffMillis : minBackoff;
+ return initialBackoffMillis;
}
/**
@@ -538,7 +582,7 @@ public class JobInfo implements Parcelable {
if (hasLateConstraint != j.hasLateConstraint) {
return false;
}
- if (networkType != j.networkType) {
+ if (!Objects.equals(networkRequest, j.networkRequest)) {
return false;
}
if (networkBytes != j.networkBytes) {
@@ -601,7 +645,9 @@ public class JobInfo implements Parcelable {
hashCode = 31 * hashCode + Long.hashCode(triggerContentMaxDelay);
hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
- hashCode = 31 * hashCode + networkType;
+ if (networkRequest != null) {
+ hashCode = 31 * hashCode + networkRequest.hashCode();
+ }
hashCode = 31 * hashCode + Long.hashCode(networkBytes);
hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
@@ -632,7 +678,11 @@ public class JobInfo implements Parcelable {
triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
triggerContentUpdateDelay = in.readLong();
triggerContentMaxDelay = in.readLong();
- networkType = in.readInt();
+ if (in.readInt() != 0) {
+ networkRequest = NetworkRequest.CREATOR.createFromParcel(in);
+ } else {
+ networkRequest = null;
+ }
networkBytes = in.readLong();
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
@@ -661,7 +711,7 @@ public class JobInfo implements Parcelable {
: null;
triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
triggerContentMaxDelay = b.mTriggerContentMaxDelay;
- networkType = b.mNetworkType;
+ networkRequest = b.mNetworkRequest;
networkBytes = b.mNetworkBytes;
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
@@ -699,7 +749,12 @@ public class JobInfo implements Parcelable {
out.writeTypedArray(triggerContentUris, flags);
out.writeLong(triggerContentUpdateDelay);
out.writeLong(triggerContentMaxDelay);
- out.writeInt(networkType);
+ if (networkRequest != null) {
+ out.writeInt(1);
+ networkRequest.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
out.writeLong(networkBytes);
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
@@ -833,7 +888,7 @@ public class JobInfo implements Parcelable {
private int mFlags;
// Requirements.
private int mConstraintFlags;
- private int mNetworkType;
+ private NetworkRequest mNetworkRequest;
private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
private ArrayList<TriggerContentUri> mTriggerContentUris;
private long mTriggerContentUpdateDelay = -1;
@@ -934,24 +989,84 @@ public class JobInfo implements Parcelable {
}
/**
- * Set some description of the kind of network type your job needs to
- * have. Not calling this function means the network is not necessary,
- * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
- * calling this function defines network as a strict requirement for
- * your job. If the network requested is not available your job will
- * never run. See {@link #setOverrideDeadline(long)} to change this
- * behaviour.
+ * Set basic description of the kind of network your job requires. If
+ * you need more precise control over network capabilities, see
+ * {@link #setRequiredNetwork(NetworkRequest)}.
+ * <p>
+ * If your job doesn't need a network connection, you don't need to call
+ * this method, as the default value is {@link #NETWORK_TYPE_NONE}.
+ * <p>
+ * Calling this method defines network as a strict requirement for your
+ * job. If the network requested is not available your job will never
+ * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+ * Calling this method will override any requirements previously defined
+ * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
+ * want to call one of these methods.
* <p class="note">
- * Note: When your job executes in
+ * When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
* specific network returned by {@link JobParameters#getNetwork()},
* otherwise you'll use the default network which may not meet this
* constraint.
*
+ * @see #setRequiredNetwork(NetworkRequest)
+ * @see JobInfo#getNetworkType()
* @see JobParameters#getNetwork()
*/
public Builder setRequiredNetworkType(@NetworkType int networkType) {
- mNetworkType = networkType;
+ if (networkType == NETWORK_TYPE_NONE) {
+ return setRequiredNetwork(null);
+ } else {
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+
+ // All types require validated Internet
+ builder.addCapability(NET_CAPABILITY_INTERNET);
+ builder.addCapability(NET_CAPABILITY_VALIDATED);
+ builder.removeCapability(NET_CAPABILITY_NOT_VPN);
+
+ if (networkType == NETWORK_TYPE_ANY) {
+ // No other capabilities
+ } else if (networkType == NETWORK_TYPE_UNMETERED) {
+ builder.addCapability(NET_CAPABILITY_NOT_METERED);
+ } else if (networkType == NETWORK_TYPE_NOT_ROAMING) {
+ builder.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ } else if (networkType == NETWORK_TYPE_CELLULAR) {
+ builder.addTransportType(TRANSPORT_CELLULAR);
+ }
+
+ return setRequiredNetwork(builder.build());
+ }
+ }
+
+ /**
+ * Set detailed description of the kind of network your job requires.
+ * <p>
+ * If your job doesn't need a network connection, you don't need to call
+ * this method, as the default is {@code null}.
+ * <p>
+ * Calling this method defines network as a strict requirement for your
+ * job. If the network requested is not available your job will never
+ * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+ * Calling this method will override any requirements previously defined
+ * by {@link #setRequiredNetworkType(int)}; you typically only want to
+ * call one of these methods.
+ * <p class="note">
+ * When your job executes in
+ * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+ * specific network returned by {@link JobParameters#getNetwork()},
+ * otherwise you'll use the default network which may not meet this
+ * constraint.
+ *
+ * @param networkRequest The detailed description of the kind of network
+ * this job requires, or {@code null} if no specific kind of
+ * network is required. Defining a {@link NetworkSpecifier}
+ * is only supported for jobs that aren't persisted.
+ * @see #setRequiredNetworkType(int)
+ * @see JobInfo#getRequiredNetwork()
+ * @see JobParameters#getNetwork()
+ */
+ public Builder setRequiredNetwork(@Nullable NetworkRequest networkRequest) {
+ mNetworkRequest = networkRequest;
return this;
}
@@ -1140,6 +1255,21 @@ public class JobInfo implements Parcelable {
* higher.
*/
public Builder setPeriodic(long intervalMillis, long flexMillis) {
+ final long minPeriod = getMinPeriodMillis();
+ if (intervalMillis < minPeriod) {
+ Log.w(TAG, "Requested interval " + formatDuration(intervalMillis) + " for job "
+ + mJobId + " is too small; raising to " + formatDuration(minPeriod));
+ intervalMillis = minPeriod;
+ }
+
+ final long percentClamp = 5 * intervalMillis / 100;
+ final long minFlex = Math.max(percentClamp, getMinFlexMillis());
+ if (flexMillis < minFlex) {
+ Log.w(TAG, "Requested flex " + formatDuration(flexMillis) + " for job " + mJobId
+ + " is too small; raising to " + formatDuration(minFlex));
+ flexMillis = minFlex;
+ }
+
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
mFlexMillis = flexMillis;
@@ -1189,6 +1319,13 @@ public class JobInfo implements Parcelable {
*/
public Builder setBackoffCriteria(long initialBackoffMillis,
@BackoffPolicy int backoffPolicy) {
+ final long minBackoff = getMinBackoffMillis();
+ if (initialBackoffMillis < minBackoff) {
+ Log.w(TAG, "Requested backoff " + formatDuration(initialBackoffMillis) + " for job "
+ + mJobId + " is too small; raising to " + formatDuration(minBackoff));
+ initialBackoffMillis = minBackoff;
+ }
+
mBackoffPolicySet = true;
mInitialBackoffMillis = initialBackoffMillis;
mBackoffPolicy = backoffPolicy;
@@ -1213,16 +1350,22 @@ public class JobInfo implements Parcelable {
public JobInfo build() {
// Allow jobs with no constraints - What am I, a database?
if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
- mNetworkType == NETWORK_TYPE_NONE &&
+ mNetworkRequest == null &&
mTriggerContentUris == null) {
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
// Check that network estimates require network type
- if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+ if (mNetworkBytes > 0 && mNetworkRequest == null) {
throw new IllegalArgumentException(
"Can't provide estimated network usage without requiring a network");
}
+ // We can't serialize network specifiers
+ if (mIsPersisted && mNetworkRequest != null
+ && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) {
+ throw new IllegalArgumentException(
+ "Network specifiers aren't supported for persistent jobs");
+ }
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic) {
if (mMaxExecutionDelayMillis != 0L) {
@@ -1257,31 +1400,7 @@ public class JobInfo implements Parcelable {
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
- JobInfo job = new JobInfo(this);
- if (job.isPeriodic()) {
- if (job.intervalMillis != job.getIntervalMillis()) {
- StringBuilder builder = new StringBuilder();
- builder.append("Specified interval for ")
- .append(String.valueOf(mJobId))
- .append(" is ");
- formatDuration(mIntervalMillis, builder);
- builder.append(". Clamped to ");
- formatDuration(job.getIntervalMillis(), builder);
- Log.w(TAG, builder.toString());
- }
- if (job.flexMillis != job.getFlexMillis()) {
- StringBuilder builder = new StringBuilder();
- builder.append("Specified flex for ")
- .append(String.valueOf(mJobId))
- .append(" is ");
- formatDuration(mFlexMillis, builder);
- builder.append(". Clamped to ");
- formatDuration(job.getFlexMillis(), builder);
- Log.w(TAG, builder.toString());
- }
- }
- return job;
+ return new JobInfo(this);
}
}
-
}
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index f6b6b869..616a5be3 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -21,8 +21,12 @@ import android.annotation.Nullable;
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.ContentProvider;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -54,7 +58,12 @@ public final class Slice implements Parcelable {
public @interface SliceHint{ }
/**
- * Hint that this content is a title of other content in the slice.
+ * Hint that this content is a title of other content in the slice. This can also indicate that
+ * the content should be used in the shortcut representation of the slice (icon, label, action),
+ * normally this should be indicated by adding the hint on the action containing that content.
+ *
+ * @see SliceView#MODE_SHORTCUT
+ * @see SliceItem#TYPE_ACTION
*/
public static final String HINT_TITLE = "title";
/**
@@ -100,6 +109,21 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_NO_TINT = "no_tint";
/**
+ * Hint to indicate that this content should not be shown in the {@link SliceView#MODE_SMALL}
+ * and {@link SliceView#MODE_LARGE} modes of SliceView. This content may be used to populate
+ * the {@link SliceView#MODE_SHORTCUT} format of the slice.
+ * @hide
+ */
+ public static final String HINT_HIDDEN = "hidden";
+ /**
+ * Hint to indicate that this content has a toggle action associated with it. To indicate that
+ * the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the intent
+ * associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} which can be
+ * retrieved to see the new state of the toggle.
+ * @hide
+ */
+ public static final String HINT_TOGGLE = "toggle";
+ /**
* Hint to indicate that this slice is incomplete and an update will be sent once
* loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
* OS and should not be cached by apps.
@@ -112,6 +136,11 @@ public final class Slice implements Parcelable {
* @hide
*/
public static final String HINT_ALT = "alt";
+ /**
+ * Key to retrieve an extra added to an intent when a control is changed.
+ * @hide
+ */
+ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
@@ -398,4 +427,58 @@ public final class Slice implements Parcelable {
resolver.releaseProvider(provider);
}
}
+
+ /**
+ * Turns a slice intent into slice content. Expects an explicit intent. If there is no
+ * {@link ContentProvider} associated with the given intent this will throw
+ * {@link IllegalArgumentException}.
+ *
+ * @param context The context to use.
+ * @param intent The intent associated with a slice.
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ * @see SliceProvider#onMapIntentToUri(Intent)
+ * @see Intent
+ */
+ public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+ Preconditions.checkNotNull(intent, "intent");
+ Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
+ "Slice intent must be explicit " + intent);
+ ContentResolver resolver = context.getContentResolver();
+
+ // Check if the intent has data for the slice uri on it and use that
+ final Uri intentData = intent.getData();
+ if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
+ return bindSlice(resolver, intentData);
+ }
+ // Otherwise ask the app
+ List<ResolveInfo> providers =
+ context.getPackageManager().queryIntentContentProviders(intent, 0);
+ if (providers == null) {
+ throw new IllegalArgumentException("Unable to resolve intent " + intent);
+ }
+ String authority = providers.get(0).providerInfo.authority;
+ Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).build();
+ IContentProvider provider = resolver.acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ final Bundle res = provider.call(resolver.getPackageName(),
+ SliceProvider.METHOD_MAP_INTENT, null, extras);
+ if (res == null) {
+ return null;
+ }
+ return res.getParcelable(SliceProvider.EXTRA_SLICE);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ resolver.releaseProvider(provider);
+ }
+ }
}
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 33825b4b..05f4ce6e 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -16,46 +16,69 @@
package android.app.slice;
import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.app.slice.widget.SliceView;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
+import android.os.UserHandle;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
- * A SliceProvider allows app to provide content to be displayed in system
- * spaces. This content is templated and can contain actions, and the behavior
- * of how it is surfaced is specific to the system surface.
+ * A SliceProvider allows an app to provide content to be displayed in system spaces. This content
+ * is templated and can contain actions, and the behavior of how it is surfaced is specific to the
+ * system surface.
+ * <p>
+ * Slices are not currently live content. They are bound once and shown to the user. If the content
+ * changes due to a callback from user interaction, then
+ * {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system.
+ * </p>
+ * <p>
+ * The provider needs to be declared in the manifest to provide the authority for the app. The
+ * authority for most slices is expected to match the package of the application.
+ * </p>
*
- * <p>Slices are not currently live content. They are bound once and shown to the
- * user. If the content changes due to a callback from user interaction, then
- * {@link ContentResolver#notifyChange(Uri, ContentObserver)}
- * should be used to notify the system.</p>
- *
- * <p>The provider needs to be declared in the manifest to provide the authority
- * for the app. The authority for most slices is expected to match the package
- * of the application.</p>
* <pre class="prettyprint">
* {@literal
* <provider
* android:name="com.android.mypkg.MySliceProvider"
* android:authorities="com.android.mypkg" />}
* </pre>
+ * <p>
+ * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider
+ * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via
+ * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an
+ * appropriate Uri representing the slice.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ * android:name="com.android.mypkg.MySliceProvider"
+ * android:authorities="com.android.mypkg">
+ * <intent-filter>
+ * <action android:name="android.intent.action.MY_SLICE_INTENT" />
+ * </intent-filter>
+ * </provider>}
+ * </pre>
*
* @see Slice
*/
public abstract class SliceProvider extends ContentProvider {
-
/**
* This is the Android platform's MIME type for a slice: URI
* containing a slice implemented through {@link SliceProvider}.
@@ -74,6 +97,14 @@ public abstract class SliceProvider extends ContentProvider {
/**
* @hide
*/
+ public static final String METHOD_MAP_INTENT = "map_slice";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_INTENT = "slice_intent";
+ /**
+ * @hide
+ */
public static final String EXTRA_SLICE = "slice";
private static final boolean DEBUG = false;
@@ -94,6 +125,20 @@ public abstract class SliceProvider extends ContentProvider {
// TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
public abstract Slice onBindSlice(Uri sliceUri);
+ /**
+ * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
+ * In that case, this method can be called and is expected to return a non-null Uri representing
+ * a slice. Otherwise this will throw {@link UnsupportedOperationException}.
+ *
+ * @return Uri representing the slice associated with the provided intent.
+ * @see {@link Slice}
+ * @see {@link SliceView#setSlice(Intent)}
+ */
+ public @NonNull Uri onMapIntentToUri(Intent intent) {
+ throw new UnsupportedOperationException(
+ "This provider has not implemented intent to uri mapping");
+ }
+
@Override
public final int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
@@ -143,14 +188,31 @@ public abstract class SliceProvider extends ContentProvider {
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
- getContext().enforceCallingPermission(permission.BIND_SLICE,
- "Slice binding requires the permission BIND_SLICE");
Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+ if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
+ getContext().enforceUriPermission(uri, permission.BIND_SLICE,
+ permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ "Slice binding requires the permission BIND_SLICE");
+ }
Slice s = handleBindSlice(uri);
Bundle b = new Bundle();
b.putParcelable(EXTRA_SLICE, s);
return b;
+ } else if (method.equals(METHOD_MAP_INTENT)) {
+ getContext().enforceCallingPermission(permission.BIND_SLICE,
+ "Slice binding requires the permission BIND_SLICE");
+ Intent intent = extras.getParcelable(EXTRA_INTENT);
+ Uri uri = onMapIntentToUri(intent);
+ Bundle b = new Bundle();
+ if (uri != null) {
+ Slice s = handleBindSlice(uri);
+ b.putParcelable(EXTRA_SLICE, s);
+ } else {
+ b.putParcelable(EXTRA_SLICE, null);
+ }
+ return b;
}
return super.call(method, arg, extras);
}
diff --git a/android/app/slice/widget/GridView.java b/android/app/slice/widget/GridView.java
index 67a3c671..793abc05 100644
--- a/android/app/slice/widget/GridView.java
+++ b/android/app/slice/widget/GridView.java
@@ -126,6 +126,9 @@ public class GridView extends LinearLayout implements SliceListView {
* Returns true if this item is just an image.
*/
private boolean addItem(SliceItem item) {
+ if (item.hasHint(Slice.HINT_HIDDEN)) {
+ return false;
+ }
if (item.getType() == SliceItem.TYPE_IMAGE) {
ImageView v = new ImageView(getContext());
v.setImageIcon(item.getIcon());
@@ -145,6 +148,9 @@ public class GridView extends LinearLayout implements SliceListView {
items.addAll(item.getSlice().getItems());
}
items.forEach(i -> {
+ if (i.hasHint(Slice.HINT_HIDDEN)) {
+ return;
+ }
Context context = getContext();
switch (i.getType()) {
case SliceItem.TYPE_TEXT:
diff --git a/android/app/slice/widget/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java
index f45b2a8f..788f6fb6 100644
--- a/android/app/slice/widget/LargeTemplateView.java
+++ b/android/app/slice/widget/LargeTemplateView.java
@@ -85,9 +85,14 @@ public class LargeTemplateView extends SliceModeView {
addList(slice, items);
} else {
slice.getItems().forEach(item -> {
- if (item.hasHint(Slice.HINT_ACTIONS)) {
+ if (item.hasHint(Slice.HINT_HIDDEN)) {
+ // If it's hidden we don't show it
+ return;
+ } else if (item.hasHint(Slice.HINT_ACTIONS)) {
+ // Action groups don't show in lists
return;
} else if (item.getType() == SliceItem.TYPE_COLOR) {
+ // A color is not a list item
return;
} else if (item.getType() == SliceItem.TYPE_SLICE
&& item.hasHint(Slice.HINT_LIST)) {
@@ -108,8 +113,12 @@ public class LargeTemplateView extends SliceModeView {
private void addList(Slice slice, List<SliceItem> items) {
List<SliceItem> sliceItems = slice.getItems();
- sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
- items.addAll(sliceItems);
+ sliceItems.forEach(i -> {
+ if (!i.hasHint(Slice.HINT_HIDDEN) && i.getType() != SliceItem.TYPE_COLOR) {
+ i.addHint(Slice.HINT_LIST_ITEM);
+ items.add(i);
+ }
+ });
}
/**
diff --git a/android/app/slice/widget/ShortcutView.java b/android/app/slice/widget/ShortcutView.java
index 0bca8ce2..0b7ad0d6 100644
--- a/android/app/slice/widget/ShortcutView.java
+++ b/android/app/slice/widget/ShortcutView.java
@@ -24,13 +24,20 @@ import android.app.slice.SliceQuery;
import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.net.Uri;
import com.android.internal.R;
+import java.util.List;
+
/**
* @hide
*/
@@ -38,27 +45,26 @@ public class ShortcutView extends SliceModeView {
private static final String TAG = "ShortcutView";
- private PendingIntent mAction;
private Uri mUri;
+ private PendingIntent mAction;
+ private SliceItem mLabel;
+ private SliceItem mIcon;
+
private int mLargeIconSize;
private int mSmallIconSize;
public ShortcutView(Context context) {
super(context);
- mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+ final Resources res = getResources();
+ mSmallIconSize = res.getDimensionPixelSize(R.dimen.slice_icon_size);
+ mLargeIconSize = res.getDimensionPixelSize(R.dimen.slice_shortcut_size);
}
@Override
public void setSlice(Slice slice) {
removeAllViews();
- SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
- SliceItem iconItem = SliceQuery.getPrimaryIcon(slice);
- SliceItem textItem = sliceItem != null
- ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
- : SliceQuery.find(slice, SliceItem.TYPE_TEXT);
- SliceItem colorItem = sliceItem != null
- ? SliceQuery.find(sliceItem, SliceItem.TYPE_COLOR)
- : SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ determineShortcutItems(getContext(), slice);
+ SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
if (colorItem == null) {
colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
}
@@ -67,13 +73,11 @@ public class ShortcutView extends SliceModeView {
ShapeDrawable circle = new ShapeDrawable(new OvalShape());
circle.setTint(color);
setBackground(circle);
- if (iconItem != null) {
- final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
+ if (mIcon != null) {
+ final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE);
final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
- SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
+ SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
isLarge, this /* parent */);
- mAction = sliceItem != null ? sliceItem.getAction()
- : null;
mUri = slice.getUri();
setClickable(true);
} else {
@@ -103,4 +107,69 @@ public class ShortcutView extends SliceModeView {
}
return true;
}
+
+ /**
+ * Looks at the slice and determines which items are best to use to compose the shortcut.
+ */
+ private void determineShortcutItems(Context context, Slice slice) {
+ List<String> h = slice.getHints();
+ SliceItem sliceItem = new SliceItem(slice, SliceItem.TYPE_SLICE,
+ h.toArray(new String[h.size()]));
+ SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION,
+ Slice.HINT_TITLE, null);
+
+ if (titleItem != null) {
+ // Preferred case: hinted action containing hinted image and text
+ mAction = titleItem.getAction();
+ mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+ null);
+ mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ null);
+ } else {
+ // No hinted action; just use the first one
+ SliceItem actionItem = SliceQuery.find(sliceItem, SliceItem.TYPE_ACTION, (String) null,
+ null);
+ mAction = (actionItem != null) ? actionItem.getAction() : null;
+ }
+ // First fallback: any hinted image and text
+ if (mIcon == null) {
+ mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+ null);
+ }
+ if (mLabel == null) {
+ mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ null);
+ }
+ // Second fallback: first image and text
+ if (mIcon == null) {
+ mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, (String) null,
+ null);
+ }
+ if (mLabel == null) {
+ mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, (String) null,
+ null);
+ }
+ // Final fallback: use app info
+ if (mIcon == null || mLabel == null || mAction == null) {
+ PackageManager pm = context.getPackageManager();
+ ProviderInfo providerInfo = pm.resolveContentProvider(
+ slice.getUri().getAuthority(), 0);
+ ApplicationInfo appInfo = providerInfo.applicationInfo;
+ if (appInfo != null) {
+ if (mIcon == null) {
+ Drawable icon = appInfo.loadDefaultIcon(pm);
+ mIcon = new SliceItem(SliceViewUtil.createIconFromDrawable(icon),
+ SliceItem.TYPE_IMAGE, new String[] {Slice.HINT_LARGE});
+ }
+ if (mLabel == null) {
+ mLabel = new SliceItem(pm.getApplicationLabel(appInfo),
+ SliceItem.TYPE_TEXT, null);
+ }
+ if (mAction == null) {
+ mAction = PendingIntent.getActivity(context, 0,
+ pm.getLaunchIntentForPackage(appInfo.packageName), 0);
+ }
+ }
+ }
+ }
}
diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java
index 5bafbc03..fa1b64ce 100644
--- a/android/app/slice/widget/SliceView.java
+++ b/android/app/slice/widget/SliceView.java
@@ -115,7 +115,9 @@ public class SliceView extends ViewGroup {
*/
public static final String MODE_LARGE = "SLICE_LARGE";
/**
- * Mode indicating this slice should be presented as an icon.
+ * Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
+ * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a
+ * slice.
*/
public static final String MODE_SHORTCUT = "SLICE_ICON";
@@ -181,10 +183,25 @@ public class SliceView extends ViewGroup {
}
/**
+ * Populates this view with the {@link Slice} associated with the provided {@link Intent}. To
+ * use this method your app must have the permission
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
+ * updated with the slice identified by the provided intent changes. The lifecycle of this
+ * observer is handled by SliceView in {@link #onAttachedToWindow()} and
+ * {@link #onDetachedFromWindow()}. To unregister this observer outside of that you can call
+ * {@link #clearSlice}.
+ *
+ * @return true if a slice was found for the provided intent.
* @hide
*/
- public void showSlice(Intent intent) {
- // TODO
+ public boolean setSlice(@Nullable Intent intent) {
+ Slice s = Slice.bindSlice(mContext, intent);
+ if (s != null) {
+ return setSlice(s.getUri());
+ }
+ return s != null;
}
/**
@@ -197,8 +214,7 @@ public class SliceView extends ViewGroup {
* is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
* To unregister this observer outside of that you can call {@link #clearSlice}.
*
- * @return true if the a slice was found for the provided uri.
- * @see #clearSlice
+ * @return true if a slice was found for the provided uri.
*/
public boolean setSlice(@NonNull Uri sliceUri) {
Preconditions.checkNotNull(sliceUri,
@@ -210,11 +226,15 @@ public class SliceView extends ViewGroup {
validate(sliceUri);
Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
if (s != null) {
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ }
mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
if (isAttachedToWindow()) {
registerSlice(sliceUri);
}
- showSlice(s);
+ mCurrentSlice = s;
+ reinflate();
}
return s != null;
}
diff --git a/android/app/slice/widget/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java
index 03669983..1cf0055b 100644
--- a/android/app/slice/widget/SliceViewUtil.java
+++ b/android/app/slice/widget/SliceViewUtil.java
@@ -28,6 +28,7 @@ import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.view.Gravity;
@@ -141,6 +142,21 @@ public class SliceViewUtil {
/**
* @hide
*/
+ public static Icon createIconFromDrawable(Drawable d) {
+ if (d instanceof BitmapDrawable) {
+ return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+ }
+ Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(b);
+ d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ d.draw(canvas);
+ return Icon.createWithBitmap(b);
+ }
+
+ /**
+ * @hide
+ */
public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
boolean isLarge, ViewGroup parent) {
ImageView v = new ImageView(context);
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index c827432a..3a3e16e0 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -261,7 +261,10 @@ public final class UsageStatsManager {
/**
* @hide
+ * Changes the app standby state to the provided bucket.
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE)
public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
try {
mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java
index dbaace2f..29e7439f 100644
--- a/android/app/usage/UsageStatsManagerInternal.java
+++ b/android/app/usage/UsageStatsManagerInternal.java
@@ -118,7 +118,15 @@ public abstract class UsageStatsManagerInternal {
AppIdleStateChangeListener listener);
public static abstract class AppIdleStateChangeListener {
- public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle);
+
+ /** Callback to inform listeners that the idle state has changed to a new bucket. */
+ public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
+ int bucket);
+
+ /**
+ * Callback to inform listeners that the parole state has changed. This means apps are
+ * allowed to do work even if they're idle or in a low bucket.
+ */
public abstract void onParoleStateChanged(boolean isParoleOn);
}
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 106b2ef0..e8895bd0 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -37,6 +37,7 @@ public class AndroidViewModel extends ViewModel {
/**
* Return the application.
*/
+ @SuppressWarnings("TypeParameterUnusedInFormals")
@NonNull
public <T extends Application> T getApplication() {
//noinspection unchecked
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index 1ddcb1a9..f1352446 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,136 +1,9 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+//ComputableLiveData interface for tests
package android.arch.lifecycle;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A LiveData class that can be invalidated & computed on demand.
- * <p>
- * This is an internal class for now, might be public if we see the necessity.
- *
- * @param <T> The type of the live data
- * @hide internal
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+import android.arch.lifecycle.LiveData;
public abstract class ComputableLiveData<T> {
-
- private final LiveData<T> mLiveData;
-
- private AtomicBoolean mInvalid = new AtomicBoolean(true);
- private AtomicBoolean mComputing = new AtomicBoolean(false);
-
- /**
- * Creates a computable live data which is computed when there are active observers.
- * <p>
- * It can also be invalidated via {@link #invalidate()} which will result in a call to
- * {@link #compute()} if there are active observers (or when they start observing)
- */
- @SuppressWarnings("WeakerAccess")
- public ComputableLiveData() {
- mLiveData = new LiveData<T>() {
- @Override
- protected void onActive() {
- // TODO if we make this class public, we should accept an executor
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- };
- }
-
- /**
- * Returns the LiveData managed by this class.
- *
- * @return A LiveData that is controlled by ComputableLiveData.
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LiveData<T> getLiveData() {
- return mLiveData;
- }
-
- @VisibleForTesting
- final Runnable mRefreshRunnable = new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- boolean computed;
- do {
- computed = false;
- // compute can happen only in 1 thread but no reason to lock others.
- if (mComputing.compareAndSet(false, true)) {
- // as long as it is invalid, keep computing.
- try {
- T value = null;
- while (mInvalid.compareAndSet(true, false)) {
- computed = true;
- value = compute();
- }
- if (computed) {
- mLiveData.postValue(value);
- }
- } finally {
- // release compute lock
- mComputing.set(false);
- }
- }
- // check invalid after releasing compute lock to avoid the following scenario.
- // Thread A runs compute()
- // Thread A checks invalid, it is false
- // Main thread sets invalid to true
- // Thread B runs, fails to acquire compute lock and skips
- // Thread A releases compute lock
- // We've left invalid in set state. The check below recovers.
- } while (computed && mInvalid.get());
- }
- };
-
- // invalidation check always happens on the main thread
- @VisibleForTesting
- final Runnable mInvalidationRunnable = new Runnable() {
- @MainThread
- @Override
- public void run() {
- boolean isActive = mLiveData.hasActiveObservers();
- if (mInvalid.compareAndSet(false, true)) {
- if (isActive) {
- // TODO if we make this class public, we should accept an executor.
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- }
- }
- };
-
- /**
- * Invalidates the LiveData.
- * <p>
- * When there are active observers, this will trigger a call to {@link #compute()}.
- */
- public void invalidate() {
- ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
- }
-
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- protected abstract T compute();
+ public ComputableLiveData(){}
+ abstract protected T compute();
+ public LiveData<T> getLiveData() {return null;}
+ public void invalidate() {}
}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 5b09c32f..3aea6acb 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,410 +1,4 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+//LiveData interface for tests
package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * LiveData is a data holder class that can be observed within a given lifecycle.
- * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
- * this observer will be notified about modifications of the wrapped data only if the paired
- * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
- * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
- * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
- * about modifications. For those observers, you should manually call
- * {@link #removeObserver(Observer)}.
- *
- * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
- * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
- * activities and fragments where they can safely observe LiveData and not worry about leaks:
- * they will be instantly unsubscribed when they are destroyed.
- *
- * <p>
- * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
- * to get notified when number of active {@link Observer}s change between 0 and 1.
- * This allows LiveData to release any heavy resources when it does not have any Observers that
- * are actively observing.
- * <p>
- * This class is designed to hold individual data fields of {@link ViewModel},
- * but can also be used for sharing data between different modules in your application
- * in a decoupled fashion.
- *
- * @param <T> The type of data held by this instance
- * @see ViewModel
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
-// thread.
-public abstract class LiveData<T> {
- private final Object mDataLock = new Object();
- static final int START_VERSION = -1;
- private static final Object NOT_SET = new Object();
-
- private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
-
- private LifecycleRegistry mRegistry = init();
-
- private LifecycleRegistry init() {
- LifecycleRegistry registry = new LifecycleRegistry(this);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
- return registry;
- }
-
- @Override
- public Lifecycle getLifecycle() {
- return mRegistry;
- }
- };
-
- private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
- new SafeIterableMap<>();
-
- // how many observers are in active state
- private int mActiveCount = 0;
- private volatile Object mData = NOT_SET;
- // when setData is called, we set the pending data and actual data swap happens on the main
- // thread
- private volatile Object mPendingData = NOT_SET;
- private int mVersion = START_VERSION;
-
- private boolean mDispatchingValue;
- @SuppressWarnings("FieldCanBeLocal")
- private boolean mDispatchInvalidated;
- private final Runnable mPostValueRunnable = new Runnable() {
- @Override
- public void run() {
- Object newValue;
- synchronized (mDataLock) {
- newValue = mPendingData;
- mPendingData = NOT_SET;
- }
- //noinspection unchecked
- setValue((T) newValue);
- }
- };
-
- private void considerNotify(LifecycleBoundObserver observer) {
- if (!observer.active) {
- return;
- }
- // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
- //
- // we still first check observer.active to keep it as the entrance for events. So even if
- // the observer moved to an active state, if we've not received that event, we better not
- // notify for a more predictable notification order.
- if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
- observer.activeStateChanged(false);
- return;
- }
- if (observer.lastVersion >= mVersion) {
- return;
- }
- observer.lastVersion = mVersion;
- //noinspection unchecked
- observer.observer.onChanged((T) mData);
- }
-
- private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
- if (mDispatchingValue) {
- mDispatchInvalidated = true;
- return;
- }
- mDispatchingValue = true;
- do {
- mDispatchInvalidated = false;
- if (initiator != null) {
- considerNotify(initiator);
- initiator = null;
- } else {
- for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
- mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
- considerNotify(iterator.next().getValue());
- if (mDispatchInvalidated) {
- break;
- }
- }
- }
- } while (mDispatchInvalidated);
- mDispatchingValue = false;
- }
-
- /**
- * Adds the given observer to the observers list within the lifespan of the given
- * owner. The events are dispatched on the main thread. If LiveData already has data
- * set, it will be delivered to the observer.
- * <p>
- * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
- * or {@link Lifecycle.State#RESUMED} state (active).
- * <p>
- * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
- * automatically be removed.
- * <p>
- * When data changes while the {@code owner} is not active, it will not receive any updates.
- * If it becomes active again, it will receive the last available data automatically.
- * <p>
- * LiveData keeps a strong reference to the observer and the owner as long as the
- * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
- * the observer &amp; the owner.
- * <p>
- * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
- * ignores the call.
- * <p>
- * If the given owner, observer tuple is already in the list, the call is ignored.
- * If the observer is already in the list with another owner, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param owner The LifecycleOwner which controls the observer
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- // ignore
- return;
- }
- LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
- LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
- if (existing != null && existing.owner != wrapper.owner) {
- throw new IllegalArgumentException("Cannot add the same observer"
- + " with different lifecycles");
- }
- if (existing != null) {
- return;
- }
- owner.getLifecycle().addObserver(wrapper);
- }
-
- /**
- * Adds the given observer to the observers list. This call is similar to
- * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
- * is always active. This means that the given observer will receive all events and will never
- * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
- * observing this LiveData.
- * While LiveData has one of such observers, it will be considered
- * as active.
- * <p>
- * If the observer was already added with an owner to this LiveData, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observeForever(@NonNull Observer<T> observer) {
- observe(ALWAYS_ON, observer);
- }
-
- /**
- * Removes the given observer from the observers list.
- *
- * @param observer The Observer to receive events.
- */
- @MainThread
- public void removeObserver(@NonNull final Observer<T> observer) {
- assertMainThread("removeObserver");
- LifecycleBoundObserver removed = mObservers.remove(observer);
- if (removed == null) {
- return;
- }
- removed.owner.getLifecycle().removeObserver(removed);
- removed.activeStateChanged(false);
- }
-
- /**
- * Removes all observers that are tied to the given {@link LifecycleOwner}.
- *
- * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
- */
- @MainThread
- public void removeObservers(@NonNull final LifecycleOwner owner) {
- assertMainThread("removeObservers");
- for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
- if (entry.getValue().owner == owner) {
- removeObserver(entry.getKey());
- }
- }
- }
-
- /**
- * Posts a task to a main thread to set the given value. So if you have a following code
- * executed in the main thread:
- * <pre class="prettyprint">
- * liveData.postValue("a");
- * liveData.setValue("b");
- * </pre>
- * The value "b" would be set at first and later the main thread would override it with
- * the value "a".
- * <p>
- * If you called this method multiple times before a main thread executed a posted task, only
- * the last value would be dispatched.
- *
- * @param value The new value
- */
- protected void postValue(T value) {
- boolean postTask;
- synchronized (mDataLock) {
- postTask = mPendingData == NOT_SET;
- mPendingData = value;
- }
- if (!postTask) {
- return;
- }
- ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
- }
-
- /**
- * Sets the value. If there are active observers, the value will be dispatched to them.
- * <p>
- * This method must be called from the main thread. If you need set a value from a background
- * thread, you can use {@link #postValue(Object)}
- *
- * @param value The new value
- */
- @MainThread
- protected void setValue(T value) {
- assertMainThread("setValue");
- mVersion++;
- mData = value;
- dispatchingValue(null);
- }
-
- /**
- * Returns the current value.
- * Note that calling this method on a background thread does not guarantee that the latest
- * value set will be received.
- *
- * @return the current value
- */
- @Nullable
- public T getValue() {
- Object data = mData;
- if (data != NOT_SET) {
- //noinspection unchecked
- return (T) data;
- }
- return null;
- }
-
- int getVersion() {
- return mVersion;
- }
-
- /**
- * Called when the number of active observers change to 1 from 0.
- * <p>
- * This callback can be used to know that this LiveData is being used thus should be kept
- * up to date.
- */
- protected void onActive() {
-
- }
-
- /**
- * Called when the number of active observers change from 1 to 0.
- * <p>
- * This does not mean that there are no observers left, there may still be observers but their
- * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
- * (like an Activity in the back stack).
- * <p>
- * You can check if there are observers via {@link #hasObservers()}.
- */
- protected void onInactive() {
-
- }
-
- /**
- * Returns true if this LiveData has observers.
- *
- * @return true if this LiveData has observers
- */
- public boolean hasObservers() {
- return mObservers.size() > 0;
- }
-
- /**
- * Returns true if this LiveData has active observers.
- *
- * @return true if this LiveData has active observers
- */
- public boolean hasActiveObservers() {
- return mActiveCount > 0;
- }
-
- class LifecycleBoundObserver implements GenericLifecycleObserver {
- public final LifecycleOwner owner;
- public final Observer<T> observer;
- public boolean active;
- public int lastVersion = START_VERSION;
-
- LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
- this.owner = owner;
- this.observer = observer;
- }
-
- @Override
- public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- removeObserver(observer);
- return;
- }
- // immediately set active state, so we'd never dispatch anything to inactive
- // owner
- activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
- }
-
- void activeStateChanged(boolean newActive) {
- if (newActive == active) {
- return;
- }
- active = newActive;
- boolean wasInactive = LiveData.this.mActiveCount == 0;
- LiveData.this.mActiveCount += active ? 1 : -1;
- if (wasInactive && active) {
- onActive();
- }
- if (LiveData.this.mActiveCount == 0 && !active) {
- onInactive();
- }
- if (active) {
- dispatchingValue(this);
- }
- }
- }
-
- static boolean isActiveState(State state) {
- return state.isAtLeast(STARTED);
- }
-
- private void assertMainThread(String methodName) {
- if (!ArchTaskExecutor.getInstance().isMainThread()) {
- throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
- + " thread");
- }
- }
+public class LiveData<T> {
}
diff --git a/android/arch/lifecycle/LiveDataReactiveStreams.java b/android/arch/lifecycle/LiveDataReactiveStreams.java
index 2b25bc9b..ba76f8e8 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -24,7 +24,7 @@ import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
-import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Adapts {@link LiveData} input and output to the ReactiveStreams spec.
@@ -53,83 +53,114 @@ public final class LiveDataReactiveStreams {
public static <T> Publisher<T> toPublisher(
final LifecycleOwner lifecycle, final LiveData<T> liveData) {
- return new Publisher<T>() {
+ return new LiveDataPublisher<>(lifecycle, liveData);
+ }
+
+ private static final class LiveDataPublisher<T> implements Publisher<T> {
+ final LifecycleOwner mLifecycle;
+ final LiveData<T> mLiveData;
+
+ LiveDataPublisher(final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+ this.mLifecycle = lifecycle;
+ this.mLiveData = liveData;
+ }
+
+ @Override
+ public void subscribe(Subscriber<? super T> subscriber) {
+ subscriber.onSubscribe(new LiveDataSubscription<T>(subscriber, mLifecycle, mLiveData));
+ }
+
+ static final class LiveDataSubscription<T> implements Subscription, Observer<T> {
+ final Subscriber<? super T> mSubscriber;
+ final LifecycleOwner mLifecycle;
+ final LiveData<T> mLiveData;
+
+ volatile boolean mCanceled;
+ // used on main thread only
boolean mObserving;
- boolean mCanceled;
long mRequested;
+ // used on main thread only
@Nullable
T mLatest;
+ LiveDataSubscription(final Subscriber<? super T> subscriber,
+ final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+ this.mSubscriber = subscriber;
+ this.mLifecycle = lifecycle;
+ this.mLiveData = liveData;
+ }
+
+ @Override
+ public void onChanged(T t) {
+ if (mCanceled) {
+ return;
+ }
+ if (mRequested > 0) {
+ mLatest = null;
+ mSubscriber.onNext(t);
+ if (mRequested != Long.MAX_VALUE) {
+ mRequested--;
+ }
+ } else {
+ mLatest = t;
+ }
+ }
+
@Override
- public void subscribe(final Subscriber<? super T> subscriber) {
- final Observer<T> observer = new Observer<T>() {
+ public void request(final long n) {
+ if (mCanceled) {
+ return;
+ }
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
- public void onChanged(@Nullable T t) {
+ public void run() {
if (mCanceled) {
return;
}
- if (mRequested > 0) {
- mLatest = null;
- subscriber.onNext(t);
- if (mRequested != Long.MAX_VALUE) {
- mRequested--;
+ if (n <= 0L) {
+ mCanceled = true;
+ if (mObserving) {
+ mLiveData.removeObserver(LiveDataSubscription.this);
+ mObserving = false;
}
- } else {
- mLatest = t;
+ mLatest = null;
+ mSubscriber.onError(
+ new IllegalArgumentException("Non-positive request"));
+ return;
}
- }
- };
- subscriber.onSubscribe(new Subscription() {
- @Override
- public void request(final long n) {
- if (n < 0 || mCanceled) {
- return;
+ // Prevent overflowage.
+ mRequested = mRequested + n >= mRequested
+ ? mRequested + n : Long.MAX_VALUE;
+ if (!mObserving) {
+ mObserving = true;
+ mLiveData.observe(mLifecycle, LiveDataSubscription.this);
+ } else if (mLatest != null) {
+ onChanged(mLatest);
+ mLatest = null;
}
- ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
- @Override
- public void run() {
- if (mCanceled) {
- return;
- }
- // Prevent overflowage.
- mRequested = mRequested + n >= mRequested
- ? mRequested + n : Long.MAX_VALUE;
- if (!mObserving) {
- mObserving = true;
- liveData.observe(lifecycle, observer);
- } else if (mLatest != null) {
- observer.onChanged(mLatest);
- mLatest = null;
- }
- }
- });
}
+ });
+ }
+ @Override
+ public void cancel() {
+ if (mCanceled) {
+ return;
+ }
+ mCanceled = true;
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
- public void cancel() {
- if (mCanceled) {
- return;
+ public void run() {
+ if (mObserving) {
+ mLiveData.removeObserver(LiveDataSubscription.this);
+ mObserving = false;
}
- ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
- @Override
- public void run() {
- if (mCanceled) {
- return;
- }
- if (mObserving) {
- liveData.removeObserver(observer);
- mObserving = false;
- }
- mLatest = null;
- mCanceled = true;
- }
- });
+ mLatest = null;
}
});
}
-
- };
+ }
}
/**
@@ -145,6 +176,10 @@ public final class LiveDataReactiveStreams {
* Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
* added, it will automatically notify with the last value held in LiveData,
* which might not be the last value emitted by the Publisher.
+ * <p>
+ * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+ * in the data that's held. In case of an error being emitted by the publisher, an error will
+ * be propagated to the main thread and the app will crash.
*
* @param <T> The type of data hold by this instance.
*/
@@ -166,67 +201,80 @@ public final class LiveDataReactiveStreams {
* added, it will automatically notify with the last value held in LiveData,
* which might not be the last value emitted by the Publisher.
*
+ * <p>
+ * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+ * in the data that's held. In case of an error being emitted by the publisher, an error will
+ * be propagated to the main thread and the app will crash.
+ *
* @param <T> The type of data hold by this instance.
*/
private static class PublisherLiveData<T> extends LiveData<T> {
- private WeakReference<Subscription> mSubscriptionRef;
private final Publisher mPublisher;
- private final Object mLock = new Object();
+ final AtomicReference<LiveDataSubscriber> mSubscriber;
PublisherLiveData(@NonNull final Publisher publisher) {
mPublisher = publisher;
+ mSubscriber = new AtomicReference<>();
}
@Override
protected void onActive() {
super.onActive();
+ LiveDataSubscriber s = new LiveDataSubscriber();
+ mSubscriber.set(s);
+ mPublisher.subscribe(s);
+ }
- mPublisher.subscribe(new Subscriber<T>() {
- @Override
- public void onSubscribe(Subscription s) {
- // Don't worry about backpressure. If the stream is too noisy then
- // backpressure can be handled upstream.
- synchronized (mLock) {
- s.request(Long.MAX_VALUE);
- mSubscriptionRef = new WeakReference<>(s);
- }
- }
+ @Override
+ protected void onInactive() {
+ super.onInactive();
+ LiveDataSubscriber s = mSubscriber.getAndSet(null);
+ if (s != null) {
+ s.cancelSubscription();
+ }
+ }
- @Override
- public void onNext(final T t) {
- postValue(t);
- }
+ final class LiveDataSubscriber extends AtomicReference<Subscription>
+ implements Subscriber<T> {
- @Override
- public void onError(Throwable t) {
- synchronized (mLock) {
- mSubscriptionRef = null;
- }
- // Errors should be handled upstream, so propagate as a crash.
- throw new RuntimeException(t);
+ @Override
+ public void onSubscribe(Subscription s) {
+ if (compareAndSet(null, s)) {
+ s.request(Long.MAX_VALUE);
+ } else {
+ s.cancel();
}
+ }
- @Override
- public void onComplete() {
- synchronized (mLock) {
- mSubscriptionRef = null;
- }
- }
- });
+ @Override
+ public void onNext(T item) {
+ postValue(item);
+ }
- }
+ @Override
+ public void onError(final Throwable ex) {
+ mSubscriber.compareAndSet(this, null);
- @Override
- protected void onInactive() {
- super.onInactive();
- synchronized (mLock) {
- WeakReference<Subscription> subscriptionRef = mSubscriptionRef;
- if (subscriptionRef != null) {
- Subscription subscription = subscriptionRef.get();
- if (subscription != null) {
- subscription.cancel();
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ // Errors should be handled upstream, so propagate as a crash.
+ throw new RuntimeException("LiveData does not handle errors. Errors from "
+ + "publishers should be handled upstream and propagated as "
+ + "state", ex);
}
- mSubscriptionRef = null;
+ });
+ }
+
+ @Override
+ public void onComplete() {
+ mSubscriber.compareAndSet(this, null);
+ }
+
+ public void cancelSubscription() {
+ Subscription s = get();
+ if (s != null) {
+ s.cancel();
}
}
}
diff --git a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
index 7278847c..83e543c3 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -16,6 +16,9 @@
package android.arch.lifecycle;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -34,6 +37,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import io.reactivex.Flowable;
import io.reactivex.disposables.Disposable;
@@ -115,6 +119,41 @@ public class LiveDataReactiveStreamsTest {
}
@Test
+ public void convertsFromPublisherSubscribeWithDelay() {
+ PublishProcessor<String> processor = PublishProcessor.create();
+ processor.delaySubscription(100, TimeUnit.SECONDS, sBackgroundScheduler);
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ processor.onNext("foo");
+ liveData.removeObserver(mObserver);
+ sBackgroundScheduler.triggerActions();
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ processor.onNext("bar");
+ processor.onNext("baz");
+
+ assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "bar", "baz")));
+ }
+
+ @Test
+ public void convertsFromPublisherThrowsException() {
+ PublishProcessor<String> processor = PublishProcessor.create();
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ IllegalStateException exception = new IllegalStateException("test exception");
+ try {
+ processor.onError(exception);
+ fail("Runtime Exception expected");
+ } catch (RuntimeException ex) {
+ assertEquals(ex.getCause(), exception);
+ }
+ }
+
+ @Test
public void convertsFromPublisherWithMultipleObservers() {
final List<String> output2 = new ArrayList<>();
PublishProcessor<String> processor = PublishProcessor.create();
@@ -125,7 +164,7 @@ public class LiveDataReactiveStreamsTest {
processor.onNext("foo");
processor.onNext("bar");
- // The second mObserver should only get the newest value and any later values.
+ // The second observer should only get the newest value and any later values.
liveData.observe(mLifecycleOwner, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
@@ -140,6 +179,32 @@ public class LiveDataReactiveStreamsTest {
}
@Test
+ public void convertsFromPublisherWithMultipleObserversAfterInactive() {
+ final List<String> output2 = new ArrayList<>();
+ PublishProcessor<String> processor = PublishProcessor.create();
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ processor.onNext("foo");
+ processor.onNext("bar");
+
+ // The second observer should only get the newest value and any later values.
+ liveData.observe(mLifecycleOwner, new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ output2.add(s);
+ }
+ });
+
+ liveData.removeObserver(mObserver);
+ processor.onNext("baz");
+
+ assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar")));
+ assertThat(output2, is(Arrays.asList("bar", "baz")));
+ }
+
+ @Test
public void convertsFromPublisherAfterInactive() {
PublishProcessor<String> processor = PublishProcessor.create();
LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
@@ -156,7 +221,7 @@ public class LiveDataReactiveStreamsTest {
}
@Test
- public void convertsFromPublisherManagesSubcriptions() {
+ public void convertsFromPublisherManagesSubscriptions() {
PublishProcessor<String> processor = PublishProcessor.create();
LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
@@ -198,7 +263,7 @@ public class LiveDataReactiveStreamsTest {
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "bar", "baz"}));
+ is(new String[]{"foo", "bar", "baz"}));
}
@Test
@@ -263,10 +328,10 @@ public class LiveDataReactiveStreamsTest {
final Subscription subscription = subscriptionSubject.blockingSingle();
subscription.request(1);
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
liveData.setValue("foo");
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
subscription.request(2);
liveData.setValue("baz");
@@ -274,7 +339,7 @@ public class LiveDataReactiveStreamsTest {
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "baz", "fizz"}));
+ is(new String[]{"foo", "baz", "fizz"}));
// 'nyan' will be dropped as there is nothing currently requesting a stream.
liveData.setValue("nyan");
@@ -282,13 +347,13 @@ public class LiveDataReactiveStreamsTest {
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "baz", "fizz"}));
+ is(new String[]{"foo", "baz", "fizz"}));
// When a new request comes in, the latest value will be pushed.
subscription.request(1);
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "baz", "fizz", "cat"}));
+ is(new String[]{"foo", "baz", "fizz", "cat"}));
}
@Test
@@ -301,17 +366,17 @@ public class LiveDataReactiveStreamsTest {
liveData.setValue("foo");
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
sBackgroundScheduler.triggerActions();
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
liveData.setValue("bar");
liveData.setValue("baz");
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
sBackgroundScheduler.triggerActions();
assertThat(mOutputProcessor.getValues(
new String[]{}),
- is(new String[] {"foo", "bar", "baz"}));
+ is(new String[]{"foo", "bar", "baz"}));
}
}
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 647d5d7a..c1dc54da 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -45,6 +45,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
@@ -52,11 +53,22 @@ import org.mockito.Mockito;
@RunWith(JUnit4.class)
public class LiveDataTest {
private PublicLiveData<String> mLiveData;
+ private MethodExec mActiveObserversChanged;
+
private LifecycleOwner mOwner;
- private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry;
+
+ private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry2;
- private MethodExec mActiveObserversChanged;
+
+ private LifecycleOwner mOwner3;
+ private Lifecycle mLifecycle3;
+ private Observer<String> mObserver3;
+
+ private LifecycleOwner mOwner4;
+ private Lifecycle mLifecycle4;
+ private Observer<String> mObserver4;
+
private boolean mInObserver;
@Before
@@ -67,12 +79,10 @@ public class LiveDataTest {
mLiveData.activeObserversChanged = mActiveObserversChanged;
mOwner = mock(LifecycleOwner.class);
-
mRegistry = new LifecycleRegistry(mOwner);
when(mOwner.getLifecycle()).thenReturn(mRegistry);
mOwner2 = mock(LifecycleOwner.class);
-
mRegistry2 = new LifecycleRegistry(mOwner2);
when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
@@ -80,6 +90,19 @@ public class LiveDataTest {
}
@Before
+ public void initNonLifecycleRegistry() {
+ mOwner3 = mock(LifecycleOwner.class);
+ mLifecycle3 = mock(Lifecycle.class);
+ mObserver3 = (Observer<String>) mock(Observer.class);
+ when(mOwner3.getLifecycle()).thenReturn(mLifecycle3);
+
+ mOwner4 = mock(LifecycleOwner.class);
+ mLifecycle4 = mock(Lifecycle.class);
+ mObserver4 = (Observer<String>) mock(Observer.class);
+ when(mOwner4.getLifecycle()).thenReturn(mLifecycle4);
+ }
+
+ @Before
public void swapExecutorDelegate() {
ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
}
@@ -572,100 +595,195 @@ public class LiveDataTest {
verify(mActiveObserversChanged, never()).onCall(anyBoolean());
}
- /**
- * Verifies that if a lifecycle's state changes without an event, and changes to something that
- * LiveData would become inactive in response to, LiveData will detect the change upon new data
- * being set and become inactive. Also verifies that once the lifecycle enters into a state
- * that LiveData should become active to, that it does indeed become active.
- */
@Test
- public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
- Observer<String> observer = (Observer<String>) mock(Observer.class);
- mLiveData.observe(mOwner, observer);
+ public void setValue_lifecycleIsCreatedNoEvent_liveDataBecomesInactiveAndObserverNotCalled() {
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+
+ GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
- // Marking state as CREATED should call onInactive.
- reset(mActiveObserversChanged);
- mRegistry.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged).onCall(false);
reset(mActiveObserversChanged);
+ reset(mObserver3);
+
+ // Act.
- // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
- // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
- // and the Observer shouldn't be affected.
mLiveData.setValue("1");
- // state is already CREATED so should not call again
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer, never()).onChanged(anyString());
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(false);
+ verify(mObserver3, never()).onChanged(anyString());
+ }
+
+ /*
+ * Arrange: LiveData was made inactive via SetValue (because the Lifecycle it was
+ * observing was in the CREATED state and no event was dispatched).
+ * Act: Lifecycle enters Started state and dispatches event.
+ * Assert: LiveData becomes active and dispatches new value to observer.
+ */
+ @Test
+ public void test_liveDataInactiveViaSetValueThenLifecycleResumes() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+
+ GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ mLiveData.setValue("1");
- // Sanity check. Because we've only marked the state as CREATED, sending ON_START
- // should re-dispatch events.
reset(mActiveObserversChanged);
- reset(observer);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ reset(mObserver3);
+
+ // Act.
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ // Assert.
+
verify(mActiveObserversChanged).onCall(true);
- verify(observer).onChanged("1");
+ verify(mObserver3).onChanged("1");
}
- /**
- * This test verifies that LiveData will detect changes in LifecycleState that would make it
- * inactive upon the setting of new data, but only if all of the Lifecycles it's observing
- * are all in those states. It also makes sure that once it is inactive, that it will become
- * active again once one of the lifecycles it's observing moves to an appropriate state.
+ /*
+ * Arrange: One of two Lifecycles enter the CREATED state without sending an event.
+ * Act: Lifecycle's setValue method is called with new value.
+ * Assert: LiveData stays active and new value is dispatched to Lifecycle that is still at least
+ * STARTED.
*/
@Test
- public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
- Observer<String> observer1 = mock(Observer.class);
- Observer<String> observer2 = mock(Observer.class);
+ public void setValue_oneOfTwoLifecyclesAreCreatedNoEvent() {
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
+ // Arrange.
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
- // Marking the state to created won't change LiveData to be inactive.
reset(mActiveObserversChanged);
- mRegistry.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
- // After setting a value, the LiveData will stay active because there is still a STARTED
- // lifecycle being observed. The one Observer associated with the STARTED lifecycle will
- // also have been called, but the other Observer will not have been called.
- reset(observer1);
- reset(observer2);
mLiveData.setValue("1");
+
+ // Assert.
+
verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer1, never()).onChanged(anyString());
- verify(observer2).onChanged("1");
+ verify(mObserver3, never()).onChanged(anyString());
+ verify(mObserver4).onChanged("1");
+ }
- // Now we set the other Lifecycle to be inactive, live data should become inactive.
- reset(observer1);
- reset(observer2);
- mRegistry2.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged).onCall(false);
- verify(observer1, never()).onChanged(anyString());
- verify(observer2, never()).onChanged(anyString());
+ /*
+ * Arrange: Two observed Lifecycles enter the CREATED state without sending an event.
+ * Act: Lifecycle's setValue method is called with new value.
+ * Assert: LiveData becomes inactive and nothing is dispatched to either observer.
+ */
+ @Test
+ public void setValue_twoLifecyclesAreCreatedNoEvent() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
- // Now we post another value, because both lifecycles are in the Created state, live data
- // will not dispatch any values
reset(mActiveObserversChanged);
- mLiveData.setValue("2");
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer1, never()).onChanged(anyString());
- verify(observer2, never()).onChanged(anyString());
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
+
+ mLiveData.setValue("1");
+
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(false);
+ verify(mObserver3, never()).onChanged(anyString());
+ verify(mObserver3, never()).onChanged(anyString());
+ }
+
+ /*
+ * Arrange: LiveData was made inactive via SetValue (because both Lifecycles it was
+ * observing were in the CREATED state and no event was dispatched).
+ * Act: One Lifecycle enters STARTED state and dispatches lifecycle event.
+ * Assert: LiveData becomes active and dispatches new value to observer associated with started
+ * Lifecycle.
+ */
+ @Test
+ public void test_liveDataInactiveViaSetValueThenOneLifecycleResumes() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+ mLiveData.setValue("1");
- // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
- // be made active and it's associated Observer will be called with the new value, but the
- // Observer associated with the Lifecycle that is still in the Created state won't be
- // called.
reset(mActiveObserversChanged);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ // Assert.
+
verify(mActiveObserversChanged).onCall(true);
- verify(observer1).onChanged("2");
- verify(observer2, never()).onChanged(anyString());
+ verify(mObserver3).onChanged("1");
+ verify(mObserver4, never()).onChanged(anyString());
+ }
+
+ private GenericLifecycleObserver getGenericLifecycleObserver(Lifecycle lifecycle) {
+ ArgumentCaptor<GenericLifecycleObserver> captor =
+ ArgumentCaptor.forClass(GenericLifecycleObserver.class);
+ verify(lifecycle).addObserver(captor.capture());
+ return (captor.getValue());
}
@SuppressWarnings("WeakerAccess")
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 29cbab8e..a7b3aeba 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -138,6 +138,7 @@ public class ViewModelProvider {
*/
public static class NewInstanceFactory implements Factory {
+ @SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
diff --git a/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java b/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
index 6f4aa68a..373b122d 100644
--- a/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
+++ b/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
@@ -45,6 +45,7 @@ public abstract class EntityDeletionOrUpdateAdapter<T> extends SharedSQLiteState
*
* @return An SQL query that can delete or update instances of T.
*/
+ @Override
protected abstract String createQuery();
/**
diff --git a/android/arch/persistence/room/Room.java b/android/arch/persistence/room/Room.java
index 2850b55e..9b168fce 100644
--- a/android/arch/persistence/room/Room.java
+++ b/android/arch/persistence/room/Room.java
@@ -72,6 +72,7 @@ public class Room {
return new RoomDatabase.Builder<>(context, klass, null);
}
+ @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
@NonNull
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
final String fullPackage = klass.getPackage().getName();
diff --git a/android/arch/persistence/room/testing/MigrationTestHelper.java b/android/arch/persistence/room/testing/MigrationTestHelper.java
index 18e0a146..2e93bbe4 100644
--- a/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ b/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -341,7 +341,7 @@ public class MigrationTestHelper extends TestWatcher {
return 0;
}
- class MigratingDelegate extends RoomOpenHelperDelegate {
+ static class MigratingDelegate extends RoomOpenHelperDelegate {
private final boolean mVerifyDroppedTables;
MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
diff --git a/android/bluetooth/BluetoothSocket.java b/android/bluetooth/BluetoothSocket.java
index 4035ee1b..05699134 100644
--- a/android/bluetooth/BluetoothSocket.java
+++ b/android/bluetooth/BluetoothSocket.java
@@ -375,7 +375,7 @@ public final class BluetoothSocket implements Closeable {
IBluetooth bluetoothProxy =
BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
- mPfd = bluetoothProxy.connectSocket(mDevice, mType,
+ mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
mUuid, mPort, getSecurityFlags());
synchronized (this) {
if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
@@ -417,7 +417,7 @@ public final class BluetoothSocket implements Closeable {
return -1;
}
try {
- mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
+ mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName,
mUuid, mPort, getSecurityFlags());
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
diff --git a/android/content/AsyncTaskLoader.java b/android/content/AsyncTaskLoader.java
index b7545bf5..6e9f09cb 100644
--- a/android/content/AsyncTaskLoader.java
+++ b/android/content/AsyncTaskLoader.java
@@ -49,7 +49,10 @@ import java.util.concurrent.Executor;
* fragment}
*
* @param <D> the data type to be loaded.
+ *
+ * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
*/
+@Deprecated
public abstract class AsyncTaskLoader<D> extends Loader<D> {
static final String TAG = "AsyncTaskLoader";
static final boolean DEBUG = false;
diff --git a/android/content/ContentProviderClient.java b/android/content/ContentProviderClient.java
index f8c139fe..2d490a03 100644
--- a/android/content/ContentProviderClient.java
+++ b/android/content/ContentProviderClient.java
@@ -22,6 +22,7 @@ import android.content.res.AssetFileDescriptor;
import android.database.CrossProcessCursorWrapper;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.DeadObjectException;
@@ -102,8 +103,16 @@ public class ContentProviderClient implements AutoCloseable {
if (sAnrHandler == null) {
sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
}
+
+ // If the remote process hangs, we're going to kill it, so we're
+ // technically okay doing blocking calls.
+ Binder.allowBlocking(mContentProvider.asBinder());
} else {
mAnrRunnable = null;
+
+ // If we're no longer watching for hangs, revert back to default
+ // blocking behavior.
+ Binder.defaultBlocking(mContentProvider.asBinder());
}
}
}
@@ -511,6 +520,10 @@ public class ContentProviderClient implements AutoCloseable {
private boolean closeInternal() {
mCloseGuard.close();
if (mClosed.compareAndSet(false, true)) {
+ // We can't do ANR checks after we cease to exist! Reset any
+ // blocking behavior changes we might have made.
+ setDetectNotResponding(0);
+
if (mStable) {
return mContentResolver.releaseProvider(mContentProvider);
} else {
diff --git a/android/content/Context.java b/android/content/Context.java
index c165fb3e..19e24ad5 100644
--- a/android/content/Context.java
+++ b/android/content/Context.java
@@ -3413,6 +3413,8 @@ public abstract class Context {
public static final String NETWORK_STATS_SERVICE = "netstats";
/** {@hide} */
public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+ /** {@hide} */
+ public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
/**
* Use with {@link #getSystemService} to retrieve a {@link
@@ -4042,6 +4044,13 @@ public abstract class Context {
public static final String STATS_COMPANION_SERVICE = "statscompanion";
/**
+ * Use with {@link #getSystemService} to retrieve an {@link android.stats.StatsManager}.
+ * @hide
+ */
+ @SystemApi
+ public static final String STATS_MANAGER = "stats";
+
+ /**
* Use with {@link #getSystemService} to retrieve a {@link
* android.content.om.OverlayManager} for managing overlay packages.
*
@@ -4071,6 +4080,14 @@ public abstract class Context {
public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.pm.crossprofile.CrossProfileApps} for cross profile operations.
+ *
+ * @see #getSystemService
+ */
+ public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/android/content/CursorLoader.java b/android/content/CursorLoader.java
index c78871c3..33386e5c 100644
--- a/android/content/CursorLoader.java
+++ b/android/content/CursorLoader.java
@@ -38,7 +38,10 @@ import java.util.Arrays;
* in the desired paramters with {@link #setUri(Uri)}, {@link #setSelection(String)},
* {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
* and {@link #setProjection(String[])}.
+ *
+ * @deprecated Use {@link android.support.v4.content.CursorLoader}
*/
+@Deprecated
public class CursorLoader extends AsyncTaskLoader<Cursor> {
final ForceLoadContentObserver mObserver;
diff --git a/android/content/Intent.java b/android/content/Intent.java
index e47de752..bad452ce 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -1728,6 +1728,9 @@ public class Intent implements Parcelable, Cloneable {
* <p>
* Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
* succeeded.
+ * <p>
+ * Requires {@link android.Manifest.permission#REQUEST_DELETE_PACKAGES}
+ * since {@link Build.VERSION_CODES#P}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
@@ -3444,11 +3447,12 @@ public class Intent implements Parcelable, Cloneable {
/**
* A broadcast action to trigger a factory reset.
*
- * <p> The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission.
+ * <p>The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. The
+ * reason for the factory reset should be specified as {@link #EXTRA_REASON}.
*
* <p>Not for use by third-party applications.
*
- * @see #EXTRA_FORCE_MASTER_CLEAR
+ * @see #EXTRA_FORCE_FACTORY_RESET
*
* {@hide}
*/
@@ -4827,7 +4831,13 @@ public class Intent implements Parcelable, Cloneable {
/** @hide */
public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
- /** {@hide} */
+ /**
+ * Intent extra: the reason that the operation associated with this intent is being performed.
+ *
+ * <p>Type: String
+ * @hide
+ */
+ @SystemApi
public static final String EXTRA_REASON = "android.intent.extra.REASON";
/**
diff --git a/android/content/Loader.java b/android/content/Loader.java
index 3faf13b6..80f9a14c 100644
--- a/android/content/Loader.java
+++ b/android/content/Loader.java
@@ -48,7 +48,10 @@ import java.io.PrintWriter;
* </div>
*
* @param <D> The result returned when the load is complete
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader}
*/
+@Deprecated
public class Loader<D> {
int mId;
OnLoadCompleteListener<D> mListener;
@@ -66,7 +69,10 @@ public class Loader<D> {
* is told it has changed. You do not normally need to use this yourself;
* it is used for you by {@link CursorLoader} to take care of executing
* an update when the cursor's backing data changes.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver}
*/
+ @Deprecated
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
@@ -90,7 +96,10 @@ public class Loader<D> {
* to find out when a Loader it is managing has completed so that this can
* be reported to its client. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener}
*/
+ @Deprecated
public interface OnLoadCompleteListener<D> {
/**
* Called on the thread that created the Loader when the load is complete.
@@ -108,7 +117,10 @@ public class Loader<D> {
* to find out when a Loader it is managing has been canceled so that it
* can schedule the next Loader. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener}
*/
+ @Deprecated
public interface OnLoadCanceledListener<D> {
/**
* Called on the thread that created the Loader when the load is canceled.
diff --git a/android/content/SharedPreferences.java b/android/content/SharedPreferences.java
index 4b09feda..d3652e88 100644
--- a/android/content/SharedPreferences.java
+++ b/android/content/SharedPreferences.java
@@ -30,6 +30,11 @@ import java.util.Set;
* when they are committed to storage. Objects that are returned from the
* various <code>get</code> methods must be treated as immutable by the application.
*
+ * <p>Note: This class provides strong consistency guarantees. It is using expensive operations
+ * which might slow down an app. Frequently changing properties or properties where loss can be
+ * tolerated should use other mechanisms. For more details read the comments on
+ * {@link Editor#commit()} and {@link Editor#apply()}.
+ *
* <p><em>Note: This class does not support use across multiple processes.</em>
*
* <div class="special reference">
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
index 41667c4c..837c00a7 100644
--- a/android/content/pm/ActivityInfo.java
+++ b/android/content/pm/ActivityInfo.java
@@ -455,7 +455,6 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
*/
public static final int FLAG_TURN_SCREEN_ON = 0x1000000;
-
/**
* @hide Bit in {@link #flags}: If set, this component will only be seen
* by the system user. Only works with broadcast receivers. Set from the
@@ -1001,20 +1000,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* Returns true if the activity's orientation is fixed.
* @hide
*/
- public boolean isFixedOrientation() {
+ boolean isFixedOrientation() {
return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
/**
- * Returns true if the specified orientation is considered fixed.
- * @hide
- */
- static public boolean isFixedOrientation(int orientation) {
- return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
- }
-
- /**
* Returns true if the activity's orientation is fixed to landscape.
* @hide
*/
diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java
index b94a410b..9e54e235 100644
--- a/android/content/pm/LauncherApps.java
+++ b/android/content/pm/LauncherApps.java
@@ -282,13 +282,13 @@ public class LauncherApps {
public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
/**
- * @hide include all pinned shortcuts by any launchers, not just by the caller,
+ * Include all pinned shortcuts by any launchers, not just by the caller,
* in the result.
- * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS}
- * permission, this flag will be ignored.
+ *
+ * The caller must be the selected assistant app to use this flag, or have the system
+ * {@code ACCESS_SHORTCUTS} permission.
*/
- @TestApi
- public static final int FLAG_MATCH_ALL_PINNED = 1 << 10;
+ public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10;
/**
* FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
@@ -302,7 +302,7 @@ public class LauncherApps {
* @hide
*/
public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
- FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED;
+ FLAG_MATCH_ALL_KINDS | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
/** @hide kept for unit tests */
@Deprecated
@@ -671,9 +671,13 @@ public class LauncherApps {
}
/**
- * Returns whether the caller can access the shortcut information.
+ * Returns whether the caller can access the shortcut information. Access is currently
+ * available to:
*
- * <p>Only the default launcher can access the shortcut information.
+ * <ul>
+ * <li>The current launcher (or default launcher if there is no set current launcher).</li>
+ * <li>The currently active voice interaction service.</li>
+ * </ul>
*
* <p>Note when this method returns {@code false}, it may be a temporary situation because
* the user is trying a new launcher application. The user may decide to change the default
diff --git a/android/content/pm/PackageInstaller.java b/android/content/pm/PackageInstaller.java
index f4fdcaa4..86288396 100644
--- a/android/content/pm/PackageInstaller.java
+++ b/android/content/pm/PackageInstaller.java
@@ -444,6 +444,9 @@ public class PackageInstaller {
* @param packageName The package to uninstall.
* @param statusReceiver Where to deliver the result.
*/
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
uninstall(packageName, 0 /*flags*/, statusReceiver);
}
@@ -476,6 +479,9 @@ public class PackageInstaller {
* @param versionedPackage The versioned package to uninstall.
* @param statusReceiver Where to deliver the result.
*/
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
public void uninstall(@NonNull VersionedPackage versionedPackage,
@NonNull IntentSender statusReceiver) {
uninstall(versionedPackage, 0 /*flags*/, statusReceiver);
@@ -1184,10 +1190,10 @@ public class PackageInstaller {
}
/**
- * Sets the UID that initiated package installation. This is informational
+ * Sets the UID that initiated the package installation. This is informational
* and may be used as a signal for anti-malware purposes.
*
- * @see PackageManager#EXTRA_VERIFICATION_INSTALLER_UID
+ * @see Intent#EXTRA_ORIGINATING_UID
*/
public void setOriginatingUid(int originatingUid) {
this.originatingUid = originatingUid;
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index b48829cf..1c5cf15d 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -102,6 +102,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -1708,13 +1709,33 @@ public class PackageParser {
*/
public static ApkLite parseApkLite(File apkFile, int flags)
throws PackageParserException {
- final String apkPath = apkFile.getAbsolutePath();
+ return parseApkLiteInner(apkFile, null, null, flags);
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param fd already open file descriptor of an apk file
+ * @param debugPathName arbitrary text name for this file, for debug output
+ * @param flags optional parse flags, such as
+ * {@link #PARSE_COLLECT_CERTIFICATES}
+ */
+ public static ApkLite parseApkLite(FileDescriptor fd, String debugPathName, int flags)
+ throws PackageParserException {
+ return parseApkLiteInner(null, fd, debugPathName, flags);
+ }
+
+ private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName,
+ int flags) throws PackageParserException {
+ final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
AssetManager assets = null;
XmlResourceParser parser = null;
try {
assets = newConfiguredAssetManager();
- int cookie = assets.addAssetPath(apkPath);
+ int cookie = fd != null
+ ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Failed to parse " + apkPath);
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
index 7fc25d82..dadfaa9f 100644
--- a/android/content/pm/ShortcutServiceInternal.java
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -73,6 +73,9 @@ public abstract class ShortcutServiceInternal {
public abstract boolean hasShortcutHostPermission(int launcherUserId,
@NonNull String callingPackage, int callingPid, int callingUid);
+ public abstract void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+ int userId);
+
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
@Nullable IntentSender resultIntent, int userId);
diff --git a/android/content/pm/crossprofile/CrossProfileApps.java b/android/content/pm/crossprofile/CrossProfileApps.java
new file mode 100644
index 00000000..c441b5f3
--- /dev/null
+++ b/android/content/pm/crossprofile/CrossProfileApps.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.crossprofile;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.List;
+
+/**
+ * Class for handling cross profile operations. Apps can use this class to interact with its
+ * instance in any profile that is in {@link #getTargetUserProfiles()}. For example, app can
+ * use this class to start its main activity in managed profile.
+ */
+public class CrossProfileApps {
+ private final Context mContext;
+ private final ICrossProfileApps mService;
+
+ /** @hide */
+ public CrossProfileApps(Context context, ICrossProfileApps service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Starts the specified main activity of the caller package in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch, it must be exported and has
+ * action {@link android.content.Intent#ACTION_MAIN}, category
+ * {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
+ * be thrown.
+ * @param user The UserHandle of the profile, must be one of the users returned by
+ * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+ * be thrown.
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon, see
+ * {@link android.content.Intent#setSourceBounds(Rect)}.
+ * @param startActivityOptions Options to pass to startActivity
+ */
+ public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user,
+ @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
+ try {
+ mService.startActivityAsUser(mContext.getPackageName(),
+ component, sourceBounds, startActivityOptions, user);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of user profiles that that the caller can use when calling other APIs in this
+ * class.
+ * <p>
+ * A user profile would be considered as a valid target user profile, provided that:
+ * <ul>
+ * <li>It gets caller app installed</li>
+ * <li>It is not equal to the calling user</li>
+ * <li>It is in the same profile group of calling user profile</li>
+ * <li>It is enabled</li>
+ * </ul>
+ *
+ * @see UserManager#getUserProfiles()
+ */
+ public @NonNull List<UserHandle> getTargetUserProfiles() {
+ try {
+ return mService.getTargetUserProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/content/res/AssetManager.java b/android/content/res/AssetManager.java
index f0adcd6c..78665609 100644
--- a/android/content/res/AssetManager.java
+++ b/android/content/res/AssetManager.java
@@ -28,8 +28,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
-import dalvik.annotation.optimization.FastNative;
-
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -694,7 +693,35 @@ public final class AssetManager implements AutoCloseable {
private native final int addAssetPathNative(String path, boolean appAsLib);
- /**
+ /**
+ * Add an additional set of assets to the asset manager from an already open
+ * FileDescriptor. Not for use by applications.
+ * This does not give full AssetManager functionality for these assets,
+ * since the origin of the file is not known for purposes of sharing,
+ * overlay resolution, and other features. However it does allow you
+ * to do simple access to the contents of the given fd as an apk file.
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ * Returns the cookie of the added asset, or 0 on failure.
+ * {@hide}
+ */
+ public int addAssetFd(FileDescriptor fd, String debugPathName) {
+ return addAssetFdInternal(fd, debugPathName, false);
+ }
+
+ private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
+ boolean appAsLib) {
+ synchronized (this) {
+ int res = addAssetFdNative(fd, debugPathName, appAsLib);
+ makeStringBlocks(mStringBlocks);
+ return res;
+ }
+ }
+
+ private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
+ boolean appAsLib);
+
+ /**
* Add a set of assets to overlay an already added set of assets.
*
* This is only intended for application resources. System wide resources
diff --git a/android/content/res/Configuration.java b/android/content/res/Configuration.java
index dfd3bbf0..26efda10 100644
--- a/android/content/res/Configuration.java
+++ b/android/content/res/Configuration.java
@@ -2105,6 +2105,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
break;
case DENSITY_DPI_NONE:
parts.add("nodpi");
+ break;
default:
parts.add(config.densityDpi + "dpi");
break;
diff --git a/android/content/res/FontResourcesParser.java b/android/content/res/FontResourcesParser.java
index 28e9fce3..6a4aae66 100644
--- a/android/content/res/FontResourcesParser.java
+++ b/android/content/res/FontResourcesParser.java
@@ -80,13 +80,15 @@ public class FontResourcesParser {
private int mWeight;
private int mItalic;
private int mTtcIndex;
+ private String mVariationSettings;
private int mResourceId;
public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
- int ttcIndex) {
+ @Nullable String variationSettings, int ttcIndex) {
mFileName = fileName;
mWeight = weight;
mItalic = italic;
+ mVariationSettings = variationSettings;
mTtcIndex = ttcIndex;
}
@@ -102,6 +104,10 @@ public class FontResourcesParser {
return mItalic;
}
+ public @Nullable String getVariationSettings() {
+ return mVariationSettings;
+ }
+
public int getTtcIndex() {
return mTtcIndex;
}
@@ -211,6 +217,8 @@ public class FontResourcesParser {
Typeface.RESOLVE_BY_FONT_TABLE);
int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
Typeface.RESOLVE_BY_FONT_TABLE);
+ String variationSettings = array.getString(
+ R.styleable.FontFamilyFont_fontVariationSettings);
int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
String filename = array.getString(R.styleable.FontFamilyFont_font);
array.recycle();
@@ -220,7 +228,7 @@ public class FontResourcesParser {
if (filename == null) {
return null;
}
- return new FontFileResourceEntry(filename, weight, italic, ttcIndex);
+ return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
index 386239cf..3239212a 100644
--- a/android/content/res/ResourcesImpl.java
+++ b/android/content/res/ResourcesImpl.java
@@ -49,6 +49,8 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.DisplayAdjustments;
+import com.android.internal.util.GrowingArrayUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -117,6 +119,13 @@ public class ResourcesImpl {
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
new ConfigurationBoundResourceCache<>();
+ // A stack of all the resourceIds already referenced when parsing a resource. This is used to
+ // detect circular references in the xml.
+ // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
+ // calls to ResourcesImpl
+ private final ThreadLocal<LookupStack> mLookupStack =
+ ThreadLocal.withInitial(() -> new LookupStack());
+
/** Size of the cyclical cache used to map XML files to blocks. */
private static final int XML_BLOCK_CACHE_SIZE = 4;
@@ -784,19 +793,29 @@ public class ResourcesImpl {
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ LookupStack stack = mLookupStack.get();
try {
- if (file.endsWith(".xml")) {
- final XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
- rp.close();
- } else {
- final InputStream is = mAssets.openNonAsset(
- value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
- is.close();
+ // Perform a linear search to check if we have already referenced this resource before.
+ if (stack.contains(id)) {
+ throw new Exception("Recursive reference in drawable");
}
- } catch (Exception | StackOverflowError e) {
+ stack.push(id);
+ try {
+ if (file.endsWith(".xml")) {
+ final XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
+ rp.close();
+ } else {
+ final InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+ dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
+ is.close();
+ }
+ } finally {
+ stack.pop();
+ }
+ } catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
@@ -1377,4 +1396,29 @@ public class ResourcesImpl {
}
}
}
+
+ private static class LookupStack {
+
+ // Pick a reasonable default size for the array, it is grown as needed.
+ private int[] mIds = new int[4];
+ private int mSize = 0;
+
+ public void push(int id) {
+ mIds = GrowingArrayUtils.append(mIds, mSize, id);
+ mSize++;
+ }
+
+ public boolean contains(int id) {
+ for (int i = 0; i < mSize; i++) {
+ if (mIds[i] == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void pop() {
+ mSize--;
+ }
+ }
}
diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java
index d9c97fe2..a32d5282 100644
--- a/android/content/res/Resources_Delegate.java
+++ b/android/content/res/Resources_Delegate.java
@@ -887,30 +887,19 @@ public class Resources_Delegate {
@LayoutlibDelegate
static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- String v = value.getSecond().getValue();
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
- if (v != null) {
- // check this is a file
- File f = new File(v);
- if (f.isFile()) {
- try {
- XmlPullParser parser = ParserFactory.create(f);
+ if (v != null) {
+ ResourceValue value = v.getSecond();
- return new BridgeXmlBlockParser(parser, getContext(resources),
- mPlatformResourceFlag[0]);
- } catch (XmlPullParserException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- }
- }
+ try {
+ return ResourceHelper.getXmlBlockParser(getContext(resources), value);
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
}
}
diff --git a/android/database/MergeCursor.java b/android/database/MergeCursor.java
index 2c25db76..272cfa24 100644
--- a/android/database/MergeCursor.java
+++ b/android/database/MergeCursor.java
@@ -17,7 +17,7 @@
package android.database;
/**
- * A convience class that lets you present an array of Cursors as a single linear Cursor.
+ * A convenience class that lets you present an array of Cursors as a single linear Cursor.
* The schema of the cursors presented is entirely up to the creator of the MergeCursor, and
* may be different if that is desired. Calls to getColumns, getColumnIndex, etc will return the
* value for the row that the MergeCursor is currently pointing at.
diff --git a/android/database/sqlite/SQLiteConnection.java b/android/database/sqlite/SQLiteConnection.java
index c28583ea..2c93a7fe 100644
--- a/android/database/sqlite/SQLiteConnection.java
+++ b/android/database/sqlite/SQLiteConnection.java
@@ -289,12 +289,19 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private void setWalModeFromConfiguration() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+ final boolean walEnabled =
+ (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ // Use compatibility WAL unless an app explicitly set journal/synchronous mode
+ final boolean useCompatibilityWal = mConfiguration.journalMode == null
+ && mConfiguration.syncMode == null && mConfiguration.useCompatibilityWal;
+ if (walEnabled || useCompatibilityWal) {
setJournalMode("WAL");
setSyncMode(SQLiteGlobal.getWALSyncMode());
} else {
- setJournalMode(SQLiteGlobal.getDefaultJournalMode());
- setSyncMode(SQLiteGlobal.getDefaultSyncMode());
+ setJournalMode(mConfiguration.journalMode == null
+ ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
+ setSyncMode(mConfiguration.syncMode == null
+ ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
}
}
}
@@ -308,12 +315,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
}
private static String canonicalizeSyncMode(String value) {
- if (value.equals("0")) {
- return "OFF";
- } else if (value.equals("1")) {
- return "NORMAL";
- } else if (value.equals("2")) {
- return "FULL";
+ switch (value) {
+ case "0": return "OFF";
+ case "1": return "NORMAL";
+ case "2": return "FULL";
}
return value;
}
@@ -414,7 +419,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
!= mConfiguration.foreignKeyConstraintsEnabled;
boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
- & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0
+ || configuration.useCompatibilityWal != mConfiguration.useCompatibilityWal;
boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
// Update configuration parameters.
diff --git a/android/database/sqlite/SQLiteCursor.java b/android/database/sqlite/SQLiteCursor.java
index 2dc5ca43..13e6f718 100644
--- a/android/database/sqlite/SQLiteCursor.java
+++ b/android/database/sqlite/SQLiteCursor.java
@@ -22,6 +22,8 @@ import android.database.DatabaseUtils;
import android.os.StrictMode;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.HashMap;
import java.util.Map;
@@ -60,6 +62,9 @@ public class SQLiteCursor extends AbstractWindowedCursor {
/** Used to find out where a cursor was allocated in case it never got released. */
private final Throwable mStackTrace;
+ /** Controls fetching of rows relative to requested position **/
+ private boolean mFillWindowForwardOnly;
+
/**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
@@ -136,18 +141,19 @@ public class SQLiteCursor extends AbstractWindowedCursor {
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
-
try {
+ Preconditions.checkArgumentNonnegative(requiredPos,
+ "requiredPos cannot be negative, but was " + requiredPos);
+
if (mCount == NO_COUNT) {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
- mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
+ mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
- mCursorWindowCapacity);
+ int startPos = mFillWindowForwardOnly ? requiredPos : DatabaseUtils
+ .cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
} catch (RuntimeException ex) {
@@ -252,6 +258,20 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
/**
+ * Controls fetching of rows relative to requested position.
+ *
+ * <p>Calling this method defines how rows will be loaded, but it doesn't affect rows that
+ * are already in the window. This setting is preserved if a new window is
+ * {@link #setWindow(CursorWindow) set}
+ *
+ * @param fillWindowForwardOnly if true, rows will be fetched starting from requested position
+ * up to the window's capacity. Default value is false.
+ */
+ public void setFillWindowForwardOnly(boolean fillWindowForwardOnly) {
+ mFillWindowForwardOnly = fillWindowForwardOnly;
+ }
+
+ /**
* Release the native resources, if they haven't been released yet.
*/
@Override
diff --git a/android/database/sqlite/SQLiteDatabase.java b/android/database/sqlite/SQLiteDatabase.java
index df0e262b..863fb198 100644
--- a/android/database/sqlite/SQLiteDatabase.java
+++ b/android/database/sqlite/SQLiteDatabase.java
@@ -262,7 +262,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
private SQLiteDatabase(final String path, final int openFlags,
CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
- int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs) {
+ int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs,
+ String journalMode, String syncMode) {
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
@@ -285,6 +286,9 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
}
mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
+ mConfigurationLocked.journalMode = journalMode;
+ mConfigurationLocked.syncMode = syncMode;
+ mConfigurationLocked.useCompatibilityWal = SQLiteGlobal.isCompatibilityWalSupported();
}
@Override
@@ -720,7 +724,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
openParams.mCursorFactory, openParams.mErrorHandler,
openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
- openParams.mIdleConnectionTimeout);
+ openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
db.open();
return db;
}
@@ -746,7 +750,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
*/
public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
@DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
- SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1);
+ SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1, null,
+ null);
db.open();
return db;
}
@@ -2070,15 +2075,21 @@ public final class SQLiteDatabase extends SQLiteClosable {
synchronized (mLock) {
throwIfNotOpenLocked();
- if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
+ final boolean oldUseCompatibilityWal = mConfigurationLocked.useCompatibilityWal;
+ final int oldFlags = mConfigurationLocked.openFlags;
+ if (!oldUseCompatibilityWal && (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
return;
}
mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING;
+ // If an app explicitly disables WAL, do not even use compatibility mode
+ mConfigurationLocked.useCompatibilityWal = false;
+
try {
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
} catch (RuntimeException ex) {
- mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING;
+ mConfigurationLocked.openFlags = oldFlags;
+ mConfigurationLocked.useCompatibilityWal = oldUseCompatibilityWal;
throw ex;
}
}
@@ -2295,17 +2306,21 @@ public final class SQLiteDatabase extends SQLiteClosable {
private final DatabaseErrorHandler mErrorHandler;
private final int mLookasideSlotSize;
private final int mLookasideSlotCount;
- private long mIdleConnectionTimeout;
+ private final long mIdleConnectionTimeout;
+ private final String mJournalMode;
+ private final String mSyncMode;
private OpenParams(int openFlags, CursorFactory cursorFactory,
DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount,
- long idleConnectionTimeout) {
+ long idleConnectionTimeout, String journalMode, String syncMode) {
mOpenFlags = openFlags;
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler;
mLookasideSlotSize = lookasideSlotSize;
mLookasideSlotCount = lookasideSlotCount;
mIdleConnectionTimeout = idleConnectionTimeout;
+ mJournalMode = journalMode;
+ mSyncMode = syncMode;
}
/**
@@ -2372,6 +2387,28 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
/**
+ * Returns <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>.
+ * This journal mode will only be used if {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING}
+ * flag is not set, otherwise a platform will use "WAL" journal mode.
+ * @see Builder#setJournalMode(String)
+ */
+ @Nullable
+ public String getJournalMode() {
+ return mJournalMode;
+ }
+
+ /**
+ * Returns <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>.
+ * This value will only be used when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag
+ * is not set, otherwise a system wide default will be used.
+ * @see Builder#setSynchronousMode(String)
+ */
+ @Nullable
+ public String getSynchronousMode() {
+ return mSyncMode;
+ }
+
+ /**
* Creates a new instance of builder {@link Builder#Builder(OpenParams) initialized} with
* {@code this} parameters.
* @hide
@@ -2391,6 +2428,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
private int mOpenFlags;
private CursorFactory mCursorFactory;
private DatabaseErrorHandler mErrorHandler;
+ private String mJournalMode;
+ private String mSyncMode;
public Builder() {
}
@@ -2401,6 +2440,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
mOpenFlags = params.mOpenFlags;
mCursorFactory = params.mCursorFactory;
mErrorHandler = params.mErrorHandler;
+ mJournalMode = params.mJournalMode;
+ mSyncMode = params.mSyncMode;
}
/**
@@ -2532,6 +2573,30 @@ public final class SQLiteDatabase extends SQLiteClosable {
return this;
}
+
+ /**
+ * Sets <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>
+ * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+ */
+ @NonNull
+ public Builder setJournalMode(@NonNull String journalMode) {
+ Preconditions.checkNotNull(journalMode);
+ mJournalMode = journalMode;
+ return this;
+ }
+
+ /**
+ * Sets <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>
+ * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+ * @return
+ */
+ @NonNull
+ public Builder setSynchronousMode(@NonNull String syncMode) {
+ Preconditions.checkNotNull(syncMode);
+ mSyncMode = syncMode;
+ return this;
+ }
+
/**
* Creates an instance of {@link OpenParams} with the options that were previously set
* on this builder
@@ -2539,7 +2604,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
@NonNull
public OpenParams build() {
return new OpenParams(mOpenFlags, mCursorFactory, mErrorHandler, mLookasideSlotSize,
- mLookasideSlotCount, mIdleConnectionTimeout);
+ mLookasideSlotCount, mIdleConnectionTimeout, mJournalMode, mSyncMode);
}
}
}
@@ -2554,4 +2619,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatabaseOpenFlags {}
+
}
+
diff --git a/android/database/sqlite/SQLiteDatabaseConfiguration.java b/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 34c9b339..a14df1eb 100644
--- a/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -111,6 +111,27 @@ public final class SQLiteDatabaseConfiguration {
public long idleConnectionTimeoutMs = Long.MAX_VALUE;
/**
+ * Enables compatibility WAL mode. Applications cannot explicitly choose compatibility WAL mode,
+ * therefore it is not exposed as a flag.
+ *
+ * <p>In this mode, only database journal mode will be changed, connection pool
+ * size will still be limited to a single connection.
+ */
+ public boolean useCompatibilityWal;
+
+ /**
+ * Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
+ * <p>Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()}
+ */
+ public String journalMode;
+
+ /**
+ * Synchronous mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
+ * <p>Default is returned by {@link SQLiteGlobal#getDefaultSyncMode()}
+ */
+ public String syncMode;
+
+ /**
* Creates a database configuration with the required parameters for opening a
* database and default values for all other parameters.
*
@@ -170,6 +191,9 @@ public final class SQLiteDatabaseConfiguration {
lookasideSlotSize = other.lookasideSlotSize;
lookasideSlotCount = other.lookasideSlotCount;
idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
+ useCompatibilityWal = other.useCompatibilityWal;
+ journalMode = other.journalMode;
+ syncMode = other.syncMode;
}
/**
diff --git a/android/database/sqlite/SQLiteGlobal.java b/android/database/sqlite/SQLiteGlobal.java
index 94d5555c..d6d9764c 100644
--- a/android/database/sqlite/SQLiteGlobal.java
+++ b/android/database/sqlite/SQLiteGlobal.java
@@ -81,6 +81,16 @@ public final class SQLiteGlobal {
}
/**
+ * Returns true if compatibility WAL mode is supported. In this mode, only
+ * database journal mode is changed. Connection pool will use at most one connection.
+ */
+ public static boolean isCompatibilityWalSupported() {
+ return SystemProperties.getBoolean("debug.sqlite.compatibility_wal_supported",
+ Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.db_compatibility_wal_supported));
+ }
+
+ /**
* Gets the journal size limit in bytes.
*/
public static int getJournalSizeLimit() {
diff --git a/android/database/sqlite/SQLiteOpenHelper.java b/android/database/sqlite/SQLiteOpenHelper.java
index cc9e0f4d..49f357e6 100644
--- a/android/database/sqlite/SQLiteOpenHelper.java
+++ b/android/database/sqlite/SQLiteOpenHelper.java
@@ -17,6 +17,8 @@
package android.database.sqlite;
import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.SQLException;
@@ -24,6 +26,8 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.os.FileUtils;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.io.File;
/**
@@ -69,7 +73,8 @@ public abstract class SQLiteOpenHelper {
* {@link #onUpgrade} will be used to upgrade the database; if the database is
* newer, {@link #onDowngrade} will be used to downgrade the database
*/
- public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+ @Nullable CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
@@ -90,12 +95,33 @@ public abstract class SQLiteOpenHelper {
* @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
* corruption, or null to use the default error handler.
*/
- public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
- DatabaseErrorHandler errorHandler) {
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+ @Nullable CursorFactory factory, int version,
+ @Nullable DatabaseErrorHandler errorHandler) {
this(context, name, factory, version, 0, errorHandler);
}
/**
+ * Create a helper object to create, open, and/or manage a database.
+ * This method always returns very quickly. The database is not actually
+ * created or opened until one of {@link #getWritableDatabase} or
+ * {@link #getReadableDatabase} is called.
+ *
+ * @param context to use to open or create the database
+ * @param name of the database file, or null for an in-memory database
+ * @param version number of the database (starting at 1); if the database is older,
+ * {@link #onUpgrade} will be used to upgrade the database; if the database is
+ * newer, {@link #onDowngrade} will be used to downgrade the database
+ * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}.
+ * Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be
+ * set when the helper opens the database
+ */
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
+ @NonNull SQLiteDatabase.OpenParams openParams) {
+ this(context, name, version, 0, openParams.toBuilder());
+ }
+
+ /**
* Same as {@link #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)}
* but also accepts an integer minimumSupportedVersion as a convenience for upgrading very old
* versions of this database that are no longer supported. If a database with older version that
@@ -118,17 +144,26 @@ public abstract class SQLiteOpenHelper {
* @see #onUpgrade(SQLiteDatabase, int, int)
* @hide
*/
- public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
- int minimumSupportedVersion, DatabaseErrorHandler errorHandler) {
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+ @Nullable CursorFactory factory, int version,
+ int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler) {
+ this(context, name, version, minimumSupportedVersion,
+ new SQLiteDatabase.OpenParams.Builder());
+ mOpenParamsBuilder.setCursorFactory(factory);
+ mOpenParamsBuilder.setErrorHandler(errorHandler);
+ }
+
+ private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
+ int minimumSupportedVersion,
+ @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
+ Preconditions.checkNotNull(openParamsBuilder);
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mNewVersion = version;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
- mOpenParamsBuilder = new SQLiteDatabase.OpenParams.Builder();
- mOpenParamsBuilder.setCursorFactory(factory);
- mOpenParamsBuilder.setErrorHandler(errorHandler);
+ mOpenParamsBuilder = openParamsBuilder;
mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
}
diff --git a/android/ext/services/notification/Assistant.java b/android/ext/services/notification/Assistant.java
index f535368b..6fe89755 100644
--- a/android/ext/services/notification/Assistant.java
+++ b/android/ext/services/notification/Assistant.java
@@ -23,16 +23,37 @@ import static android.service.notification.NotificationListenerService.Ranking
import android.app.INotificationManager;
import android.content.Context;
import android.ext.services.R;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
+import android.os.storage.StorageManager;
import android.service.notification.Adjustment;
import android.service.notification.NotificationAssistantService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Map;
/**
* Notification assistant that provides guidance on notification channel blocking
@@ -41,19 +62,112 @@ public class Assistant extends NotificationAssistantService {
private static final String TAG = "ExtAssistant";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final ArrayList<Integer> DISMISS_WITH_PREJUDICE = new ArrayList<>();
+ private static final String TAG_ASSISTANT = "assistant";
+ private static final String TAG_IMPRESSION = "impression-set";
+ private static final String ATT_KEY = "key";
+ private static final int DB_VERSION = 1;
+ private static final String ATTR_VERSION = "version";
+
+ private static final ArrayList<Integer> PREJUDICAL_DISMISSALS = new ArrayList<>();
static {
- DISMISS_WITH_PREJUDICE.add(REASON_CANCEL);
- DISMISS_WITH_PREJUDICE.add(REASON_LISTENER_CANCEL);
+ PREJUDICAL_DISMISSALS.add(REASON_CANCEL);
+ PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
}
// key : impressions tracker
- // TODO: persist across reboots
+ // TODO: prune deleted channels and apps
ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
// SBN key : channel id
ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
private Ranking mFakeRanking = null;
+ private AtomicFile mFile = null;
+
+ public Assistant() {
+ }
+
+ private void loadFile() {
+ if (DEBUG) Slog.d(TAG, "loadFile");
+ AsyncTask.execute(() -> {
+ InputStream infile = null;
+ try {
+ infile = mFile.openRead();
+ readXml(infile);
+ } catch (FileNotFoundException e) {
+ // No data yet
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read channel impressions", e);
+ } catch (NumberFormatException | XmlPullParserException e) {
+ Log.e(TAG, "Unable to parse channel impressions", e);
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ });
+ }
+
+ protected void readXml(InputStream stream)
+ throws XmlPullParserException, NumberFormatException, IOException {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (!TAG_ASSISTANT.equals(parser.getName())) {
+ continue;
+ }
+ final int impressionOuterDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, impressionOuterDepth)) {
+ if (!TAG_IMPRESSION.equals(parser.getName())) {
+ continue;
+ }
+ String key = parser.getAttributeValue(null, ATT_KEY);
+ ChannelImpressions ci = new ChannelImpressions();
+ ci.populateFromXml(parser);
+ synchronized (mkeyToImpressions) {
+ ci.append(mkeyToImpressions.get(key));
+ mkeyToImpressions.put(key, ci);
+ }
+ }
+ }
+ }
+
+ private void saveFile() throws IOException {
+ AsyncTask.execute(() -> {
+ final FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save policy file", e);
+ return;
+ }
+ try {
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ writeXml(out);
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save impressions file, restoring backup", e);
+ mFile.failWrite(stream);
+ }
+ });
+ }
+
+ protected void writeXml(XmlSerializer out) throws IOException {
+ out.startDocument(null, true);
+ out.startTag(null, TAG_ASSISTANT);
+ out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
+ synchronized (mkeyToImpressions) {
+ for (Map.Entry<String, ChannelImpressions> entry
+ : mkeyToImpressions.entrySet()) {
+ // TODO: ensure channel still exists
+ out.startTag(null, TAG_IMPRESSION);
+ out.attribute(null, ATT_KEY, entry.getKey());
+ entry.getValue().writeXml(out);
+ out.endTag(null, TAG_IMPRESSION);
+ }
+ }
+ out.endTag(null, TAG_ASSISTANT);
+ out.endDocument();
+ }
@Override
public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
@@ -87,26 +201,38 @@ public class Assistant extends NotificationAssistantService {
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
NotificationStats stats, int reason) {
try {
+ boolean updatedImpressions = false;
String channelId = mLiveNotifications.remove(sbn.getKey());
String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId);
- ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, new ChannelImpressions());
- if (stats.hasSeen()) {
- ci.incrementViews();
+ synchronized (mkeyToImpressions) {
+ ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
+ new ChannelImpressions());
+ if (stats.hasSeen()) {
+ ci.incrementViews();
+ updatedImpressions = true;
+ }
+ if (PREJUDICAL_DISMISSALS.contains(reason)) {
+ if ((!sbn.isAppGroup() || sbn.getNotification().isGroupChild())
+ && !stats.hasInteracted()
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
+ if (DEBUG) Log.i(TAG, "increment dismissals " + key);
+ ci.incrementDismissals();
+ updatedImpressions = true;
+ } else {
+ if (DEBUG) Slog.i(TAG, "reset streak " + key);
+ if (ci.getStreak() > 0) {
+ updatedImpressions = true;
+ }
+ ci.resetStreak();
+ }
+ }
+ mkeyToImpressions.put(key, ci);
}
- if (DISMISS_WITH_PREJUDICE.contains(reason)
- && !sbn.isAppGroup()
- && !sbn.getNotification().isGroupChild()
- && !stats.hasInteracted()
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
- if (DEBUG) Log.i(TAG, "increment dismissals");
- ci.incrementDismissals();
- } else {
- if (DEBUG) Slog.i(TAG, "reset streak");
- ci.resetStreak();
+ if (updatedImpressions) {
+ saveFile();
}
- mkeyToImpressions.put(key, ci);
} catch (Throwable e) {
Slog.e(TAG, "Error occurred processing removal", e);
}
@@ -121,6 +247,11 @@ public class Assistant extends NotificationAssistantService {
public void onListenerConnected() {
if (DEBUG) Log.i(TAG, "CONNECTED");
try {
+ mFile = new AtomicFile(new File(new File(
+ Environment.getDataUserCePackageDirectory(
+ StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
+ "assistant"), "block_stats.xml"));
+ loadFile();
for (StatusBarNotification sbn : getActiveNotifications()) {
onNotificationPosted(sbn);
}
@@ -129,7 +260,7 @@ public class Assistant extends NotificationAssistantService {
}
}
- private String getKey(String pkg, int userId, String channelId) {
+ protected String getKey(String pkg, int userId, String channelId) {
return pkg + "|" + userId + "|" + channelId;
}
@@ -151,6 +282,11 @@ public class Assistant extends NotificationAssistantService {
}
// for testing
+
+ protected void setFile(AtomicFile file) {
+ mFile = file;
+ }
+
protected void setFakeRanking(Ranking ranking) {
mFakeRanking = ranking;
}
@@ -162,4 +298,16 @@ public class Assistant extends NotificationAssistantService {
protected void setContext(Context context) {
mSystemContext = context;
}
+
+ protected ChannelImpressions getImpressions(String key) {
+ synchronized (mkeyToImpressions) {
+ return mkeyToImpressions.get(key);
+ }
+ }
+
+ protected void insertImpressions(String key, ChannelImpressions ci) {
+ synchronized (mkeyToImpressions) {
+ mkeyToImpressions.put(key, ci);
+ }
+ }
} \ No newline at end of file
diff --git a/android/ext/services/notification/ChannelImpressions.java b/android/ext/services/notification/ChannelImpressions.java
index 30567ccd..4ad4b241 100644
--- a/android/ext/services/notification/ChannelImpressions.java
+++ b/android/ext/services/notification/ChannelImpressions.java
@@ -18,14 +18,23 @@ package android.ext.services.notification;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Log;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
public final class ChannelImpressions implements Parcelable {
private static final String TAG = "ExtAssistant.CI";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
static final double DISMISS_TO_VIEW_RATIO_LIMIT = .8;
static final int STREAK_LIMIT = 2;
+ static final String ATT_DISMISSALS = "dismisses";
+ static final String ATT_VIEWS = "views";
+ static final String ATT_STREAK = "streak";
private int mDismissals = 0;
private int mViews = 0;
@@ -62,6 +71,14 @@ public final class ChannelImpressions implements Parcelable {
mStreak++;
}
+ public void append(ChannelImpressions additionalImpressions) {
+ if (additionalImpressions != null) {
+ mViews += additionalImpressions.getViews();
+ mStreak += additionalImpressions.getStreak();
+ mDismissals += additionalImpressions.getDismissals();
+ }
+ }
+
public void incrementViews() {
mViews++;
}
@@ -134,4 +151,36 @@ public final class ChannelImpressions implements Parcelable {
sb.append('}');
return sb.toString();
}
+
+ protected void populateFromXml(XmlPullParser parser) {
+ mDismissals = safeInt(parser, ATT_DISMISSALS, 0);
+ mStreak = safeInt(parser, ATT_STREAK, 0);
+ mViews = safeInt(parser, ATT_VIEWS, 0);
+ }
+
+ protected void writeXml(XmlSerializer out) throws IOException {
+ if (mDismissals != 0) {
+ out.attribute(null, ATT_DISMISSALS, String.valueOf(mDismissals));
+ }
+ if (mStreak != 0) {
+ out.attribute(null, ATT_STREAK, String.valueOf(mStreak));
+ }
+ if (mViews != 0) {
+ out.attribute(null, ATT_VIEWS, String.valueOf(mViews));
+ }
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseInt(val, defValue);
+ }
+
+ private static int tryParseInt(String value, int defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
}
diff --git a/android/graphics/Paint.java b/android/graphics/Paint.java
index 1a06a568..317144a2 100644
--- a/android/graphics/Paint.java
+++ b/android/graphics/Paint.java
@@ -88,7 +88,7 @@ public class Paint {
* A map from a string representation of the LocaleList to Minikin's language list ID.
*/
@GuardedBy("sCacheLock")
- private static final HashMap<String, Integer> sMinikinLangListIdCache = new HashMap<>();
+ private static final HashMap<String, Integer> sMinikinLocaleListIdCache = new HashMap<>();
/**
* @hide
@@ -1445,16 +1445,16 @@ public class Paint {
private void syncTextLocalesWithMinikin() {
final String languageTags = mLocales.toLanguageTags();
- final Integer minikinLangListId;
+ final Integer minikinLocaleListId;
synchronized (sCacheLock) {
- minikinLangListId = sMinikinLangListIdCache.get(languageTags);
- if (minikinLangListId == null) {
+ minikinLocaleListId = sMinikinLocaleListIdCache.get(languageTags);
+ if (minikinLocaleListId == null) {
final int newID = nSetTextLocales(mNativePaint, languageTags);
- sMinikinLangListIdCache.put(languageTags, newID);
+ sMinikinLocaleListIdCache.put(languageTags, newID);
return;
}
}
- nSetTextLocalesByMinikinLangListId(mNativePaint, minikinLangListId.intValue());
+ nSetTextLocalesByMinikinLocaleListId(mNativePaint, minikinLocaleListId.intValue());
}
/**
@@ -2918,8 +2918,8 @@ public class Paint {
@CriticalNative
private static native void nSetTextAlign(long paintPtr, int align);
@CriticalNative
- private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
- int mMinikinLangListId);
+ private static native void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
+ int mMinikinLocaleListId);
@CriticalNative
private static native void nSetShadowLayer(long paintPtr,
float radius, float dx, float dy, int color);
diff --git a/android/graphics/Paint_Delegate.java b/android/graphics/Paint_Delegate.java
index ef452034..62427020 100644
--- a/android/graphics/Paint_Delegate.java
+++ b/android/graphics/Paint_Delegate.java
@@ -950,7 +950,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+ /*package*/ static void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
int mMinikinLangListId) {
// FIXME
}
diff --git a/android/graphics/Rect.java b/android/graphics/Rect.java
index 3dc928de..aff942da 100644
--- a/android/graphics/Rect.java
+++ b/android/graphics/Rect.java
@@ -475,6 +475,19 @@ public final class Rect implements Parcelable {
}
/**
+ * If the specified rectangle intersects this rectangle, set this rectangle to that
+ * intersection, otherwise set this rectangle to the empty rectangle.
+ * @see #inset(int, int, int, int) but without checking if the rects overlap.
+ * @hide
+ */
+ public void intersectUnchecked(Rect other) {
+ left = Math.max(left, other.left);
+ top = Math.max(top, other.top);
+ right = Math.min(right, other.right);
+ bottom = Math.min(bottom, other.bottom);
+ }
+
+ /**
* If rectangles a and b intersect, return true and set this rectangle to
* that intersection, otherwise return false and do not change this
* rectangle. No check is performed to see if either rectangle is empty.
diff --git a/android/graphics/Typeface.java b/android/graphics/Typeface.java
index 9961ed64..3d65bd22 100644
--- a/android/graphics/Typeface.java
+++ b/android/graphics/Typeface.java
@@ -250,10 +250,10 @@ public class Typeface {
FontFamily fontFamily = new FontFamily();
for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
- // TODO: Add variation font support. (b/37853920)
if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
- fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
+ fontFile.getWeight(), fontFile.getItalic(),
+ FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
return null;
}
}
diff --git a/android/graphics/Typeface_Delegate.java b/android/graphics/Typeface_Delegate.java
index b9c03531..d793adee 100644
--- a/android/graphics/Typeface_Delegate.java
+++ b/android/graphics/Typeface_Delegate.java
@@ -16,19 +16,32 @@
package android.graphics;
+import com.android.SdkConstants;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.FontResourcesParser;
import android.graphics.FontFamily_Delegate.FontVariant;
import android.graphics.fonts.FontVariationAxis;
import android.text.FontConfig;
import android.util.ArrayMap;
import java.awt.Font;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.nio.file.Files;
@@ -231,12 +244,72 @@ public final class Typeface_Delegate {
return fontFamily;
}
+ /**
+ * Loads a single font or font family from disk
+ */
+ @Nullable
+ public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path,
+ boolean isFramework) {
+ // Check if this is an asset that we've already loaded dynamically
+ Typeface typeface = Typeface.findFromCache(context.getAssets(), path);
+ if (typeface != null) {
+ return typeface;
+ }
+
+ String lowerCaseValue = path.toLowerCase();
+ if (lowerCaseValue.endsWith(SdkConstants.DOT_XML)) {
+ // create a block parser for the file
+ Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+ RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+ XmlPullParser parser = null;
+ if (psiParserSupport != null && psiParserSupport) {
+ parser = context.getLayoutlibCallback().getXmlFileParser(path);
+ } else {
+ File f = new File(path);
+ if (f.isFile()) {
+ try {
+ parser = ParserFactory.create(f);
+ } catch (XmlPullParserException | FileNotFoundException e) {
+ // this is an error and not warning since the file existence is checked
+ // before
+ // attempting to parse it.
+ Bridge.getLog().error(null, "Failed to parse file " + path, e,
+ null /*data*/);
+ }
+ }
+ }
+
+ if (parser != null) {
+ BridgeXmlBlockParser blockParser =
+ new BridgeXmlBlockParser(parser, context, isFramework);
+ try {
+ FontResourcesParser.FamilyResourceEntry entry =
+ FontResourcesParser.parse(blockParser, context.getResources());
+ typeface = Typeface.createFromResources(entry, context.getAssets(), path);
+ } catch (XmlPullParserException | IOException e) {
+ Bridge.getLog().error(null, "Failed to parse file " + path, e, null /*data*/);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("File %s does not exist (or is not a file)", path),
+ null /*data*/);
+ }
+ } else {
+ typeface = Typeface.createFromResources(context.getAssets(), path, 0);
+ }
+
+ return typeface;
+ }
+
@LayoutlibDelegate
/*package*/ static Typeface create(String familyName, int style) {
if (familyName != null && Files.exists(Paths.get(familyName))) {
// Workaround for b/64137851
// Support lib will call this method after failing to create the TypefaceCompat.
- return Typeface.createFromFile(familyName);
+ return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName,
+ false);
}
return Typeface.create_Original(familyName, style);
}
diff --git a/android/graphics/drawable/AnimatedVectorDrawable.java b/android/graphics/drawable/AnimatedVectorDrawable.java
index 90d6ab86..e74dc6dc 100644
--- a/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -132,7 +132,7 @@ import dalvik.annotation.optimization.FastNative;
* <td>translateY</td>
* </tr>
* <tr>
- * <td rowspan="8">&lt;path&gt;</td>
+ * <td rowspan="9">&lt;path&gt;</td>
* <td>pathData</td>
* </tr>
* <tr>
@@ -154,6 +154,9 @@ import dalvik.annotation.optimization.FastNative;
* <td>trimPathStart</td>
* </tr>
* <tr>
+ * <td>trimPathEnd</td>
+ * </tr>
+ * <tr>
* <td>trimPathOffset</td>
* </tr>
* <tr>
diff --git a/android/graphics/drawable/RippleBackground.java b/android/graphics/drawable/RippleBackground.java
index 3bf4f902..dea194e4 100644
--- a/android/graphics/drawable/RippleBackground.java
+++ b/android/graphics/drawable/RippleBackground.java
@@ -36,138 +36,69 @@ class RippleBackground extends RippleComponent {
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
- private static final int OPACITY_ENTER_DURATION = 600;
- private static final int OPACITY_ENTER_DURATION_FAST = 120;
- private static final int OPACITY_EXIT_DURATION = 480;
+ private static final int OPACITY_DURATION = 80;
- // Hardware rendering properties.
- private CanvasProperty<Paint> mPropPaint;
- private CanvasProperty<Float> mPropRadius;
- private CanvasProperty<Float> mPropX;
- private CanvasProperty<Float> mPropY;
+ private ObjectAnimator mAnimator;
- // Software rendering properties.
private float mOpacity = 0;
/** Whether this ripple is bounded. */
private boolean mIsBounded;
- public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded,
- boolean forceSoftware) {
- super(owner, bounds, forceSoftware);
+ private boolean mFocused = false;
+ private boolean mHovered = false;
+
+ public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded) {
+ super(owner, bounds);
mIsBounded = isBounded;
}
public boolean isVisible() {
- return mOpacity > 0 || isHardwareAnimating();
+ return mOpacity > 0;
}
- @Override
- protected boolean drawSoftware(Canvas c, Paint p) {
- boolean hasContent = false;
-
+ public void draw(Canvas c, Paint p) {
final int origAlpha = p.getAlpha();
- final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+ final int alpha = Math.min((int) (origAlpha * mOpacity + 0.5f), 255);
if (alpha > 0) {
p.setAlpha(alpha);
c.drawCircle(0, 0, mTargetRadius, p);
p.setAlpha(origAlpha);
- hasContent = true;
}
-
- return hasContent;
- }
-
- @Override
- protected boolean drawHardware(DisplayListCanvas c) {
- c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
- return true;
- }
-
- @Override
- protected Animator createSoftwareEnter(boolean fast) {
- // Linear enter based on current opacity.
- final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
- final int duration = (int) ((1 - mOpacity) * maxDuration);
-
- final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
- opacity.setAutoCancel(true);
- opacity.setDuration(duration);
- opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- return opacity;
}
- @Override
- protected Animator createSoftwareExit() {
- final AnimatorSet set = new AnimatorSet();
-
- // Linear exit after enter is completed.
- final ObjectAnimator exit = ObjectAnimator.ofFloat(this, OPACITY, 0);
- exit.setInterpolator(LINEAR_INTERPOLATOR);
- exit.setDuration(OPACITY_EXIT_DURATION);
- exit.setAutoCancel(true);
-
- final AnimatorSet.Builder builder = set.play(exit);
-
- // Linear "fast" enter based on current opacity.
- final int fastEnterDuration = mIsBounded ?
- (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
- if (fastEnterDuration > 0) {
- final ObjectAnimator enter = ObjectAnimator.ofFloat(this, OPACITY, 1);
- enter.setInterpolator(LINEAR_INTERPOLATOR);
- enter.setDuration(fastEnterDuration);
- enter.setAutoCancel(true);
-
- builder.after(enter);
+ public void setState(boolean focused, boolean hovered, boolean animateChanged) {
+ if (mHovered != hovered || mFocused != focused) {
+ mHovered = hovered;
+ mFocused = focused;
+ onStateChanged(animateChanged);
}
-
- return set;
}
- @Override
- protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
- final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
-
- final int targetAlpha = p.getAlpha();
- final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
- p.setAlpha(currentAlpha);
-
- mPropPaint = CanvasProperty.createPaint(p);
- mPropRadius = CanvasProperty.createFloat(mTargetRadius);
- mPropX = CanvasProperty.createFloat(0);
- mPropY = CanvasProperty.createFloat(0);
-
- final int fastEnterDuration = mIsBounded ?
- (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
-
- // Linear exit after enter is completed.
- final RenderNodeAnimator exit = new RenderNodeAnimator(
- mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
- exit.setInterpolator(LINEAR_INTERPOLATOR);
- exit.setDuration(OPACITY_EXIT_DURATION);
- if (fastEnterDuration > 0) {
- exit.setStartDelay(fastEnterDuration);
- exit.setStartValue(targetAlpha);
+ private void onStateChanged(boolean animateChanged) {
+ float newOpacity = 0.0f;
+ if (mHovered) newOpacity += 1.0f;
+ if (mFocused) newOpacity += 1.0f;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ mAnimator = null;
}
- set.add(exit);
-
- // Linear "fast" enter based on current opacity.
- if (fastEnterDuration > 0) {
- final RenderNodeAnimator enter = new RenderNodeAnimator(
- mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
- enter.setInterpolator(LINEAR_INTERPOLATOR);
- enter.setDuration(fastEnterDuration);
- set.add(enter);
+ if (animateChanged) {
+ mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity);
+ mAnimator.setDuration(OPACITY_DURATION);
+ mAnimator.setInterpolator(LINEAR_INTERPOLATOR);
+ mAnimator.start();
+ } else {
+ mOpacity = newOpacity;
}
-
- return set;
}
- @Override
- protected void jumpValuesToExit() {
- mOpacity = 0;
+ public void jumpToFinal() {
+ if (mAnimator != null) {
+ mAnimator.end();
+ mAnimator = null;
+ }
}
private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
diff --git a/android/graphics/drawable/RippleComponent.java b/android/graphics/drawable/RippleComponent.java
index e83513c6..0e38826e 100644
--- a/android/graphics/drawable/RippleComponent.java
+++ b/android/graphics/drawable/RippleComponent.java
@@ -27,23 +27,14 @@ import android.view.RenderNodeAnimator;
import java.util.ArrayList;
/**
- * Abstract class that handles hardware/software hand-off and lifecycle for
- * animated ripple foreground and background components.
+ * Abstract class that handles size & positioning common to the ripple & focus states.
*/
abstract class RippleComponent {
- private final RippleDrawable mOwner;
+ protected final RippleDrawable mOwner;
/** Bounds used for computing max radius. May be modified by the owner. */
protected final Rect mBounds;
- /** Whether we can use hardware acceleration for the exit animation. */
- private boolean mHasDisplayListCanvas;
-
- private boolean mHasPendingHardwareAnimator;
- private RenderNodeAnimatorSet mHardwareAnimator;
-
- private Animator mSoftwareAnimator;
-
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
@@ -53,16 +44,9 @@ abstract class RippleComponent {
/** Screen density used to adjust pixel-based constants. */
protected float mDensityScale;
- /**
- * If set, force all ripple animations to not run on RenderThread, even if it would be
- * available.
- */
- private final boolean mForceSoftware;
-
- public RippleComponent(RippleDrawable owner, Rect bounds, boolean forceSoftware) {
+ public RippleComponent(RippleDrawable owner, Rect bounds) {
mOwner = owner;
mBounds = bounds;
- mForceSoftware = forceSoftware;
}
public void onBoundsChange() {
@@ -92,89 +76,6 @@ abstract class RippleComponent {
}
/**
- * Starts a ripple enter animation.
- *
- * @param fast whether the ripple should enter quickly
- */
- public final void enter(boolean fast) {
- cancel();
-
- mSoftwareAnimator = createSoftwareEnter(fast);
-
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.start();
- }
- }
-
- /**
- * Starts a ripple exit animation.
- */
- public final void exit() {
- cancel();
-
- if (mHasDisplayListCanvas) {
- // We don't have access to a canvas here, but we expect one on the
- // next frame. We'll start the render thread animation then.
- mHasPendingHardwareAnimator = true;
-
- // Request another frame.
- invalidateSelf();
- } else {
- mSoftwareAnimator = createSoftwareExit();
- mSoftwareAnimator.start();
- }
- }
-
- /**
- * Cancels all animations. Software animation values are left in the
- * current state, while hardware animation values jump to the end state.
- */
- public void cancel() {
- cancelSoftwareAnimations();
- endHardwareAnimations();
- }
-
- /**
- * Ends all animations, jumping values to the end state.
- */
- public void end() {
- endSoftwareAnimations();
- endHardwareAnimations();
- }
-
- /**
- * Draws the ripple to the canvas, inheriting the paint's color and alpha
- * properties.
- *
- * @param c the canvas to which the ripple should be drawn
- * @param p the paint used to draw the ripple
- * @return {@code true} if something was drawn, {@code false} otherwise
- */
- public boolean draw(Canvas c, Paint p) {
- final boolean hasDisplayListCanvas = !mForceSoftware && c.isHardwareAccelerated()
- && c instanceof DisplayListCanvas;
- if (mHasDisplayListCanvas != hasDisplayListCanvas) {
- mHasDisplayListCanvas = hasDisplayListCanvas;
-
- if (!hasDisplayListCanvas) {
- // We've switched from hardware to non-hardware mode. Panic.
- endHardwareAnimations();
- }
- }
-
- if (hasDisplayListCanvas) {
- final DisplayListCanvas hw = (DisplayListCanvas) c;
- startPendingAnimation(hw, p);
-
- if (mHardwareAnimator != null) {
- return drawHardware(hw);
- }
- }
-
- return drawSoftware(c, p);
- }
-
- /**
* Populates {@code bounds} with the maximum drawing bounds of the ripple
* relative to its center. The resulting bounds should be translated into
* parent drawable coordinates before use.
@@ -186,77 +87,10 @@ abstract class RippleComponent {
bounds.set(-r, -r, r, r);
}
- /**
- * Starts the pending hardware animation, if available.
- *
- * @param hw hardware canvas on which the animation should draw
- * @param p paint whose properties the hardware canvas should use
- */
- private void startPendingAnimation(DisplayListCanvas hw, Paint p) {
- if (mHasPendingHardwareAnimator) {
- mHasPendingHardwareAnimator = false;
-
- mHardwareAnimator = createHardwareExit(new Paint(p));
- mHardwareAnimator.start(hw);
-
- // Preemptively jump the software values to the end state now that
- // the hardware exit has read whatever values it needs.
- jumpValuesToExit();
- }
- }
-
- /**
- * Cancels any current software animations, leaving the values in their
- * current state.
- */
- private void cancelSoftwareAnimations() {
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.cancel();
- mSoftwareAnimator = null;
- }
- }
-
- /**
- * Ends any current software animations, jumping the values to their end
- * state.
- */
- private void endSoftwareAnimations() {
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.end();
- mSoftwareAnimator = null;
- }
- }
-
- /**
- * Ends any pending or current hardware animations.
- * <p>
- * Hardware animations can't synchronize values back to the software
- * thread, so there is no "cancel" equivalent.
- */
- private void endHardwareAnimations() {
- if (mHardwareAnimator != null) {
- mHardwareAnimator.end();
- mHardwareAnimator = null;
- }
-
- if (mHasPendingHardwareAnimator) {
- mHasPendingHardwareAnimator = false;
-
- // Manually jump values to their exited state. Normally we'd do that
- // later when starting the hardware exit, but we're aborting early.
- jumpValuesToExit();
- }
- }
-
protected final void invalidateSelf() {
mOwner.invalidateSelf(false);
}
- protected final boolean isHardwareAnimating() {
- return mHardwareAnimator != null && mHardwareAnimator.isRunning()
- || mHasPendingHardwareAnimator;
- }
-
protected final void onHotspotBoundsChanged() {
if (!mHasMaxRadius) {
final float halfWidth = mBounds.width() / 2.0f;
@@ -276,76 +110,4 @@ abstract class RippleComponent {
protected void onTargetRadiusChanged(float targetRadius) {
// Stub.
}
-
- protected abstract Animator createSoftwareEnter(boolean fast);
-
- protected abstract Animator createSoftwareExit();
-
- protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p);
-
- protected abstract boolean drawHardware(DisplayListCanvas c);
-
- protected abstract boolean drawSoftware(Canvas c, Paint p);
-
- /**
- * Called when the hardware exit is cancelled. Jumps software values to end
- * state to ensure that software and hardware values are synchronized.
- */
- protected abstract void jumpValuesToExit();
-
- public static class RenderNodeAnimatorSet {
- private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>();
-
- public void add(RenderNodeAnimator anim) {
- mAnimators.add(anim);
- }
-
- public void clear() {
- mAnimators.clear();
- }
-
- public void start(DisplayListCanvas target) {
- if (target == null) {
- throw new IllegalArgumentException("Hardware canvas must be non-null");
- }
-
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.setTarget(target);
- anim.start();
- }
- }
-
- public void cancel() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.cancel();
- }
- }
-
- public void end() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.end();
- }
- }
-
- public boolean isRunning() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- if (anim.isRunning()) {
- return true;
- }
- }
- return false;
- }
- }
}
diff --git a/android/graphics/drawable/RippleDrawable.java b/android/graphics/drawable/RippleDrawable.java
index 1727eca5..8b185f2b 100644
--- a/android/graphics/drawable/RippleDrawable.java
+++ b/android/graphics/drawable/RippleDrawable.java
@@ -16,11 +16,6 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo.Config;
@@ -42,6 +37,11 @@ import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.Arrays;
@@ -135,9 +135,6 @@ public class RippleDrawable extends LayerDrawable {
private PorterDuffColorFilter mMaskColorFilter;
private boolean mHasValidMask;
- /** Whether we expect to draw a background when visible. */
- private boolean mBackgroundActive;
-
/** The current ripple. May be actively animating or pending entry. */
private RippleForeground mRipple;
@@ -217,7 +214,7 @@ public class RippleDrawable extends LayerDrawable {
}
if (mBackground != null) {
- mBackground.end();
+ mBackground.jumpToFinal();
}
cancelExitingRipples();
@@ -266,9 +263,9 @@ public class RippleDrawable extends LayerDrawable {
}
}
- setRippleActive(focused || (enabled && pressed));
+ setRippleActive(enabled && pressed);
- setBackgroundActive(hovered, hovered);
+ setBackgroundActive(hovered, focused);
return changed;
}
@@ -283,14 +280,13 @@ public class RippleDrawable extends LayerDrawable {
}
}
- private void setBackgroundActive(boolean active, boolean focused) {
- if (mBackgroundActive != active) {
- mBackgroundActive = active;
- if (active) {
- tryBackgroundEnter(focused);
- } else {
- tryBackgroundExit();
- }
+ private void setBackgroundActive(boolean hovered, boolean focused) {
+ if (mBackground == null && (hovered || focused)) {
+ mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
+ mBackground.setup(mState.mMaxRadius, mDensity);
+ }
+ if (mBackground != null) {
+ mBackground.setState(focused, hovered, true);
}
}
@@ -327,10 +323,6 @@ public class RippleDrawable extends LayerDrawable {
tryRippleEnter();
}
- if (mBackgroundActive) {
- tryBackgroundEnter(false);
- }
-
// Skip animations, just show the correct final states.
jumpToCurrentState();
}
@@ -546,26 +538,6 @@ public class RippleDrawable extends LayerDrawable {
}
/**
- * Creates an active hotspot at the specified location.
- */
- private void tryBackgroundEnter(boolean focused) {
- if (mBackground == null) {
- final boolean isBounded = isBounded();
- mBackground = new RippleBackground(this, mHotspotBounds, isBounded, mForceSoftware);
- }
-
- mBackground.setup(mState.mMaxRadius, mDensity);
- mBackground.enter(focused);
- }
-
- private void tryBackgroundExit() {
- if (mBackground != null) {
- // Don't null out the background, we need it to draw!
- mBackground.exit();
- }
- }
-
- /**
* Attempts to start an enter animation for the active hotspot. Fails if
* there are too many animating ripples.
*/
@@ -593,7 +565,7 @@ public class RippleDrawable extends LayerDrawable {
}
mRipple.setup(mState.mMaxRadius, mDensity);
- mRipple.enter(false);
+ mRipple.enter();
}
/**
@@ -623,9 +595,7 @@ public class RippleDrawable extends LayerDrawable {
}
if (mBackground != null) {
- mBackground.end();
- mBackground = null;
- mBackgroundActive = false;
+ mBackground.setState(false, false, false);
}
cancelExitingRipples();
@@ -858,6 +828,40 @@ public class RippleDrawable extends LayerDrawable {
final float y = mHotspotBounds.exactCenterY();
canvas.translate(x, y);
+ final Paint p = getRipplePaint();
+
+ if (background != null && background.isVisible()) {
+ background.draw(canvas, p);
+ }
+
+ if (count > 0) {
+ final RippleForeground[] ripples = mExitingRipples;
+ for (int i = 0; i < count; i++) {
+ ripples[i].draw(canvas, p);
+ }
+ }
+
+ if (active != null) {
+ active.draw(canvas, p);
+ }
+
+ canvas.translate(-x, -y);
+ }
+
+ private void drawMask(Canvas canvas) {
+ mMask.draw(canvas);
+ }
+
+ Paint getRipplePaint() {
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+ mRipplePaint.setStyle(Paint.Style.FILL);
+ }
+
+ final float x = mHotspotBounds.exactCenterX();
+ final float y = mHotspotBounds.exactCenterY();
+
updateMaskShaderIfNeeded();
// Position the shader to account for canvas translation.
@@ -871,7 +875,7 @@ public class RippleDrawable extends LayerDrawable {
// half so that the ripple and background together yield full alpha.
final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
final int halfAlpha = (Color.alpha(color) / 2) << 24;
- final Paint p = getRipplePaint();
+ final Paint p = mRipplePaint;
if (mMaskColorFilter != null) {
// The ripple timing depends on the paint's alpha value, so we need
@@ -890,35 +894,7 @@ public class RippleDrawable extends LayerDrawable {
p.setShader(null);
}
- if (background != null && background.isVisible()) {
- background.draw(canvas, p);
- }
-
- if (count > 0) {
- final RippleForeground[] ripples = mExitingRipples;
- for (int i = 0; i < count; i++) {
- ripples[i].draw(canvas, p);
- }
- }
-
- if (active != null) {
- active.draw(canvas, p);
- }
-
- canvas.translate(-x, -y);
- }
-
- private void drawMask(Canvas canvas) {
- mMask.draw(canvas);
- }
-
- private Paint getRipplePaint() {
- if (mRipplePaint == null) {
- mRipplePaint = new Paint();
- mRipplePaint.setAntiAlias(true);
- mRipplePaint.setStyle(Paint.Style.FILL);
- }
- return mRipplePaint;
+ return p;
}
@Override
diff --git a/android/graphics/drawable/RippleForeground.java b/android/graphics/drawable/RippleForeground.java
index a675eaf8..0b5020cb 100644
--- a/android/graphics/drawable/RippleForeground.java
+++ b/android/graphics/drawable/RippleForeground.java
@@ -18,7 +18,6 @@ package android.graphics.drawable;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
@@ -29,8 +28,11 @@ import android.util.FloatProperty;
import android.util.MathUtils;
import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
+import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
+import java.util.ArrayList;
+
/**
* Draws a ripple foreground.
*/
@@ -40,7 +42,7 @@ class RippleForeground extends RippleComponent {
400f, 1.4f, 0);
// Pixel-based accelerations and velocities.
- private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
+ private static final float WAVE_TOUCH_DOWN_ACCELERATION = 2048;
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
// Bounded ripple animation properties.
@@ -49,8 +51,9 @@ class RippleForeground extends RippleComponent {
private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
private static final float MAX_BOUNDED_RADIUS = 350;
- private static final int RIPPLE_ENTER_DELAY = 80;
- private static final int OPACITY_ENTER_DURATION_FAST = 120;
+ private static final int OPACITY_ENTER_DURATION = 75;
+ private static final int OPACITY_EXIT_DURATION = 150;
+ private static final int OPACITY_HOLD_DURATION = OPACITY_ENTER_DURATION + 150;
// Parent-relative values for starting position.
private float mStartingX;
@@ -72,7 +75,7 @@ class RippleForeground extends RippleComponent {
private float mBoundedRadius = 0;
// Software rendering properties.
- private float mOpacity = 1;
+ private float mOpacity = 0;
// Values used to tween between the start and end positions.
private float mTweenRadius = 0;
@@ -82,6 +85,22 @@ class RippleForeground extends RippleComponent {
/** Whether this ripple has finished its exit animation. */
private boolean mHasFinishedExit;
+ /** Whether we can use hardware acceleration for the exit animation. */
+ private boolean mUsingProperties;
+
+ private long mEnterStartedAtMillis;
+
+ private ArrayList<RenderNodeAnimator> mPendingHwAnimators = new ArrayList<>();
+ private ArrayList<RenderNodeAnimator> mRunningHwAnimators = new ArrayList<>();
+
+ private ArrayList<Animator> mRunningSwAnimators = new ArrayList<>();
+
+ /**
+ * If set, force all ripple animations to not run on RenderThread, even if it would be
+ * available.
+ */
+ private final boolean mForceSoftware;
+
/**
* If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
*/
@@ -89,8 +108,9 @@ class RippleForeground extends RippleComponent {
public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
boolean isBounded, boolean forceSoftware) {
- super(owner, bounds, forceSoftware);
+ super(owner, bounds);
+ mForceSoftware = forceSoftware;
mStartingX = startingX;
mStartingY = startingY;
@@ -109,10 +129,7 @@ class RippleForeground extends RippleComponent {
clampStartingPosition();
}
- @Override
- protected boolean drawSoftware(Canvas c, Paint p) {
- boolean hasContent = false;
-
+ private void drawSoftware(Canvas c, Paint p) {
final int origAlpha = p.getAlpha();
final int alpha = (int) (origAlpha * mOpacity + 0.5f);
final float radius = getCurrentRadius();
@@ -122,16 +139,51 @@ class RippleForeground extends RippleComponent {
p.setAlpha(alpha);
c.drawCircle(x, y, radius, p);
p.setAlpha(origAlpha);
- hasContent = true;
}
+ }
- return hasContent;
+ private void startPending(DisplayListCanvas c) {
+ if (!mPendingHwAnimators.isEmpty()) {
+ for (int i = 0; i < mPendingHwAnimators.size(); i++) {
+ RenderNodeAnimator animator = mPendingHwAnimators.get(i);
+ animator.setTarget(c);
+ animator.start();
+ mRunningHwAnimators.add(animator);
+ }
+ mPendingHwAnimators.clear();
+ }
}
- @Override
- protected boolean drawHardware(DisplayListCanvas c) {
- c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
- return true;
+ private void pruneHwFinished() {
+ if (!mRunningHwAnimators.isEmpty()) {
+ for (int i = mRunningHwAnimators.size() - 1; i >= 0; i--) {
+ if (!mRunningHwAnimators.get(i).isRunning()) {
+ mRunningHwAnimators.remove(i);
+ }
+ }
+ }
+ }
+
+ private void pruneSwFinished() {
+ if (!mRunningSwAnimators.isEmpty()) {
+ for (int i = mRunningSwAnimators.size() - 1; i >= 0; i--) {
+ if (!mRunningSwAnimators.get(i).isRunning()) {
+ mRunningSwAnimators.remove(i);
+ }
+ }
+ }
+ }
+
+ private void drawHardware(DisplayListCanvas c, Paint p) {
+ startPending(c);
+ pruneHwFinished();
+ if (mPropPaint != null) {
+ mUsingProperties = true;
+ c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+ } else {
+ mUsingProperties = false;
+ drawSoftware(c, p);
+ }
}
/**
@@ -162,141 +214,152 @@ class RippleForeground extends RippleComponent {
return mHasFinishedExit;
}
- @Override
- protected Animator createSoftwareEnter(boolean fast) {
+ private long computeFadeOutDelay() {
+ long timeSinceEnter = AnimationUtils.currentAnimationTimeMillis() - mEnterStartedAtMillis;
+ if (timeSinceEnter > 0 && timeSinceEnter < OPACITY_HOLD_DURATION) {
+ return OPACITY_HOLD_DURATION - timeSinceEnter;
+ }
+ return 0;
+ }
+
+ private void startSoftwareEnter() {
+ for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+ mRunningSwAnimators.get(i).cancel();
+ }
+ mRunningSwAnimators.clear();
+
final int duration = getRadiusDuration();
final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
- tweenRadius.setAutoCancel(true);
tweenRadius.setDuration(duration);
tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
- tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
+ tweenRadius.start();
+ mRunningSwAnimators.add(tweenRadius);
final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
- tweenOrigin.setAutoCancel(true);
tweenOrigin.setDuration(duration);
tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
- tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
+ tweenOrigin.start();
+ mRunningSwAnimators.add(tweenOrigin);
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
- opacity.setAutoCancel(true);
- opacity.setDuration(OPACITY_ENTER_DURATION_FAST);
+ opacity.setDuration(OPACITY_ENTER_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- final AnimatorSet set = new AnimatorSet();
- set.play(tweenOrigin).with(tweenRadius).with(opacity);
-
- return set;
+ opacity.start();
+ mRunningSwAnimators.add(opacity);
}
- private float getCurrentX() {
- return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
- }
-
- private float getCurrentY() {
- return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
- }
-
- private int getRadiusDuration() {
- final float remainingRadius = mTargetRadius - getCurrentRadius();
- return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION *
- mDensityScale) + 0.5);
- }
-
- private float getCurrentRadius() {
- return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
- }
-
- private int getOpacityExitDuration() {
- return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
- }
-
- @Override
- protected Animator createSoftwareExit() {
- final int radiusDuration;
- final int originDuration;
- final int opacityDuration;
-
- radiusDuration = getRadiusDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
-
- final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
- tweenRadius.setAutoCancel(true);
- tweenRadius.setDuration(radiusDuration);
- tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
-
- final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
- tweenOrigin.setAutoCancel(true);
- tweenOrigin.setDuration(originDuration);
- tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
-
+ private void startSoftwareExit() {
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
- opacity.setAutoCancel(true);
- opacity.setDuration(opacityDuration);
+ opacity.setDuration(OPACITY_EXIT_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- final AnimatorSet set = new AnimatorSet();
- set.play(tweenOrigin).with(tweenRadius).with(opacity);
- set.addListener(mAnimationListener);
-
- return set;
+ opacity.addListener(mAnimationListener);
+ opacity.setStartDelay(computeFadeOutDelay());
+ opacity.start();
+ mRunningSwAnimators.add(opacity);
}
- @Override
- protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
- final int radiusDuration;
- final int originDuration;
- final int opacityDuration;
-
- radiusDuration = getRadiusDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
+ private void startHardwareEnter() {
+ if (mForceSoftware) { return; }
+ mPropX = CanvasProperty.createFloat(getCurrentX());
+ mPropY = CanvasProperty.createFloat(getCurrentY());
+ mPropRadius = CanvasProperty.createFloat(getCurrentRadius());
+ final Paint paint = mOwner.getRipplePaint();
+ mPropPaint = CanvasProperty.createPaint(paint);
- final float startX = getCurrentX();
- final float startY = getCurrentY();
- final float startRadius = getCurrentRadius();
-
- p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
-
- mPropPaint = CanvasProperty.createPaint(p);
- mPropRadius = CanvasProperty.createFloat(startRadius);
- mPropX = CanvasProperty.createFloat(startX);
- mPropY = CanvasProperty.createFloat(startY);
+ final int radiusDuration = getRadiusDuration();
final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
radius.setDuration(radiusDuration);
radius.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(radius);
final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
- x.setDuration(originDuration);
+ x.setDuration(radiusDuration);
x.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(x);
final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
- y.setDuration(originDuration);
+ y.setDuration(radiusDuration);
y.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(y);
+
+ final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+ RenderNodeAnimator.PAINT_ALPHA, paint.getAlpha());
+ opacity.setDuration(OPACITY_ENTER_DURATION);
+ opacity.setInterpolator(LINEAR_INTERPOLATOR);
+ opacity.setStartValue(0);
+ mPendingHwAnimators.add(opacity);
+
+ invalidateSelf();
+ }
+
+ private void startHardwareExit() {
+ // Only run a hardware exit if we had a hardware enter to continue from
+ if (mForceSoftware || mPropPaint == null) return;
final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
RenderNodeAnimator.PAINT_ALPHA, 0);
- opacity.setDuration(opacityDuration);
+ opacity.setDuration(OPACITY_EXIT_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
opacity.addListener(mAnimationListener);
+ opacity.setStartDelay(computeFadeOutDelay());
+ mPendingHwAnimators.add(opacity);
+ invalidateSelf();
+ }
+
+ /**
+ * Starts a ripple enter animation.
+ */
+ public final void enter() {
+ mEnterStartedAtMillis = AnimationUtils.currentAnimationTimeMillis();
+ startSoftwareEnter();
+ startHardwareEnter();
+ }
- final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
- set.add(radius);
- set.add(opacity);
- set.add(x);
- set.add(y);
+ /**
+ * Starts a ripple exit animation.
+ */
+ public final void exit() {
+ startSoftwareExit();
+ startHardwareExit();
+ }
- return set;
+ private float getCurrentX() {
+ return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
}
- @Override
- protected void jumpValuesToExit() {
- mOpacity = 0;
- mTweenX = 1;
- mTweenY = 1;
- mTweenRadius = 1;
+ private float getCurrentY() {
+ return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
+ }
+
+ private int getRadiusDuration() {
+ final float remainingRadius = mTargetRadius - getCurrentRadius();
+ return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION *
+ mDensityScale) + 0.5);
+ }
+
+ private float getCurrentRadius() {
+ return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
+ }
+
+ /**
+ * Draws the ripple to the canvas, inheriting the paint's color and alpha
+ * properties.
+ *
+ * @param c the canvas to which the ripple should be drawn
+ * @param p the paint used to draw the ripple
+ */
+ public void draw(Canvas c, Paint p) {
+ final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof DisplayListCanvas;
+
+ pruneSwFinished();
+ if (hasDisplayListCanvas) {
+ final DisplayListCanvas hw = (DisplayListCanvas) c;
+ drawHardware(hw, p);
+ } else {
+ drawSoftware(c, p);
+ }
}
/**
@@ -319,10 +382,39 @@ class RippleForeground extends RippleComponent {
}
}
+ /**
+ * Ends all animations, jumping values to the end state.
+ */
+ public void end() {
+ for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+ mRunningSwAnimators.get(i).end();
+ }
+ mRunningSwAnimators.clear();
+ for (int i = 0; i < mRunningHwAnimators.size(); i++) {
+ mRunningHwAnimators.get(i).end();
+ }
+ mRunningHwAnimators.clear();
+ }
+
+ private void onAnimationPropertyChanged() {
+ if (!mUsingProperties) {
+ invalidateSelf();
+ }
+ }
+
private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mHasFinishedExit = true;
+ pruneHwFinished();
+ pruneSwFinished();
+
+ if (mRunningHwAnimators.isEmpty()) {
+ mPropPaint = null;
+ mPropRadius = null;
+ mPropX = null;
+ mPropY = null;
+ }
}
};
@@ -361,7 +453,7 @@ class RippleForeground extends RippleComponent {
@Override
public void setValue(RippleForeground object, float value) {
object.mTweenRadius = value;
- object.invalidateSelf();
+ object.onAnimationPropertyChanged();
}
@Override
@@ -375,18 +467,18 @@ class RippleForeground extends RippleComponent {
*/
private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
new FloatProperty<RippleForeground>("tweenOrigin") {
- @Override
- public void setValue(RippleForeground object, float value) {
- object.mTweenX = value;
- object.mTweenY = value;
- object.invalidateSelf();
- }
+ @Override
+ public void setValue(RippleForeground object, float value) {
+ object.mTweenX = value;
+ object.mTweenY = value;
+ object.onAnimationPropertyChanged();
+ }
- @Override
- public Float get(RippleForeground object) {
- return object.mTweenX;
- }
- };
+ @Override
+ public Float get(RippleForeground object) {
+ return object.mTweenX;
+ }
+ };
/**
* Property for animating opacity between 0 and its target value.
@@ -396,7 +488,7 @@ class RippleForeground extends RippleComponent {
@Override
public void setValue(RippleForeground object, float value) {
object.mOpacity = value;
- object.invalidateSelf();
+ object.onAnimationPropertyChanged();
}
@Override
diff --git a/android/graphics/drawable/VectorDrawable.java b/android/graphics/drawable/VectorDrawable.java
index ceac3253..7b2e21a4 100644
--- a/android/graphics/drawable/VectorDrawable.java
+++ b/android/graphics/drawable/VectorDrawable.java
@@ -213,12 +213,79 @@ import dalvik.system.VMRuntime;
* &lt;/vector&gt;
* </pre>
* </li>
- * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
+ * <h4>Gradient support</h4>
+ * We support 3 types of gradients: {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ * <p/>
+ * And we support all of 3 types of tile modes {@link android.graphics.Shader.TileMode}:
+ * CLAMP, REPEAT, MIRROR.
+ * <p/>
+ * All of the attributes are listed in {@link android.R.styleable#GradientColor}.
+ * Note that different attributes are relevant for different types of gradient.
+ * <table border="2" align="center" cellpadding="5">
+ * <thead>
+ * <tr>
+ * <th>LinearGradient</th>
+ * <th>RadialGradient</th>
+ * <th>SweepGradient</th>
+ * </tr>
+ * </thead>
+ * <tr>
+ * <td>startColor </td>
+ * <td>startColor</td>
+ * <td>startColor</td>
+ * </tr>
+ * <tr>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * </tr>
+ * <tr>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * </tr>
+ * <tr>
+ * <td>type</td>
+ * <td>type</td>
+ * <td>type</td>
+ * </tr>
+ * <tr>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * </tr>
+ * <tr>
+ * <td>startX</td>
+ * <td>centerX</td>
+ * <td>centerX</td>
+ * </tr>
+ * <tr>
+ * <td>startY</td>
+ * <td>centerY</td>
+ * <td>centerY</td>
+ * </tr>
+ * <tr>
+ * <td>endX</td>
+ * <td>gradientRadius</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>endY</td>
+ * <td></td>
+ * <td></td>
+ * </tr>
+ * </table>
+ * <p/>
+ * Also note that if any color item {@link android.R.styleable#GradientColorItem} is defined, then
+ * startColor, centerColor and endColor will be ignored.
+ * <p/>
* See more details in {@link android.R.styleable#GradientColor} and
* {@link android.R.styleable#GradientColorItem}.
+ * <p/>
+ * Here is a simple example that defines a linear gradient.
* <pre>
* &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
- * android:angle="90"
* android:startColor="?android:attr/colorPrimary"
* android:endColor="?android:attr/colorControlActivated"
* android:centerColor="#f00"
@@ -229,7 +296,18 @@ import dalvik.system.VMRuntime;
* android:type="linear"&gt;
* &lt;/gradient&gt;
* </pre>
- * </li>
+ * And here is a simple example that defines a radial gradient using color items.
+ * <pre>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:centerX="300"
+ * android:centerY="300"
+ * android:gradientRadius="100"
+ * android:type="radial"&gt;
+ * &lt;item android:offset="0.1" android:color="#0ff"/&gt;
+ * &lt;item android:offset="0.4" android:color="#fff"/&gt;
+ * &lt;item android:offset="0.9" android:color="#ff0"/&gt;
+ * &lt;/gradient&gt;
+ * </pre>
*
*/
diff --git a/android/hardware/camera2/CameraCaptureSession.java b/android/hardware/camera2/CameraCaptureSession.java
index da771e48..ff69bd89 100644
--- a/android/hardware/camera2/CameraCaptureSession.java
+++ b/android/hardware/camera2/CameraCaptureSession.java
@@ -249,7 +249,7 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* <p>This function can also be called in case where multiple surfaces share the same
* OutputConfiguration, and one of the surfaces becomes available after the {@link
* CameraCaptureSession} is created. In that case, the application must first create the
- * OutputConfiguration with the available Surface, then enable furture surface sharing via
+ * OutputConfiguration with the available Surface, then enable further surface sharing via
* {@link OutputConfiguration#enableSurfaceSharing}, before creating the CameraCaptureSession.
* After the CameraCaptureSession is created, and once the extra Surface becomes available, the
* application must then call {@link OutputConfiguration#addSurface} before finalizing the
@@ -645,6 +645,44 @@ public abstract class CameraCaptureSession implements AutoCloseable {
public abstract Surface getInputSurface();
/**
+ * Update {@link OutputConfiguration} after configuration finalization see
+ * {@link #finalizeOutputConfigurations}.
+ *
+ * <p>Any {@link OutputConfiguration} that has been modified via calls to
+ * {@link OutputConfiguration#addSurface} or {@link OutputConfiguration#removeSurface} must be
+ * updated. After the update call returns without throwing exceptions any newly added surfaces
+ * can be referenced in subsequent capture requests.</p>
+ *
+ * <p>Surfaces that get removed must not be part of any active repeating or single/burst
+ * request or have any pending results. Consider updating any repeating requests first via
+ * {@link #setRepeatingRequest} or {@link #setRepeatingBurst} and then wait for the last frame
+ * number when the sequence completes {@link CaptureCallback#onCaptureSequenceCompleted}
+ * before calling updateOutputConfiguration to remove a previously active Surface.</p>
+ *
+ * <p>Surfaces that get added must not be part of any other registered
+ * {@link OutputConfiguration}.</p>
+ *
+ * @param config Modified output configuration.
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error.
+ * @throws IllegalArgumentException if an attempt was made to add a {@link Surface} already
+ * in use by another buffer-producing API, such as MediaCodec or
+ * a different camera device or {@link OutputConfiguration}; or
+ * new surfaces are not compatible (see
+ * {@link OutputConfiguration#enableSurfaceSharing}); or a
+ * {@link Surface} that was removed from the modified
+ * {@link OutputConfiguration} still has pending requests.
+ * @throws IllegalStateException if this session is no longer active, either because the session
+ * was explicitly closed, a new session has been created
+ * or the camera device has been closed.
+ */
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
* Close this capture session asynchronously.
*
* <p>Closing a session frees up the target output Surfaces of the session for reuse with either
diff --git a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index c7654c9e..374789c6 100644
--- a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -314,6 +314,20 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession
}
@Override
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ synchronized (mDeviceImpl.mInterfaceLock) {
+ checkNotClosed();
+
+ if (DEBUG) {
+ Log.v(TAG, mIdString + "updateOutputConfiguration");
+ }
+
+ mDeviceImpl.updateOutputConfiguration(config);
+ }
+ }
+
+ @Override
public boolean isReprocessable() {
return mInput != null;
}
diff --git a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index fec7fd97..8c4dbfa5 100644
--- a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -235,6 +235,13 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl
}
@Override
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Constrained high speed session doesn't support"
+ + " this method");
+ }
+
+ @Override
public void close() {
mSessionImpl.close();
}
diff --git a/android/hardware/camera2/impl/CameraDeviceImpl.java b/android/hardware/camera2/impl/CameraDeviceImpl.java
index bfeb14de..6787d84b 100644
--- a/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -764,6 +764,24 @@ public class CameraDeviceImpl extends CameraDevice
}
}
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ synchronized(mInterfaceLock) {
+ int streamId = -1;
+ for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+ if (config.getSurface() == mConfiguredOutputs.valueAt(i).getSurface()) {
+ streamId = mConfiguredOutputs.keyAt(i);
+ break;
+ }
+ }
+ if (streamId == -1) {
+ throw new IllegalArgumentException("Invalid output configuration");
+ }
+
+ mRemoteDevice.updateOutputConfiguration(streamId, config);
+ }
+ }
+
public void tearDown(Surface surface) throws CameraAccessException {
if (surface == null) throw new IllegalArgumentException("Surface is null");
diff --git a/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 27087a2e..0978ff87 100644
--- a/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -215,6 +215,16 @@ public class ICameraDeviceUserWrapper {
}
}
+ public void updateOutputConfiguration(int streamId, OutputConfiguration config)
+ throws CameraAccessException {
+ try {
+ mRemoteDevice.updateOutputConfiguration(streamId, config);
+ } catch (Throwable t) {
+ CameraManager.throwAsPublicException(t);
+ throw new UnsupportedOperationException("Unexpected exception", t);
+ }
+ }
+
public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
throws CameraAccessException {
try {
diff --git a/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 49d4096e..119cca8d 100644
--- a/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -646,6 +646,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
}
@Override
+ public void updateOutputConfiguration(int streamId, OutputConfiguration config) {
+ // TODO: b/63912484 implement updateOutputConfiguration.
+ }
+
+ @Override
public void waitUntilIdle() throws RemoteException {
if (DEBUG) {
Log.d(TAG, "waitUntilIdle called.");
diff --git a/android/hardware/camera2/params/OutputConfiguration.java b/android/hardware/camera2/params/OutputConfiguration.java
index 2b317d67..7409671f 100644
--- a/android/hardware/camera2/params/OutputConfiguration.java
+++ b/android/hardware/camera2/params/OutputConfiguration.java
@@ -42,6 +42,53 @@ import static com.android.internal.util.Preconditions.*;
* A class for describing camera output, which contains a {@link Surface} and its specific
* configuration for creating capture session.
*
+ * <p>There are several ways to instantiate, modify and use OutputConfigurations. The most common
+ * and recommended usage patterns are summarized in the following list:</p>
+ *<ul>
+ * <li>Passing a {@link Surface} to the constructor and using the OutputConfiguration instance as
+ * argument to {@link CameraDevice#createCaptureSessionByOutputConfigurations}. This is the most
+ * frequent usage and clients should consider it first before other more complicated alternatives.
+ * </li>
+ *
+ * <li>Passing only a surface source class as an argument to the constructor. This is usually
+ * followed by a call to create a capture session
+ * (see {@link CameraDevice#createCaptureSessionByOutputConfigurations} and a {@link Surface} add
+ * call {@link #addSurface} with a valid {@link Surface}. The sequence completes with
+ * {@link CameraCaptureSession#finalizeOutputConfigurations}. This is the deferred usage case which
+ * aims to enhance performance by allowing the resource-intensive capture session create call to
+ * execute in parallel with any {@link Surface} initialization, such as waiting for a
+ * {@link android.view.SurfaceView} to be ready as part of the UI initialization.</li>
+ *
+ * <li>The third and most complex usage pattern inlvolves surface sharing. Once instantiated an
+ * OutputConfiguration can be enabled for surface sharing via {@link #enableSurfaceSharing}. This
+ * must be done before creating a new capture session and enables calls to
+ * {@link CameraCaptureSession#updateOutputConfiguration}. An OutputConfiguration with enabled
+ * surface sharing can be modified via {@link #addSurface} or {@link #removeSurface}. The updates
+ * to this OutputConfiguration will only come into effect after
+ * {@link CameraCaptureSession#updateOutputConfiguration} returns without throwing exceptions.
+ * Such updates can be done as long as the session is active. Clients should always consider the
+ * additional requirements and limitations placed on the output surfaces (for more details see
+ * {@link #enableSurfaceSharing}, {@link #addSurface}, {@link #removeSurface},
+ * {@link CameraCaptureSession#updateOutputConfiguration}). A trade-off exists between additional
+ * complexity and flexibility. If exercised correctly surface sharing can switch between different
+ * output surfaces without interrupting any ongoing repeating capture requests. This saves time and
+ * can significantly improve the user experience.</li>
+ *
+ * <li>Surface sharing can be used in combination with deferred surfaces. The rules from both cases
+ * are combined and clients must call {@link #enableSurfaceSharing} before creating a capture
+ * session. Attach and/or remove output surfaces via {@link #addSurface}/{@link #removeSurface} and
+ * finalize the configuration using {@link CameraCaptureSession#finalizeOutputConfigurations}.
+ * {@link CameraCaptureSession#updateOutputConfiguration} can be called after the configuration
+ * finalize method returns without exceptions.</li>
+ *
+ * </ul>
+ *
+ * <p>Please note that surface sharing is currently only enabled for outputs that use the
+ * {@link ImageFormat#PRIVATE} format. This includes surface sources like
+ * {@link android.view.SurfaceView}, {@link android.media.MediaRecorder},
+ * {@link android.graphics.SurfaceTexture} and {@link android.media.ImageReader}, configured using
+ * the aforementioned format.</p>
+ *
* @see CameraDevice#createCaptureSessionByOutputConfigurations
*
*/
@@ -123,7 +170,7 @@ public final class OutputConfiguration implements Parcelable {
* {@link OutputConfiguration#addSurface} should not exceed this value.</p>
*
*/
- private static final int MAX_SURFACES_COUNT = 2;
+ private static final int MAX_SURFACES_COUNT = 4;
/**
* Create a new {@link OutputConfiguration} instance with a {@link Surface},
@@ -280,7 +327,10 @@ public final class OutputConfiguration implements Parcelable {
* <p>For advanced use cases, a camera application may require more streams than the combination
* guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one
* compatible surface can be attached to an OutputConfiguration so that they map to one
- * camera stream, and the outputs share memory buffers when possible. </p>
+ * camera stream, and the outputs share memory buffers when possible. Due to buffer sharing
+ * clients should be careful when adding surface outputs that modify their input data. If such
+ * case exists, camera clients should have an additional mechanism to synchronize read and write
+ * access between individual consumers.</p>
*
* <p>Two surfaces are compatible in the below cases:</p>
*
@@ -301,9 +351,9 @@ public final class OutputConfiguration implements Parcelable {
* CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link
* CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p>
*
- * <p>Up to 2 surfaces can be shared for an OutputConfiguration. The supported surfaces for
- * sharing must be of type SurfaceTexture, SurfaceView, MediaRecorder, MediaCodec, or
- * implementation defined ImageReader.</p>
+ * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration.
+ * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView,
+ * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p>
*/
public void enableSurfaceSharing() {
mIsShared = true;
@@ -329,8 +379,10 @@ public final class OutputConfiguration implements Parcelable {
* <p> This function can be called before or after {@link
* CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after,
* the application must finalize the capture session with
- * {@link CameraCaptureSession#finalizeOutputConfigurations}.
- * </p>
+ * {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method
+ * after the output configurations have been finalized only in cases of enabled surface sharing
+ * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with
+ * {@link CameraCaptureSession#updateOutputConfiguration}.</p>
*
* <p> If the OutputConfiguration was constructed with a deferred surface by {@link
* OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
@@ -388,6 +440,31 @@ public final class OutputConfiguration implements Parcelable {
}
/**
+ * Remove a surface from this OutputConfiguration.
+ *
+ * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
+ * OutputConfiguration. The only notable exception is the surface associated with
+ * the OutputConfigration see {@link #getSurface} which was passed as part of the constructor
+ * or was added first in the deferred case
+ * {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
+ *
+ * @param surface The surface to be removed.
+ *
+ * @throws IllegalArgumentException If the surface is associated with this OutputConfiguration
+ * (see {@link #getSurface}) or the surface didn't get added
+ * with {@link #addSurface}.
+ */
+ public void removeSurface(@NonNull Surface surface) {
+ if (getSurface() == surface) {
+ throw new IllegalArgumentException(
+ "Cannot remove surface associated with this output configuration");
+ }
+ if (!mSurfaces.remove(surface)) {
+ throw new IllegalArgumentException("Surface is not part of this output configuration");
+ }
+ }
+
+ /**
* Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
* instance.
*
@@ -447,6 +524,17 @@ public final class OutputConfiguration implements Parcelable {
}
/**
+ * Get the maximum supported shared {@link Surface} count.
+ *
+ * @return the maximum number of surfaces that can be added per each OutputConfiguration.
+ *
+ * @see #enableSurfaceSharing
+ */
+ public static int getMaxSharedSurfaceCount() {
+ return MAX_SURFACES_COUNT;
+ }
+
+ /**
* Get the {@link Surface} associated with this {@link OutputConfiguration}.
*
* If more than one surface is associated with this {@link OutputConfiguration}, return the
diff --git a/android/hardware/display/BrightnessChangeEvent.java b/android/hardware/display/BrightnessChangeEvent.java
new file mode 100644
index 00000000..fe24e32e
--- /dev/null
+++ b/android/hardware/display/BrightnessChangeEvent.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data about a brightness settings change.
+ * TODO make this SystemAPI
+ * @hide
+ */
+public final class BrightnessChangeEvent implements Parcelable {
+ /** Brightness in nits */
+ public int brightness;
+
+ /** Timestamp of the change {@see System.currentTimeMillis()} */
+ public long timeStamp;
+
+ /** Package name of focused activity when brightness was changed. */
+ public String packageName;
+
+ /** User id of of the user running when brightness was changed.
+ * @hide */
+ public int userId;
+
+ /** Lux values of recent sensor data */
+ public float[] luxValues;
+
+ /** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */
+ public long[] luxTimestamps;
+
+ /** Most recent battery level when brightness was changed or Float.NaN */
+ public float batteryLevel;
+
+ /** Color filter active to provide night mode */
+ public boolean nightMode;
+
+ /** If night mode color filter is active this will be the temperature in kelvin */
+ public int colorTemperature;
+
+ /** Brightness level before slider adjustment */
+ public int lastBrightness;
+
+ public BrightnessChangeEvent() {
+ }
+
+ private BrightnessChangeEvent(Parcel source) {
+ brightness = source.readInt();
+ timeStamp = source.readLong();
+ packageName = source.readString();
+ userId = source.readInt();
+ luxValues = source.createFloatArray();
+ luxTimestamps = source.createLongArray();
+ batteryLevel = source.readFloat();
+ nightMode = source.readBoolean();
+ colorTemperature = source.readInt();
+ lastBrightness = source.readInt();
+ }
+
+ public static final Creator<BrightnessChangeEvent> CREATOR =
+ new Creator<BrightnessChangeEvent>() {
+ public BrightnessChangeEvent createFromParcel(Parcel source) {
+ return new BrightnessChangeEvent(source);
+ }
+ public BrightnessChangeEvent[] newArray(int size) {
+ return new BrightnessChangeEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(brightness);
+ dest.writeLong(timeStamp);
+ dest.writeString(packageName);
+ dest.writeInt(userId);
+ dest.writeFloatArray(luxValues);
+ dest.writeLongArray(luxTimestamps);
+ dest.writeFloat(batteryLevel);
+ dest.writeBoolean(nightMode);
+ dest.writeInt(colorTemperature);
+ dest.writeInt(lastBrightness);
+ }
+}
diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java
index b2af44ec..ef77d6e6 100644
--- a/android/hardware/display/DisplayManager.java
+++ b/android/hardware/display/DisplayManager.java
@@ -30,6 +30,7 @@ import android.view.Surface;
import android.view.WindowManagerPolicy;
import java.util.ArrayList;
+import java.util.List;
/**
* Manages the properties of attached displays.
@@ -615,6 +616,21 @@ public final class DisplayManager {
}
/**
+ * Fetch {@link BrightnessChangeEvent}s.
+ * @hide until we make it a system api.
+ */
+ public List<BrightnessChangeEvent> getBrightnessEvents() {
+ return mGlobal.getBrightnessEvents();
+ }
+
+ /**
+ * @hide STOPSHIP - remove when adaptive brightness accepts curves.
+ */
+ public void setBrightness(int brightness) {
+ mGlobal.setBrightness(brightness);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/android/hardware/display/DisplayManagerGlobal.java b/android/hardware/display/DisplayManagerGlobal.java
index a8a4eb67..d93d0e4e 100644
--- a/android/hardware/display/DisplayManagerGlobal.java
+++ b/android/hardware/display/DisplayManagerGlobal.java
@@ -17,6 +17,7 @@
package android.hardware.display;
import android.content.Context;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.display.DisplayManager.DisplayListener;
@@ -37,6 +38,8 @@ import android.view.DisplayInfo;
import android.view.Surface;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Manager communication with the display manager service on behalf of
@@ -456,6 +459,33 @@ public final class DisplayManagerGlobal {
}
}
+ /**
+ * Retrieves brightness change events.
+ */
+ public List<BrightnessChangeEvent> getBrightnessEvents() {
+ try {
+ ParceledListSlice<BrightnessChangeEvent> events = mDm.getBrightnessEvents();
+ if (events == null) {
+ return Collections.emptyList();
+ }
+ return events.getList();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set brightness but don't add a BrightnessChangeEvent
+ * STOPSHIP remove when adaptive brightness accepts curves.
+ */
+ public void setBrightness(int brightness) {
+ try {
+ mDm.setBrightness(brightness);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, int event) {
diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java
index 7cbb436c..24117278 100644
--- a/android/hardware/location/ContextHubManager.java
+++ b/android/hardware/location/ContextHubManager.java
@@ -271,6 +271,59 @@ public final class ContextHubManager {
throw new UnsupportedOperationException("TODO: Implement this");
}
+ /*
+ * Helper function to generate a stub for a non-query transaction callback.
+ *
+ * @param transaction the transaction to unblock when complete
+ *
+ * @return the callback
+ *
+ * @hide
+ */
+ private IContextHubTransactionCallback createTransactionCallback(
+ ContextHubTransaction<Void> transaction) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+ Log.e(TAG, "Received a query callback on a non-query request");
+ transaction.setResponse(new ContextHubTransaction.Response<Void>(
+ ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ }
+
+ @Override
+ public void onTransactionComplete(int result) {
+ transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+ }
+ };
+ }
+
+ /*
+ * Helper function to generate a stub for a query transaction callback.
+ *
+ * @param transaction the transaction to unblock when complete
+ *
+ * @return the callback
+ *
+ * @hide
+ */
+ private IContextHubTransactionCallback createQueryCallback(
+ ContextHubTransaction<List<NanoAppState>> transaction) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+ transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+ result, nanoappList));
+ }
+
+ @Override
+ public void onTransactionComplete(int result) {
+ Log.e(TAG, "Received a non-query callback on a query request");
+ transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+ ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ }
+ };
+ }
+
/**
* Loads a nanoapp at the specified Context Hub.
*
@@ -411,7 +464,7 @@ public final class ContextHubManager {
*
* @param callback the notification callback to register
* @param hubInfo the hub to attach this client to
- * @param handler the handler to invoke the callback, if null uses the current thread Looper
+ * @param handler the handler to invoke the callback, if null uses the main thread's Looper
*
* @return the registered client object
*
diff --git a/android/hardware/location/ContextHubTransaction.java b/android/hardware/location/ContextHubTransaction.java
index 4877d38b..a8569ef4 100644
--- a/android/hardware/location/ContextHubTransaction.java
+++ b/android/hardware/location/ContextHubTransaction.java
@@ -16,11 +16,16 @@
package android.hardware.location;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* A class describing a request sent to the Context Hub Service.
@@ -29,17 +34,15 @@ import java.util.concurrent.TimeUnit;
* through the ContextHubManager APIs. The caller can either retrieve the result
* synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
* asynchronously through a user-defined callback
- * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}).
- *
- * A transaction can be invalidated if the caller of the transaction is no longer active
- * and the reference to this object is lost, or if timeout period has passed in
- * {@link #waitForResponse(long, TimeUnit)}.
+ * ({@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}).
*
* @param <T> the type of the contents in the transaction response
*
* @hide
*/
public class ContextHubTransaction<T> {
+ private static final String TAG = "ContextHubTransaction";
+
/**
* Constants describing the type of a transaction through the Context Hub Service.
*/
@@ -68,7 +71,8 @@ public class ContextHubTransaction<T> {
TRANSACTION_FAILED_UNINITIALIZED,
TRANSACTION_FAILED_PENDING,
TRANSACTION_FAILED_AT_HUB,
- TRANSACTION_FAILED_TIMEOUT})
+ TRANSACTION_FAILED_TIMEOUT,
+ TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE})
public @interface Result {}
public static final int TRANSACTION_SUCCESS = 0;
/**
@@ -95,6 +99,10 @@ public class ContextHubTransaction<T> {
* Failure mode when the transaction has timed out.
*/
public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+ /**
+ * Failure mode when the transaction has failed internally at the service.
+ */
+ public static final int TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE = 7;
/**
* A class describing the response for a ContextHubTransaction.
@@ -146,11 +154,6 @@ public class ContextHubTransaction<T> {
}
/*
- * The unique identifier representing the transaction.
- */
- private int mTransactionId;
-
- /*
* The type of the transaction.
*/
@Type
@@ -171,8 +174,17 @@ public class ContextHubTransaction<T> {
*/
private ContextHubTransaction.Callback<T> mCallback = null;
- ContextHubTransaction(int id, @Type int type) {
- mTransactionId = id;
+ /*
+ * Synchronization latch used to block on response.
+ */
+ private final CountDownLatch mDoneSignal = new CountDownLatch(1);
+
+ /*
+ * true if the response has been set throught setResponse, false otherwise.
+ */
+ private boolean mIsResponseSet = false;
+
+ ContextHubTransaction(@Type int type) {
mTransactionType = type;
}
@@ -191,17 +203,26 @@ public class ContextHubTransaction<T> {
* for the transaction represented by this object by the Context Hub, or a
* specified timeout period has elapsed.
*
- * If the specified timeout has passed, the transaction represented by this object
- * is invalidated by the Context Hub Service (resulting in a timeout failure in the
- * response).
+ * If the specified timeout has passed, a TimeoutException will be thrown and the caller may
+ * retry the invocation of this method at a later time.
*
* @param timeout the timeout duration
* @param unit the unit of the timeout
*
* @return the transaction response
+ *
+ * @throws InterruptedException if the current thread is interrupted while waiting for response
+ * @throws TimeoutException if the timeout period has passed
*/
- public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) {
- throw new UnsupportedOperationException("TODO: Implement this");
+ public ContextHubTransaction.Response<T> waitForResponse(
+ long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ boolean success = mDoneSignal.await(timeout, unit);
+
+ if (!success) {
+ throw new TimeoutException("Timed out while waiting for transaction");
+ }
+
+ return mResponse;
}
/**
@@ -215,15 +236,100 @@ public class ContextHubTransaction<T> {
* will be immediately posted by the handler. If the transaction has been invalidated,
* the callback will never be invoked.
*
+ * A transaction can be invalidated if the process owning the transaction is no longer active
+ * and the reference to this object is lost.
+ *
+ * This method or {@link #setCallbackOnCompletecan(ContextHubTransaction.Callback)} can only be
+ * invoked once, or an IllegalStateException will be thrown.
+ *
* @param callback the callback to be invoked upon completion
* @param handler the handler to post the callback
+ *
+ * @throws IllegalStateException if this method is called multiple times
+ * @throws NullPointerException if the callback or handler is null
*/
- public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
- throw new UnsupportedOperationException("TODO: Implement this");
+ public void setCallbackOnComplete(
+ @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
+ synchronized (this) {
+ if (callback == null) {
+ throw new NullPointerException("Callback cannot be null");
+ }
+ if (handler == null) {
+ throw new NullPointerException("Handler cannot be null");
+ }
+ if (mCallback != null) {
+ throw new IllegalStateException(
+ "Cannot set ContextHubTransaction callback multiple times");
+ }
+
+ mCallback = callback;
+ mHandler = handler;
+
+ if (mDoneSignal.getCount() == 0) {
+ boolean callbackPosted = mHandler.post(() -> {
+ mCallback.onComplete(this, mResponse);
+ });
+
+ if (!callbackPosted) {
+ Log.e(TAG, "Failed to post callback to Handler");
+ }
+ }
+ }
}
- private void setResponse(ContextHubTransaction.Response<T> response) {
- mResponse = response;
- throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+ /**
+ * Sets a callback to be invoked when the transaction completes.
+ *
+ * Equivalent to {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * with the handler being that of the main thread's Looper.
+ *
+ * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * can only be invoked once, or an IllegalStateException will be thrown.
+ *
+ * @param callback the callback to be invoked upon completion
+ *
+ * @throws IllegalStateException if this method is called multiple times
+ * @throws NullPointerException if the callback is null
+ */
+ public void setCallbackOnComplete(@NonNull ContextHubTransaction.Callback<T> callback) {
+ setCallbackOnComplete(callback, new Handler(Looper.getMainLooper()));
+ }
+
+ /**
+ * Sets the response of the transaction.
+ *
+ * This method should only be invoked by ContextHubManager as a result of a callback from
+ * the Context Hub Service indicating the response from a transaction. This method should not be
+ * invoked more than once.
+ *
+ * @param response the response to set
+ *
+ * @throws IllegalStateException if this method is invoked multiple times
+ * @throws NullPointerException if the response is null
+ */
+ void setResponse(ContextHubTransaction.Response<T> response) {
+ synchronized (this) {
+ if (response == null) {
+ throw new NullPointerException("Response cannot be null");
+ }
+ if (mIsResponseSet) {
+ throw new IllegalStateException(
+ "Cannot set response of ContextHubTransaction multiple times");
+ }
+
+ mResponse = response;
+ mIsResponseSet = true;
+
+ mDoneSignal.countDown();
+ if (mCallback != null) {
+ boolean callbackPosted = mHandler.post(() -> {
+ mCallback.onComplete(this, mResponse);
+ });
+
+ if (!callbackPosted) {
+ Log.e(TAG, "Failed to post callback to Handler");
+ }
+ }
+ }
}
}
diff --git a/android/hardware/location/NanoAppBinary.java b/android/hardware/location/NanoAppBinary.java
index 54542277..934e9e48 100644
--- a/android/hardware/location/NanoAppBinary.java
+++ b/android/hardware/location/NanoAppBinary.java
@@ -22,6 +22,7 @@ import android.util.Log;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Arrays;
/**
* @hide
@@ -57,7 +58,7 @@ public final class NanoAppBinary implements Parcelable {
private static final int EXPECTED_HEADER_VERSION = 1;
/*
- * The magic value expected in the header.
+ * The magic value expected in the header as defined in context_hub.h.
*/
private static final int EXPECTED_MAGIC_VALUE =
(((int) 'N' << 0) | ((int) 'A' << 8) | ((int) 'N' << 16) | ((int) 'O' << 24));
@@ -67,6 +68,17 @@ public final class NanoAppBinary implements Parcelable {
*/
private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
+ /*
+ * The size of the header in bytes as defined in context_hub.h.
+ */
+ private static final int HEADER_SIZE_BYTES = 40;
+
+ /*
+ * The bit fields for mFlags as defined in context_hub.h.
+ */
+ private static final int NANOAPP_SIGNED_FLAG_BIT = 0x1;
+ private static final int NANOAPP_ENCRYPTED_FLAG_BIT = 0x2;
+
public NanoAppBinary(byte[] appBinary) {
mNanoAppBinary = appBinary;
parseBinaryHeader();
@@ -111,11 +123,26 @@ public final class NanoAppBinary implements Parcelable {
/**
* @return the app binary byte array
*/
- public byte[] getNanoAppBinary() {
+ public byte[] getBinary() {
return mNanoAppBinary;
}
/**
+ * @return the app binary byte array without the leading header
+ *
+ * @throws IndexOutOfBoundsException if the nanoapp binary size is smaller than the header size
+ * @throws NullPointerException if the nanoapp binary is null
+ */
+ public byte[] getBinaryNoHeader() {
+ if (mNanoAppBinary.length < HEADER_SIZE_BYTES) {
+ throw new IndexOutOfBoundsException("NanoAppBinary binary byte size ("
+ + mNanoAppBinary.length + ") is less than header size (" + HEADER_SIZE_BYTES + ")");
+ }
+
+ return Arrays.copyOfRange(mNanoAppBinary, HEADER_SIZE_BYTES, mNanoAppBinary.length);
+ }
+
+ /**
* @return {@code true} if the header is valid, {@code false} otherwise
*/
public boolean hasValidHeader() {
@@ -164,6 +191,31 @@ public final class NanoAppBinary implements Parcelable {
return mTargetChreApiMinorVersion;
}
+ /**
+ * Returns the flags for the nanoapp as defined in context_hub.h.
+ *
+ * This method is meant to be used by the Context Hub Service.
+ *
+ * @return the flags for the nanoapp
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * @return {@code true} if the nanoapp binary is signed, {@code false} otherwise
+ */
+ public boolean isSigned() {
+ return (mFlags & NANOAPP_SIGNED_FLAG_BIT) != 0;
+ }
+
+ /**
+ * @return {@code true} if the nanoapp binary is encrypted, {@code false} otherwise
+ */
+ public boolean isEncrypted() {
+ return (mFlags & NANOAPP_ENCRYPTED_FLAG_BIT) != 0;
+ }
+
private NanoAppBinary(Parcel in) {
int binaryLength = in.readInt();
mNanoAppBinary = new byte[binaryLength];
diff --git a/android/hardware/sidekick/SidekickInternal.java b/android/hardware/sidekick/SidekickInternal.java
new file mode 100644
index 00000000..fe3550b2
--- /dev/null
+++ b/android/hardware/sidekick/SidekickInternal.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.sidekick;
+
+
+/**
+ * Sidekick local system service interface.
+ *
+ * @hide Only for use within the system server, and maybe by Clockwork Home.
+ */
+public abstract class SidekickInternal {
+
+ /**
+ * Tell Sidekick to reset back to newly-powered-on state.
+ *
+ * @return true on success (Sidekick is reset), false if Sidekick is not
+ * available (failed or not present). Either way, upon return Sidekick is
+ * guaranteed not to be controlling the display.
+ */
+ public abstract boolean reset();
+
+ /**
+ * Tell Sidekick it can start controlling the display.
+ *
+ * SidekickServer may choose not to actually control the display, if it's been told
+ * via other channels to leave the previous image on the display (same as SUSPEND in
+ * a non-Sidekick system).
+ *
+ * @param displayState - one of Display.STATE_DOZE_SUSPEND, Display.STATE_ON_SUSPEND
+ * @return true on success, false on failure (no sidekick available)
+ */
+ public abstract boolean startDisplayControl(int displayState);
+
+ /**
+ * Tell Sidekick it must stop controlling the display.
+ *
+ * No return code because this must always succeed - after return, Sidekick
+ * is guaranteed to not be controlling the display.
+ */
+ public abstract void endDisplayControl();
+
+}
diff --git a/android/hardware/usb/AccessoryFilter.java b/android/hardware/usb/AccessoryFilter.java
new file mode 100644
index 00000000..d9b7c5be
--- /dev/null
+++ b/android/hardware/usb/AccessoryFilter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB accessory.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class AccessoryFilter {
+ // USB accessory manufacturer (or null for unspecified)
+ public final String mManufacturer;
+ // USB accessory model (or null for unspecified)
+ public final String mModel;
+ // USB accessory version (or null for unspecified)
+ public final String mVersion;
+
+ public AccessoryFilter(String manufacturer, String model, String version) {
+ mManufacturer = manufacturer;
+ mModel = model;
+ mVersion = version;
+ }
+
+ public AccessoryFilter(UsbAccessory accessory) {
+ mManufacturer = accessory.getManufacturer();
+ mModel = accessory.getModel();
+ mVersion = accessory.getVersion();
+ }
+
+ public static AccessoryFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String manufacturer = null;
+ String model = null;
+ String version = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+
+ if ("manufacturer".equals(name)) {
+ manufacturer = value;
+ } else if ("model".equals(name)) {
+ model = value;
+ } else if ("version".equals(name)) {
+ version = value;
+ }
+ }
+ return new AccessoryFilter(manufacturer, model, version);
+ }
+
+ public void write(XmlSerializer serializer)throws IOException {
+ serializer.startTag(null, "usb-accessory");
+ if (mManufacturer != null) {
+ serializer.attribute(null, "manufacturer", mManufacturer);
+ }
+ if (mModel != null) {
+ serializer.attribute(null, "model", mModel);
+ }
+ if (mVersion != null) {
+ serializer.attribute(null, "version", mVersion);
+ }
+ serializer.endTag(null, "usb-accessory");
+ }
+
+ public boolean matches(UsbAccessory acc) {
+ if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
+ if (mModel != null && !acc.getModel().equals(mModel)) return false;
+ return !(mVersion != null && !acc.getVersion().equals(mVersion));
+ }
+
+ /**
+ * Is the accessories described {@code accessory} covered by this filter?
+ *
+ * @param accessory A filter describing the accessory
+ *
+ * @return {@code true} iff this the filter covers the accessory
+ */
+ public boolean contains(AccessoryFilter accessory) {
+ if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) {
+ return false;
+ }
+ if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false;
+ return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mManufacturer == null || mModel == null || mVersion == null) {
+ return false;
+ }
+ if (obj instanceof AccessoryFilter) {
+ AccessoryFilter filter = (AccessoryFilter)obj;
+ return (mManufacturer.equals(filter.mManufacturer) &&
+ mModel.equals(filter.mModel) &&
+ mVersion.equals(filter.mVersion));
+ }
+ if (obj instanceof UsbAccessory) {
+ UsbAccessory accessory = (UsbAccessory)obj;
+ return (mManufacturer.equals(accessory.getManufacturer()) &&
+ mModel.equals(accessory.getModel()) &&
+ mVersion.equals(accessory.getVersion()));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
+ (mModel == null ? 0 : mModel.hashCode()) ^
+ (mVersion == null ? 0 : mVersion.hashCode()));
+ }
+
+ @Override
+ public String toString() {
+ return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
+ "\", mModel=\"" + mModel +
+ "\", mVersion=\"" + mVersion + "\"]";
+ }
+}
diff --git a/android/hardware/usb/DeviceFilter.java b/android/hardware/usb/DeviceFilter.java
new file mode 100644
index 00000000..439c6297
--- /dev/null
+++ b/android/hardware/usb/DeviceFilter.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import android.util.Slog;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB device.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class DeviceFilter {
+ private static final String TAG = DeviceFilter.class.getSimpleName();
+
+ // USB Vendor ID (or -1 for unspecified)
+ public final int mVendorId;
+ // USB Product ID (or -1 for unspecified)
+ public final int mProductId;
+ // USB device or interface class (or -1 for unspecified)
+ public final int mClass;
+ // USB device subclass (or -1 for unspecified)
+ public final int mSubclass;
+ // USB device protocol (or -1 for unspecified)
+ public final int mProtocol;
+ // USB device manufacturer name string (or null for unspecified)
+ public final String mManufacturerName;
+ // USB device product name string (or null for unspecified)
+ public final String mProductName;
+ // USB device serial number string (or null for unspecified)
+ public final String mSerialNumber;
+
+ public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+ String manufacturer, String product, String serialnum) {
+ mVendorId = vid;
+ mProductId = pid;
+ mClass = clasz;
+ mSubclass = subclass;
+ mProtocol = protocol;
+ mManufacturerName = manufacturer;
+ mProductName = product;
+ mSerialNumber = serialnum;
+ }
+
+ public DeviceFilter(UsbDevice device) {
+ mVendorId = device.getVendorId();
+ mProductId = device.getProductId();
+ mClass = device.getDeviceClass();
+ mSubclass = device.getDeviceSubclass();
+ mProtocol = device.getDeviceProtocol();
+ mManufacturerName = device.getManufacturerName();
+ mProductName = device.getProductName();
+ mSerialNumber = device.getSerialNumber();
+ }
+
+ public static DeviceFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int vendorId = -1;
+ int productId = -1;
+ int deviceClass = -1;
+ int deviceSubclass = -1;
+ int deviceProtocol = -1;
+ String manufacturerName = null;
+ String productName = null;
+ String serialNumber = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ // Attribute values are ints or strings
+ if ("manufacturer-name".equals(name)) {
+ manufacturerName = value;
+ } else if ("product-name".equals(name)) {
+ productName = value;
+ } else if ("serial-number".equals(name)) {
+ serialNumber = value;
+ } else {
+ int intValue;
+ int radix = 10;
+ if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
+ (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+ // allow hex values starting with 0x or 0X
+ radix = 16;
+ value = value.substring(2);
+ }
+ try {
+ intValue = Integer.parseInt(value, radix);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "invalid number for field " + name, e);
+ continue;
+ }
+ if ("vendor-id".equals(name)) {
+ vendorId = intValue;
+ } else if ("product-id".equals(name)) {
+ productId = intValue;
+ } else if ("class".equals(name)) {
+ deviceClass = intValue;
+ } else if ("subclass".equals(name)) {
+ deviceSubclass = intValue;
+ } else if ("protocol".equals(name)) {
+ deviceProtocol = intValue;
+ }
+ }
+ }
+ return new DeviceFilter(vendorId, productId,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, serialNumber);
+ }
+
+ public void write(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, "usb-device");
+ if (mVendorId != -1) {
+ serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
+ }
+ if (mProductId != -1) {
+ serializer.attribute(null, "product-id", Integer.toString(mProductId));
+ }
+ if (mClass != -1) {
+ serializer.attribute(null, "class", Integer.toString(mClass));
+ }
+ if (mSubclass != -1) {
+ serializer.attribute(null, "subclass", Integer.toString(mSubclass));
+ }
+ if (mProtocol != -1) {
+ serializer.attribute(null, "protocol", Integer.toString(mProtocol));
+ }
+ if (mManufacturerName != null) {
+ serializer.attribute(null, "manufacturer-name", mManufacturerName);
+ }
+ if (mProductName != null) {
+ serializer.attribute(null, "product-name", mProductName);
+ }
+ if (mSerialNumber != null) {
+ serializer.attribute(null, "serial-number", mSerialNumber);
+ }
+ serializer.endTag(null, "usb-device");
+ }
+
+ private boolean matches(int clasz, int subclass, int protocol) {
+ return ((mClass == -1 || clasz == mClass) &&
+ (mSubclass == -1 || subclass == mSubclass) &&
+ (mProtocol == -1 || protocol == mProtocol));
+ }
+
+ public boolean matches(UsbDevice device) {
+ if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
+ if (mProductId != -1 && device.getProductId() != mProductId) return false;
+ if (mManufacturerName != null && device.getManufacturerName() == null) return false;
+ if (mProductName != null && device.getProductName() == null) return false;
+ if (mSerialNumber != null && device.getSerialNumber() == null) return false;
+ if (mManufacturerName != null && device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) return false;
+ if (mProductName != null && device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) return false;
+ if (mSerialNumber != null && device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber())) return false;
+
+ // check device class/subclass/protocol
+ if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+ device.getDeviceProtocol())) return true;
+
+ // if device doesn't match, check the interfaces
+ int count = device.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ UsbInterface intf = device.getInterface(i);
+ if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+ intf.getInterfaceProtocol())) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the device described by {@code device} covered by this filter?
+ *
+ * @param device The device
+ *
+ * @return {@code true} iff this filter covers the {@code device}
+ */
+ public boolean contains(DeviceFilter device) {
+ // -1 and null means "match anything"
+
+ if (mVendorId != -1 && device.mVendorId != mVendorId) return false;
+ if (mProductId != -1 && device.mProductId != mProductId) return false;
+ if (mManufacturerName != null && !Objects.equals(mManufacturerName,
+ device.mManufacturerName)) {
+ return false;
+ }
+ if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) {
+ return false;
+ }
+ if (mSerialNumber != null
+ && !Objects.equals(mSerialNumber, device.mSerialNumber)) {
+ return false;
+ }
+
+ // check device class/subclass/protocol
+ return matches(device.mClass, device.mSubclass, device.mProtocol);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mVendorId == -1 || mProductId == -1 ||
+ mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+ return false;
+ }
+ if (obj instanceof DeviceFilter) {
+ DeviceFilter filter = (DeviceFilter)obj;
+
+ if (filter.mVendorId != mVendorId ||
+ filter.mProductId != mProductId ||
+ filter.mClass != mClass ||
+ filter.mSubclass != mSubclass ||
+ filter.mProtocol != mProtocol) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName == null) ||
+ (filter.mManufacturerName == null &&
+ mManufacturerName != null) ||
+ (filter.mProductName != null &&
+ mProductName == null) ||
+ (filter.mProductName == null &&
+ mProductName != null) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber == null) ||
+ (filter.mSerialNumber == null &&
+ mSerialNumber != null)) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName != null &&
+ !mManufacturerName.equals(filter.mManufacturerName)) ||
+ (filter.mProductName != null &&
+ mProductName != null &&
+ !mProductName.equals(filter.mProductName)) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber != null &&
+ !mSerialNumber.equals(filter.mSerialNumber))) {
+ return false;
+ }
+ return true;
+ }
+ if (obj instanceof UsbDevice) {
+ UsbDevice device = (UsbDevice)obj;
+ if (device.getVendorId() != mVendorId ||
+ device.getProductId() != mProductId ||
+ device.getDeviceClass() != mClass ||
+ device.getDeviceSubclass() != mSubclass ||
+ device.getDeviceProtocol() != mProtocol) {
+ return(false);
+ }
+ if ((mManufacturerName != null && device.getManufacturerName() == null) ||
+ (mManufacturerName == null && device.getManufacturerName() != null) ||
+ (mProductName != null && device.getProductName() == null) ||
+ (mProductName == null && device.getProductName() != null) ||
+ (mSerialNumber != null && device.getSerialNumber() == null) ||
+ (mSerialNumber == null && device.getSerialNumber() != null)) {
+ return(false);
+ }
+ if ((device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) ||
+ (device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) ||
+ (device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber()))) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (((mVendorId << 16) | mProductId) ^
+ ((mClass << 16) | (mSubclass << 8) | mProtocol));
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
+ ",mClass=" + mClass + ",mSubclass=" + mSubclass +
+ ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
+ ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
+ "]";
+ }
+}
diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java
index 996824d4..6ce96698 100644
--- a/android/hardware/usb/UsbManager.java
+++ b/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@ public class UsbManager {
"android.hardware.usb.action.USB_PORT_CHANGED";
/**
- * Activity intent sent when a USB device is attached.
+ * Activity intent sent when user attaches a USB device.
*
* This intent is sent when a USB device is attached to the USB bus when in host mode.
* <ul>
@@ -128,7 +128,7 @@ public class UsbManager {
"android.hardware.usb.action.USB_DEVICE_DETACHED";
/**
- * Activity intent sent when a USB accessory is attached.
+ * Activity intent sent when user attaches a USB accessory.
*
* <ul>
* <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
diff --git a/android/location/Location.java b/android/location/Location.java
index e7f903e8..c9d2f7f8 100644
--- a/android/location/Location.java
+++ b/android/location/Location.java
@@ -821,7 +821,7 @@ public class Location implements Parcelable {
* considered 1 standard deviation.
*
* <p>For example, if {@link #getAltitude()} returns 150, and
- * {@link #getVerticalAccuracyMeters()} ()} returns 20 then there is a 68% probability
+ * {@link #getVerticalAccuracyMeters()} returns 20 then there is a 68% probability
* of the true altitude being between 130 and 170 meters.
*
* <p>If this location does not have a vertical accuracy, then 0.0 is returned.
@@ -933,7 +933,7 @@ public class Location implements Parcelable {
* considered 1 standard deviation.
*
* <p>For example, if {@link #getBearing()} returns 60, and
- * {@link #getBearingAccuracyDegrees()} ()} returns 10, then there is a 68% probability of the
+ * {@link #getBearingAccuracyDegrees()} returns 10, then there is a 68% probability of the
* true bearing being between 50 and 70 degrees.
*
* <p>If this location does not have a bearing accuracy, then 0.0 is returned.
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 20405d3b..7afe267f 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -244,6 +244,7 @@ public final class AudioAttributes implements Parcelable {
SUPPRESSIBLE_USAGES.put(USAGE_GAME, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
}
/**
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index dab7632a..58976ca0 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -43,17 +43,16 @@ import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Pair;
+import android.util.Slog;
import android.view.KeyEvent;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -1966,9 +1965,28 @@ public class AudioManager {
*/
private boolean querySoundEffectsEnabled(int user) {
return Settings.System.getIntForUser(getContext().getContentResolver(),
- Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
+ Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0
+ && !areSystemSoundsZenModeBlocked(getContext());
}
+ private boolean areSystemSoundsZenModeBlocked(Context context) {
+ int zenMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ZEN_MODE, 0);
+
+ switch (zenMode) {
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return true;
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ final NotificationManager noMan = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ return (noMan.getNotificationPolicy().priorityCategories
+ & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+ case Settings.Global.ZEN_MODE_OFF:
+ default:
+ return false;
+ }
+ }
/**
* Load Sound effects.
diff --git a/android/media/BufferingParams.java b/android/media/BufferingParams.java
index 681271b1..521e8975 100644
--- a/android/media/BufferingParams.java
+++ b/android/media/BufferingParams.java
@@ -26,170 +26,68 @@ import java.lang.annotation.RetentionPolicy;
/**
* Structure for source buffering management params.
*
- * Used by {@link MediaPlayer#getDefaultBufferingParams()},
- * {@link MediaPlayer#getBufferingParams()} and
+ * Used by {@link MediaPlayer#getBufferingParams()} and
* {@link MediaPlayer#setBufferingParams(BufferingParams)}
* to control source buffering behavior.
*
* <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
* (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
- * is playing back source). {@link BufferingParams} includes mode and corresponding
- * watermarks for each stage of source buffering. The watermarks could be either size
- * based (in milliseconds), or time based (in kilobytes) or both, depending on the mode.
+ * is playing back source). {@link BufferingParams} includes corresponding marks for each
+ * stage of source buffering. The marks are time based (in milliseconds).
*
- * <p>There are 4 buffering modes: {@link #BUFFERING_MODE_NONE},
- * {@link #BUFFERING_MODE_TIME_ONLY}, {@link #BUFFERING_MODE_SIZE_ONLY} and
- * {@link #BUFFERING_MODE_TIME_THEN_SIZE}.
- * {@link MediaPlayer} source component has default buffering modes which can be queried
- * by calling {@link MediaPlayer#getDefaultBufferingParams()}.
- * Users should always use those default modes or their downsized version when trying to
- * change buffering params. For example, {@link #BUFFERING_MODE_TIME_THEN_SIZE} can be
- * downsized to {@link #BUFFERING_MODE_NONE}, {@link #BUFFERING_MODE_TIME_ONLY} or
- * {@link #BUFFERING_MODE_SIZE_ONLY}. But {@link #BUFFERING_MODE_TIME_ONLY} can not be
- * downsized to {@link #BUFFERING_MODE_SIZE_ONLY}.
+ * <p>{@link MediaPlayer} source component has default marks which can be queried by
+ * calling {@link MediaPlayer#getBufferingParams()} before any change is made by
+ * {@link MediaPlayer#setBufferingParams()}.
* <ul>
- * <li><strong>initial buffering stage:</strong> has one watermark which is used when
- * {@link MediaPlayer} is being prepared. When cached data amount exceeds this watermark,
- * {@link MediaPlayer} is prepared.</li>
- * <li><strong>rebuffering stage:</strong> has two watermarks, low and high, which are
- * used when {@link MediaPlayer} is playing back content.
+ * <li><strong>initial buffering:</strong> initialMarkMs is used when
+ * {@link MediaPlayer} is being prepared. When cached data amount exceeds this mark
+ * {@link MediaPlayer} is prepared. </li>
+ * <li><strong>rebuffering during playback:</strong> resumePlaybackMarkMs is used when
+ * {@link MediaPlayer} is playing back content.
* <ul>
- * <li> When cached data amount exceeds high watermark, {@link MediaPlayer} will pause
- * buffering. Buffering will resume when cache runs below some limit which could be low
- * watermark or some intermediate value decided by the source component.</li>
- * <li> When cached data amount runs below low watermark, {@link MediaPlayer} will paused
- * playback. Playback will resume when cached data amount exceeds high watermark
- * or reaches end of stream.</li>
- * </ul>
+ * <li> {@link MediaPlayer} has internal mark, namely pausePlaybackMarkMs, to decide when
+ * to pause playback if cached data amount runs low. This internal mark varies based on
+ * type of data source. </li>
+ * <li> When cached data amount exceeds resumePlaybackMarkMs, {@link MediaPlayer} will
+ * resume playback if it has been paused due to low cached data amount. The internal mark
+ * pausePlaybackMarkMs shall be less than resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely pauseRebufferingMarkMs, to decide
+ * when to pause rebuffering. Apparently, this internal mark shall be no less than
+ * resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely resumeRebufferingMarkMs, to decide
+ * when to resume buffering. This internal mark varies based on type of data source. This
+ * mark shall be larger than pausePlaybackMarkMs, and less than pauseRebufferingMarkMs.
+ * </li>
+ * </ul> </li>
* </ul>
* <p>Users should use {@link Builder} to change {@link BufferingParams}.
* @hide
*/
public final class BufferingParams implements Parcelable {
- /**
- * This mode indicates that source buffering is not supported.
- */
- public static final int BUFFERING_MODE_NONE = 0;
- /**
- * This mode indicates that only time based source buffering is supported. This means
- * the watermark(s) are time based.
- */
- public static final int BUFFERING_MODE_TIME_ONLY = 1;
- /**
- * This mode indicates that only size based source buffering is supported. This means
- * the watermark(s) are size based.
- */
- public static final int BUFFERING_MODE_SIZE_ONLY = 2;
- /**
- * This mode indicates that both time and size based source buffering are supported,
- * and time based calculation precedes size based. Size based calculation will be used
- * only when time information is not available from the source.
- */
- public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3;
-
- /** @hide */
- @IntDef(
- value = {
- BUFFERING_MODE_NONE,
- BUFFERING_MODE_TIME_ONLY,
- BUFFERING_MODE_SIZE_ONLY,
- BUFFERING_MODE_TIME_THEN_SIZE,
- }
- )
- @Retention(RetentionPolicy.SOURCE)
- public @interface BufferingMode {}
-
- private static final int BUFFERING_NO_WATERMARK = -1;
+ private static final int BUFFERING_NO_MARK = -1;
// params
- private int mInitialBufferingMode = BUFFERING_MODE_NONE;
- private int mRebufferingMode = BUFFERING_MODE_NONE;
-
- private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
- private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+ private int mInitialMarkMs = BUFFERING_NO_MARK;
- private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+ private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
private BufferingParams() {
}
/**
- * Return the initial buffering mode used when {@link MediaPlayer} is being prepared.
- * @return one of the values that can be set in {@link Builder#setInitialBufferingMode(int)}
- */
- public int getInitialBufferingMode() {
- return mInitialBufferingMode;
- }
-
- /**
- * Return the rebuffering mode used when {@link MediaPlayer} is playing back source.
- * @return one of the values that can be set in {@link Builder#setRebufferingMode(int)}
- */
- public int getRebufferingMode() {
- return mRebufferingMode;
- }
-
- /**
- * Return the time based initial buffering watermark in milliseconds.
- * It is meaningful only when initial buffering mode obatined from
- * {@link #getInitialBufferingMode()} is time based.
- * @return time based initial buffering watermark in milliseconds
- */
- public int getInitialBufferingWatermarkMs() {
- return mInitialWatermarkMs;
- }
-
- /**
- * Return the size based initial buffering watermark in kilobytes.
- * It is meaningful only when initial buffering mode obatined from
- * {@link #getInitialBufferingMode()} is size based.
- * @return size based initial buffering watermark in kilobytes
+ * Return initial buffering mark in milliseconds.
+ * @return initial buffering mark in milliseconds
*/
- public int getInitialBufferingWatermarkKB() {
- return mInitialWatermarkKB;
+ public int getInitialMarkMs() {
+ return mInitialMarkMs;
}
/**
- * Return the time based low watermark in milliseconds for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is time based.
- * @return time based low watermark for rebuffering in milliseconds
+ * Return the mark in milliseconds for resuming playback.
+ * @return the mark for resuming playback in milliseconds
*/
- public int getRebufferingWatermarkLowMs() {
- return mRebufferingWatermarkLowMs;
- }
-
- /**
- * Return the time based high watermark in milliseconds for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is time based.
- * @return time based high watermark for rebuffering in milliseconds
- */
- public int getRebufferingWatermarkHighMs() {
- return mRebufferingWatermarkHighMs;
- }
-
- /**
- * Return the size based low watermark in kilobytes for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is size based.
- * @return size based low watermark for rebuffering in kilobytes
- */
- public int getRebufferingWatermarkLowKB() {
- return mRebufferingWatermarkLowKB;
- }
-
- /**
- * Return the size based high watermark in kilobytes for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is size based.
- * @return size based high watermark for rebuffering in kilobytes
- */
- public int getRebufferingWatermarkHighKB() {
- return mRebufferingWatermarkHighKB;
+ public int getResumePlaybackMarkMs() {
+ return mResumePlaybackMarkMs;
}
/**
@@ -200,27 +98,19 @@ public final class BufferingParams implements Parcelable {
* <pre class="prettyprint">
* BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
* myParams = new BufferingParams.Builder(myParams)
- * .setInitialBufferingWatermarkMs(10000)
- * .build();
+ * .setInitialMarkMs(10000)
+ * .setResumePlaybackMarkMs(15000)
+ * .build();
* mediaplayer.setBufferingParams(myParams);
* </pre>
*/
public static class Builder {
- private int mInitialBufferingMode = BUFFERING_MODE_NONE;
- private int mRebufferingMode = BUFFERING_MODE_NONE;
-
- private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
- private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
-
- private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+ private int mInitialMarkMs = BUFFERING_NO_MARK;
+ private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
/**
* Constructs a new Builder with the defaults.
- * By default, both initial buffering mode and rebuffering mode are
- * {@link BufferingParams#BUFFERING_MODE_NONE}, and all watermarks are -1.
+ * By default, all marks are -1.
*/
public Builder() {
}
@@ -231,16 +121,8 @@ public final class BufferingParams implements Parcelable {
* in the new Builder.
*/
public Builder(BufferingParams bp) {
- mInitialBufferingMode = bp.mInitialBufferingMode;
- mRebufferingMode = bp.mRebufferingMode;
-
- mInitialWatermarkMs = bp.mInitialWatermarkMs;
- mInitialWatermarkKB = bp.mInitialWatermarkKB;
-
- mRebufferingWatermarkLowMs = bp.mRebufferingWatermarkLowMs;
- mRebufferingWatermarkHighMs = bp.mRebufferingWatermarkHighMs;
- mRebufferingWatermarkLowKB = bp.mRebufferingWatermarkLowKB;
- mRebufferingWatermarkHighKB = bp.mRebufferingWatermarkHighKB;
+ mInitialMarkMs = bp.mInitialMarkMs;
+ mResumePlaybackMarkMs = bp.mResumePlaybackMarkMs;
}
/**
@@ -250,179 +132,37 @@ public final class BufferingParams implements Parcelable {
* @return a new {@link BufferingParams} object
*/
public BufferingParams build() {
- if (isTimeBasedMode(mRebufferingMode)
- && mRebufferingWatermarkLowMs > mRebufferingWatermarkHighMs) {
- throw new IllegalStateException("Illegal watermark:"
- + mRebufferingWatermarkLowMs + " : " + mRebufferingWatermarkHighMs);
- }
- if (isSizeBasedMode(mRebufferingMode)
- && mRebufferingWatermarkLowKB > mRebufferingWatermarkHighKB) {
- throw new IllegalStateException("Illegal watermark:"
- + mRebufferingWatermarkLowKB + " : " + mRebufferingWatermarkHighKB);
- }
-
BufferingParams bp = new BufferingParams();
- bp.mInitialBufferingMode = mInitialBufferingMode;
- bp.mRebufferingMode = mRebufferingMode;
-
- bp.mInitialWatermarkMs = mInitialWatermarkMs;
- bp.mInitialWatermarkKB = mInitialWatermarkKB;
+ bp.mInitialMarkMs = mInitialMarkMs;
+ bp.mResumePlaybackMarkMs = mResumePlaybackMarkMs;
- bp.mRebufferingWatermarkLowMs = mRebufferingWatermarkLowMs;
- bp.mRebufferingWatermarkHighMs = mRebufferingWatermarkHighMs;
- bp.mRebufferingWatermarkLowKB = mRebufferingWatermarkLowKB;
- bp.mRebufferingWatermarkHighKB = mRebufferingWatermarkHighKB;
return bp;
}
- private boolean isTimeBasedMode(int mode) {
- return (mode == BUFFERING_MODE_TIME_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
- }
-
- private boolean isSizeBasedMode(int mode) {
- return (mode == BUFFERING_MODE_SIZE_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
- }
-
/**
- * Sets the initial buffering mode.
- * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
- * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * Sets the time based mark in milliseconds for initial buffering.
+ * @param markMs time based mark in milliseconds
* @return the same Builder instance.
*/
- public Builder setInitialBufferingMode(@BufferingMode int mode) {
- switch (mode) {
- case BUFFERING_MODE_NONE:
- case BUFFERING_MODE_TIME_ONLY:
- case BUFFERING_MODE_SIZE_ONLY:
- case BUFFERING_MODE_TIME_THEN_SIZE:
- mInitialBufferingMode = mode;
- break;
- default:
- throw new IllegalArgumentException("Illegal buffering mode " + mode);
- }
+ public Builder setInitialMarkMs(int markMs) {
+ mInitialMarkMs = markMs;
return this;
}
/**
- * Sets the rebuffering mode.
- * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
- * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * Sets the time based mark in milliseconds for resuming playback.
+ * @param markMs time based mark in milliseconds for resuming playback
* @return the same Builder instance.
*/
- public Builder setRebufferingMode(@BufferingMode int mode) {
- switch (mode) {
- case BUFFERING_MODE_NONE:
- case BUFFERING_MODE_TIME_ONLY:
- case BUFFERING_MODE_SIZE_ONLY:
- case BUFFERING_MODE_TIME_THEN_SIZE:
- mRebufferingMode = mode;
- break;
- default:
- throw new IllegalArgumentException("Illegal buffering mode " + mode);
- }
- return this;
- }
-
- /**
- * Sets the time based watermark in milliseconds for initial buffering.
- * @param watermarkMs time based watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setInitialBufferingWatermarkMs(int watermarkMs) {
- mInitialWatermarkMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the size based watermark in kilobytes for initial buffering.
- * @param watermarkKB size based watermark in kilobytes
- * @return the same Builder instance.
- */
- public Builder setInitialBufferingWatermarkKB(int watermarkKB) {
- mInitialWatermarkKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the time based low watermark in milliseconds for rebuffering.
- * @param watermarkMs time based low watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkLowMs(int watermarkMs) {
- mRebufferingWatermarkLowMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the time based high watermark in milliseconds for rebuffering.
- * @param watermarkMs time based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkHighMs(int watermarkMs) {
- mRebufferingWatermarkHighMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the size based low watermark in milliseconds for rebuffering.
- * @param watermarkKB size based low watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkLowKB(int watermarkKB) {
- mRebufferingWatermarkLowKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the size based high watermark in milliseconds for rebuffering.
- * @param watermarkKB size based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkHighKB(int watermarkKB) {
- mRebufferingWatermarkHighKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the time based low and high watermarks in milliseconds for rebuffering.
- * @param lowWatermarkMs time based low watermark in milliseconds
- * @param highWatermarkMs time based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarksMs(int lowWatermarkMs, int highWatermarkMs) {
- mRebufferingWatermarkLowMs = lowWatermarkMs;
- mRebufferingWatermarkHighMs = highWatermarkMs;
- return this;
- }
-
- /**
- * Sets the size based low and high watermarks in kilobytes for rebuffering.
- * @param lowWatermarkKB size based low watermark in kilobytes
- * @param highWatermarkKB size based high watermark in kilobytes
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarksKB(int lowWatermarkKB, int highWatermarkKB) {
- mRebufferingWatermarkLowKB = lowWatermarkKB;
- mRebufferingWatermarkHighKB = highWatermarkKB;
+ public Builder setResumePlaybackMarkMs(int markMs) {
+ mResumePlaybackMarkMs = markMs;
return this;
}
}
private BufferingParams(Parcel in) {
- mInitialBufferingMode = in.readInt();
- mRebufferingMode = in.readInt();
-
- mInitialWatermarkMs = in.readInt();
- mInitialWatermarkKB = in.readInt();
-
- mRebufferingWatermarkLowMs = in.readInt();
- mRebufferingWatermarkHighMs = in.readInt();
- mRebufferingWatermarkLowKB = in.readInt();
- mRebufferingWatermarkHighKB = in.readInt();
+ mInitialMarkMs = in.readInt();
+ mResumePlaybackMarkMs = in.readInt();
}
public static final Parcelable.Creator<BufferingParams> CREATOR =
@@ -446,15 +186,7 @@ public final class BufferingParams implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mInitialBufferingMode);
- dest.writeInt(mRebufferingMode);
-
- dest.writeInt(mInitialWatermarkMs);
- dest.writeInt(mInitialWatermarkKB);
-
- dest.writeInt(mRebufferingWatermarkLowMs);
- dest.writeInt(mRebufferingWatermarkHighMs);
- dest.writeInt(mRebufferingWatermarkLowKB);
- dest.writeInt(mRebufferingWatermarkHighKB);
+ dest.writeInt(mInitialMarkMs);
+ dest.writeInt(mResumePlaybackMarkMs);
}
}
diff --git a/android/media/ExifInterface.java b/android/media/ExifInterface.java
index ba41a7bd..91754162 100644
--- a/android/media/ExifInterface.java
+++ b/android/media/ExifInterface.java
@@ -2564,51 +2564,66 @@ public class ExifInterface {
});
}
+ String hasImage = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
String hasVideo = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
- final String METADATA_HAS_VIDEO_VALUE_YES = "yes";
- if (METADATA_HAS_VIDEO_VALUE_YES.equals(hasVideo)) {
- String width = retriever.extractMetadata(
+ String width = null;
+ String height = null;
+ String rotation = null;
+ final String METADATA_VALUE_YES = "yes";
+ // If the file has both image and video, prefer image info over video info.
+ // App querying ExifInterface is most likely using the bitmap path which
+ // picks the image first.
+ if (METADATA_VALUE_YES.equals(hasImage)) {
+ width = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
+ height = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
+ rotation = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
+ } else if (METADATA_VALUE_YES.equals(hasVideo)) {
+ width = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
- String height = retriever.extractMetadata(
+ height = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+ rotation = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ }
- if (width != null) {
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
- ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
- }
-
- if (height != null) {
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
- ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
- }
+ if (width != null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
+ ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
+ }
- String rotation = retriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
- if (rotation != null) {
- int orientation = ExifInterface.ORIENTATION_NORMAL;
+ if (height != null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
+ ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
+ }
- // all rotation angles in CW
- switch (Integer.parseInt(rotation)) {
- case 90:
- orientation = ExifInterface.ORIENTATION_ROTATE_90;
- break;
- case 180:
- orientation = ExifInterface.ORIENTATION_ROTATE_180;
- break;
- case 270:
- orientation = ExifInterface.ORIENTATION_ROTATE_270;
- break;
- }
+ if (rotation != null) {
+ int orientation = ExifInterface.ORIENTATION_NORMAL;
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
- ExifAttribute.createUShort(orientation, mExifByteOrder));
+ // all rotation angles in CW
+ switch (Integer.parseInt(rotation)) {
+ case 90:
+ orientation = ExifInterface.ORIENTATION_ROTATE_90;
+ break;
+ case 180:
+ orientation = ExifInterface.ORIENTATION_ROTATE_180;
+ break;
+ case 270:
+ orientation = ExifInterface.ORIENTATION_ROTATE_270;
+ break;
}
- if (DEBUG) {
- Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
- }
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
+ ExifAttribute.createUShort(orientation, mExifByteOrder));
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
}
} finally {
retriever.release();
diff --git a/android/media/MediaDrm.java b/android/media/MediaDrm.java
index 88b1c5ff..1feea890 100644
--- a/android/media/MediaDrm.java
+++ b/android/media/MediaDrm.java
@@ -994,7 +994,6 @@ public final class MediaDrm {
* {@link #PROPERTY_VENDOR}, {@link #PROPERTY_VERSION},
* {@link #PROPERTY_DESCRIPTION}, {@link #PROPERTY_ALGORITHMS}
*/
- /* FIXME this throws IllegalStateException for invalid property names */
@NonNull
public native String getPropertyString(@NonNull @StringProperty String propertyName);
@@ -1002,7 +1001,6 @@ public final class MediaDrm {
* Byte array property name: the device unique identifier is established during
* device provisioning and provides a means of uniquely identifying each device.
*/
- /* FIXME this throws IllegalStateException for invalid property names */
public static final String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId";
/** @hide */
diff --git a/android/media/MediaFormat.java b/android/media/MediaFormat.java
index ed5f7d84..c475e122 100644
--- a/android/media/MediaFormat.java
+++ b/android/media/MediaFormat.java
@@ -96,6 +96,19 @@ import java.util.Map;
* <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
* <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr>
* </table>
+ *
+ * Image formats have the following keys:
+ * <table>
+ * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
+ * <tr><td>{@link #KEY_WIDTH}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_HEIGHT}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_COLOR_FORMAT}</td><td>Integer</td><td>set by the user
+ * for encoders, readable in the output format of decoders</b></td></tr>
+ * <tr><td>{@link #KEY_GRID_WIDTH}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_HEIGHT}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_ROWS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_COLS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * </table>
*/
public final class MediaFormat {
public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
@@ -126,6 +139,35 @@ public final class MediaFormat {
public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
/**
+ * MIME type for HEIF still image data encoded in HEVC.
+ *
+ * To decode such an image, {@link MediaCodec} decoder for
+ * {@ #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form
+ * the correct {@link #MediaFormat} based on additional information in
+ * the track format, and send it to {@link MediaCodec#configure}.
+ *
+ * The track's MediaFormat will come with {@link #KEY_WIDTH} and
+ * {@link #KEY_HEIGHT} keys, which describes the width and height
+ * of the image. If the image doesn't contain grid (i.e. none of
+ * {@link #KEY_GRID_WIDTH}, {@link #KEY_GRID_HEIGHT},
+ * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLS} are present}), the
+ * track will contain a single sample of coded data for the entire image,
+ * and the image width and height should be used to set up the decoder.
+ *
+ * If the image does come with grid, each sample from the track will
+ * contain one tile in the grid, of which the size is described by
+ * {@link #KEY_GRID_WIDTH} and {@link #KEY_GRID_HEIGHT}. This size
+ * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be
+ * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS}
+ * by {@link #KEY_GRID_COLS} samples in row-major, top-row first,
+ * left-to-right order. The output image should be reconstructed by
+ * first tiling the decoding results of the tiles in the correct order,
+ * then trimming (before rotation is applied) on the bottom and right
+ * side, if the tiled area is larger than the image width and height.
+ */
+ public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
+
+ /**
* MIME type for WebVTT subtitle data.
*/
public static final String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -232,6 +274,54 @@ public final class MediaFormat {
public static final String KEY_FRAME_RATE = "frame-rate";
/**
+ * A key describing the grid width of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+ * track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_ROWS
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_WIDTH = "grid-width";
+
+ /**
+ * A key describing the grid height of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+ * track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_ROWS
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_HEIGHT = "grid-height";
+
+ /**
+ * A key describing the number of grid rows in the content in a
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_ROWS = "grid-rows";
+
+ /**
+ * A key describing the number of grid columns in the content in a
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_ROWS
+ */
+ public static final String KEY_GRID_COLS = "grid-cols";
+
+ /**
* A key describing the raw audio sample encoding/format.
*
* <p>The associated value is an integer, using one of the
diff --git a/android/media/MediaMetadataRetriever.java b/android/media/MediaMetadataRetriever.java
index 760cc49b..0b864018 100644
--- a/android/media/MediaMetadataRetriever.java
+++ b/android/media/MediaMetadataRetriever.java
@@ -47,7 +47,7 @@ public class MediaMetadataRetriever
// The field below is accessed by native methods
@SuppressWarnings("unused")
private long mNativeContext;
-
+
private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF;
public MediaMetadataRetriever() {
@@ -58,7 +58,7 @@ public class MediaMetadataRetriever
* Sets the data source (file pathname) to use. Call this
* method before the rest of the methods in this class. This method may be
* time-consuming.
- *
+ *
* @param path The path of the input media file.
* @throws IllegalArgumentException If the path is invalid.
*/
@@ -113,7 +113,7 @@ public class MediaMetadataRetriever
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
- *
+ *
* @param fd the FileDescriptor for the file you want to play
* @param offset the offset into the file where the data to be played starts,
* in bytes. It must be non-negative
@@ -123,13 +123,13 @@ public class MediaMetadataRetriever
*/
public native void setDataSource(FileDescriptor fd, long offset, long length)
throws IllegalArgumentException;
-
+
/**
* Sets the data source (FileDescriptor) to use. It is the caller's
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
- *
+ *
* @param fd the FileDescriptor for the file you want to play
* @throws IllegalArgumentException if the FileDescriptor is invalid
*/
@@ -138,11 +138,11 @@ public class MediaMetadataRetriever
// intentionally less than LONG_MAX
setDataSource(fd, 0, 0x7ffffffffffffffL);
}
-
+
/**
- * Sets the data source as a content Uri. Call this method before
+ * Sets the data source as a content Uri. Call this method before
* the rest of the methods in this class. This method may be time-consuming.
- *
+ *
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @throws IllegalArgumentException if the Uri is invalid
@@ -154,7 +154,7 @@ public class MediaMetadataRetriever
if (uri == null) {
throw new IllegalArgumentException();
}
-
+
String scheme = uri.getScheme();
if(scheme == null || scheme.equals("file")) {
setDataSource(uri.getPath());
@@ -213,12 +213,12 @@ public class MediaMetadataRetriever
/**
* Call this method after setDataSource(). This method retrieves the
* meta data value associated with the keyCode.
- *
+ *
* The keyCode currently supported is listed below as METADATA_XXX
* constants. With any other value, it returns a null pointer.
- *
+ *
* @param keyCode One of the constants listed below at the end of the class.
- * @return The meta data value associate with the given keyCode on success;
+ * @return The meta data value associate with the given keyCode on success;
* null on failure.
*/
public native String extractMetadata(int keyCode);
@@ -357,6 +357,109 @@ public class MediaMetadataRetriever
private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
/**
+ * This method retrieves a video frame by its index. It should only be called
+ * after {@link #setDataSource}.
+ *
+ * @param frameIndex 0-based index of the video frame. The frame index must be that of
+ * a valid frame. The total number of frames available for retrieval can be queried
+ * via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+ *
+ * @throws IllegalStateException if the container doesn't contain video or image sequences.
+ * @throws IllegalArgumentException if the requested frame index does not exist.
+ *
+ * @return A Bitmap containing the requested video frame, or null if the retrieval fails.
+ *
+ * @see #getFramesAtIndex(int, int)
+ */
+ public Bitmap getFrameAtIndex(int frameIndex) {
+ Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1);
+ if (bitmaps == null || bitmaps.length < 1) {
+ return null;
+ }
+ return bitmaps[0];
+ }
+
+ /**
+ * This method retrieves a consecutive set of video frames starting at the
+ * specified index. It should only be called after {@link #setDataSource}.
+ *
+ * If the caller intends to retrieve more than one consecutive video frames,
+ * this method is preferred over {@link #getFrameAtIndex(int)} for efficiency.
+ *
+ * @param frameIndex 0-based index of the first video frame to retrieve. The frame index
+ * must be that of a valid frame. The total number of frames available for retrieval
+ * can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+ * @param numFrames number of consecutive video frames to retrieve. Must be a positive
+ * value. The stream must contain at least numFrames frames starting at frameIndex.
+ *
+ * @throws IllegalStateException if the container doesn't contain video or image sequences.
+ * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the
+ * stream doesn't contain at least numFrames starting at frameIndex.
+
+ * @return An array of Bitmaps containing the requested video frames. The returned
+ * array could contain less frames than requested if the retrieval fails.
+ *
+ * @see #getFrameAtIndex(int)
+ */
+ public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) {
+ if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) {
+ throw new IllegalStateException("Does not contail video or image sequences");
+ }
+ int frameCount = Integer.parseInt(
+ extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+ if (frameIndex < 0 || numFrames < 1
+ || frameIndex >= frameCount
+ || frameIndex > frameCount - numFrames) {
+ throw new IllegalArgumentException("Invalid frameIndex or numFrames: "
+ + frameIndex + ", " + numFrames);
+ }
+ return _getFrameAtIndex(frameIndex, numFrames);
+ }
+ private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames);
+
+ /**
+ * This method retrieves a still image by its index. It should only be called
+ * after {@link #setDataSource}.
+ *
+ * @param imageIndex 0-based index of the image, with negative value indicating
+ * the primary image.
+ * @throws IllegalStateException if the container doesn't contain still images.
+ * @throws IllegalArgumentException if the requested image does not exist.
+ *
+ * @return the requested still image, or null if the image cannot be retrieved.
+ *
+ * @see #getPrimaryImage
+ */
+ public Bitmap getImageAtIndex(int imageIndex) {
+ if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) {
+ throw new IllegalStateException("Does not contail still images");
+ }
+
+ String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT);
+ if (imageIndex >= Integer.parseInt(imageCount)) {
+ throw new IllegalArgumentException("Invalid image index: " + imageCount);
+ }
+
+ return _getImageAtIndex(imageIndex);
+ }
+
+ /**
+ * This method retrieves the primary image of the media content. It should only
+ * be called after {@link #setDataSource}.
+ *
+ * @return the primary image, or null if it cannot be retrieved.
+ *
+ * @throws IllegalStateException if the container doesn't contain still images.
+ *
+ * @see #getImageAtIndex(int)
+ */
+ public Bitmap getPrimaryImage() {
+ return getImageAtIndex(-1);
+ }
+
+ private native Bitmap _getImageAtIndex(int imageIndex);
+
+ /**
* Call this method after setDataSource(). This method finds the optional
* graphic or album/cover art associated associated with the data source. If
* there are more than one pictures, (any) one of them is returned.
@@ -572,5 +675,40 @@ public class MediaMetadataRetriever
* number.
*/
public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
+ /**
+ * If this key exists the media contains still image content.
+ */
+ public static final int METADATA_KEY_HAS_IMAGE = 26;
+ /**
+ * If the media contains still images, this key retrieves the number
+ * of still images.
+ */
+ public static final int METADATA_KEY_IMAGE_COUNT = 27;
+ /**
+ * If the media contains still images, this key retrieves the image
+ * index of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_PRIMARY = 28;
+ /**
+ * If the media contains still images, this key retrieves the width
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_WIDTH = 29;
+ /**
+ * If the media contains still images, this key retrieves the height
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_HEIGHT = 30;
+ /**
+ * If the media contains still images, this key retrieves the rotation
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_ROTATION = 31;
+ /**
+ * If the media contains video and this key exists, it retrieves the
+ * total number of frames in the video sequence.
+ */
+ public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32;
+
// Add more here...
}
diff --git a/android/media/MediaPlayer.java b/android/media/MediaPlayer.java
index 62757e2e..649c091b 100644
--- a/android/media/MediaPlayer.java
+++ b/android/media/MediaPlayer.java
@@ -43,6 +43,7 @@ import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.util.Pair;
+import android.util.ArrayMap;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.widget.VideoView;
@@ -58,6 +59,7 @@ import android.media.SubtitleData;
import android.media.SubtitleTrack.RenderingWidget;
import android.media.SyncParams;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import libcore.io.IoBridge;
@@ -577,6 +579,7 @@ import java.util.Vector;
public class MediaPlayer extends PlayerBase
implements SubtitleController.Listener
, VolumeAutomation
+ , AudioRouting
{
/**
Constant to retrieve only the new metadata since the last
@@ -1417,6 +1420,155 @@ public class MediaPlayer extends PlayerBase
private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);
+ //--------------------------------------------------------------------------
+ // Explicit Routing
+ //--------------------
+ private AudioDeviceInfo mPreferredDevice = null;
+
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the output from this MediaPlayer.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio device.
+ */
+ @Override
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+ if (deviceInfo != null && !deviceInfo.isSink()) {
+ return false;
+ }
+ int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+ boolean status = native_setOutputDevice(preferredDeviceId);
+ if (status == true) {
+ synchronized (this) {
+ mPreferredDevice = deviceInfo;
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for playback.
+ */
+ @Override
+ public AudioDeviceInfo getPreferredDevice() {
+ synchronized (this) {
+ return mPreferredDevice;
+ }
+ }
+
+ /**
+ * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer
+ * Note: The query is only valid if the MediaPlayer is currently playing.
+ * If the player is not playing, the returned device can be null or correspond to previously
+ * selected device when the player was last active.
+ */
+ @Override
+ public AudioDeviceInfo getRoutedDevice() {
+ int deviceId = native_getRoutedDeviceId();
+ if (deviceId == 0) {
+ return null;
+ }
+ AudioDeviceInfo[] devices =
+ AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
+ for (int i = 0; i < devices.length; i++) {
+ if (devices[i].getId() == deviceId) {
+ return devices[i];
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+ */
+ private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+ if (mRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback(enabled);
+ }
+ }
+
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this MediaPlayer.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the handler on the main looper will be used.
+ */
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ enableNativeRoutingCallbacksLocked(true);
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ enableNativeRoutingCallbacksLocked(false);
+ }
+ }
+ }
+
+ /**
+ * Helper class to handle the forwarding of native events to the appropriate listener
+ * (potentially) handled in a different thread
+ */
+ private class NativeRoutingEventHandlerDelegate {
+ private MediaPlayer mMediaPlayer;
+ private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
+ private Handler mHandler;
+
+ NativeRoutingEventHandlerDelegate(final MediaPlayer mediaPlayer,
+ final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
+ mMediaPlayer = mediaPlayer;
+ mOnRoutingChangedListener = listener;
+ mHandler = handler != null ? handler : mEventHandler;
+ }
+
+ void notifyClient() {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mOnRoutingChangedListener != null) {
+ mOnRoutingChangedListener.onRoutingChanged(mMediaPlayer);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private native final boolean native_setOutputDevice(int deviceId);
+ private native final int native_getRoutedDeviceId();
+ private native final void native_enableDeviceCallback(boolean enabled);
+
/**
* Set the low-level power management behavior for this MediaPlayer. This
* can be used when the MediaPlayer is not playing through a SurfaceHolder
@@ -1546,21 +1698,9 @@ public class MediaPlayer extends PlayerBase
public native boolean isPlaying();
/**
- * Gets the default buffering management params.
- * Calling it only after {@code setDataSource} has been called.
- * Each type of data source might have different set of default params.
- *
- * @return the default buffering management params supported by the source component.
- * @throws IllegalStateException if the internal player engine has not been
- * initialized, or {@code setDataSource} has not been called.
- * @hide
- */
- @NonNull
- public native BufferingParams getDefaultBufferingParams();
-
- /**
* Gets the current buffering management params used by the source component.
* Calling it only after {@code setDataSource} has been called.
+ * Each type of data source might have different set of default params.
*
* @return the current buffering management params used by the source component.
* @throws IllegalStateException if the internal player engine has not been
@@ -1575,8 +1715,7 @@ public class MediaPlayer extends PlayerBase
* The object sets its internal BufferingParams to the input, except that the input is
* invalid or not supported.
* Call it only after {@code setDataSource} has been called.
- * Users should only use supported mode returned by {@link #getDefaultBufferingParams()}
- * or its downsized version as described in {@link BufferingParams}.
+ * The input is a hint to MediaPlayer.
*
* @param params the buffering management params.
*
@@ -3176,6 +3315,7 @@ public class MediaPlayer extends PlayerBase
private static final int MEDIA_SUBTITLE_DATA = 201;
private static final int MEDIA_META_DATA = 202;
private static final int MEDIA_DRM_INFO = 210;
+ private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000;
private TimeProvider mTimeProvider;
@@ -3414,6 +3554,16 @@ public class MediaPlayer extends PlayerBase
case MEDIA_NOP: // interface test message - ignore
break;
+ case MEDIA_AUDIO_ROUTING_CHANGED:
+ AudioManager.resetAudioPortGeneration();
+ synchronized (mRoutingChangeListeners) {
+ for (NativeRoutingEventHandlerDelegate delegate
+ : mRoutingChangeListeners.values()) {
+ delegate.notifyClient();
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
diff --git a/android/net/ConnectivityManager.java b/android/net/ConnectivityManager.java
index d7ecc81f..8071e8b8 100644
--- a/android/net/ConnectivityManager.java
+++ b/android/net/ConnectivityManager.java
@@ -619,6 +619,35 @@ public class ConnectivityManager {
*/
public static final int NETID_UNSET = 0;
+ /**
+ * Private DNS Mode values.
+ *
+ * The "private_dns_mode" global setting stores a String value which is
+ * expected to be one of the following.
+ */
+
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OFF = "off";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+ /**
+ * The default Private DNS mode.
+ *
+ * This may change from release to release or may become dependent upon
+ * the capabilities of the underlying platform.
+ *
+ * @hide
+ */
+ public static final String PRIVATE_DNS_DEFAULT_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
private final IConnectivityManager mService;
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
diff --git a/android/net/ConnectivityMetricsEvent.java b/android/net/ConnectivityMetricsEvent.java
index 46bb3467..394ac428 100644
--- a/android/net/ConnectivityMetricsEvent.java
+++ b/android/net/ConnectivityMetricsEvent.java
@@ -18,6 +18,7 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
+
import com.android.internal.util.BitUtils;
/**
@@ -80,7 +81,7 @@ public final class ConnectivityMetricsEvent implements Parcelable {
StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
buffer.append(String.format("%tT.%tL", timestamp, timestamp));
if (netId != 0) {
- buffer.append(", ").append(netId);
+ buffer.append(", ").append("netId=").append(netId);
}
if (ifname != null) {
buffer.append(", ").append(ifname);
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 16b14523..64f8f39e 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -78,7 +78,11 @@ public final class IpSecAlgorithm implements Parcelable {
/**
* AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
*
- * <p>Valid lengths for this key are {128, 192, 256}.
+ * <p>Valid lengths for keying material are {160, 224, 288}.
+ *
+ * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key
+ * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per
+ * invocation with the same key.
*
* <p>Valid ICV (truncation) lengths are {64, 96, 128}.
*/
diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java
index d7b32561..eccd5f47 100644
--- a/android/net/IpSecManager.java
+++ b/android/net/IpSecManager.java
@@ -136,7 +136,7 @@ public final class IpSecManager {
}
@Override
- protected void finalize() {
+ protected void finalize() throws Throwable {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
diff --git a/android/net/LocalSocketImpl.java b/android/net/LocalSocketImpl.java
index 05c8afb3..6e4a231b 100644
--- a/android/net/LocalSocketImpl.java
+++ b/android/net/LocalSocketImpl.java
@@ -16,18 +16,18 @@
package android.net;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.InputStream;
-import java.io.FileDescriptor;
-import java.net.SocketOptions;
-
import android.system.ErrnoException;
+import android.system.Int32Ref;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructLinger;
import android.system.StructTimeval;
-import android.util.MutableInt;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketOptions;
/**
* Socket implementation used for android.net.LocalSocket and
@@ -62,7 +62,7 @@ class LocalSocketImpl
FileDescriptor myFd = fd;
if (myFd == null) throw new IOException("socket closed");
- MutableInt avail = new MutableInt(0);
+ Int32Ref avail = new Int32Ref(0);
try {
Os.ioctlInt(myFd, OsConstants.FIONREAD, avail);
} catch (ErrnoException e) {
@@ -167,7 +167,7 @@ class LocalSocketImpl
if (myFd == null) throw new IOException("socket closed");
// Loop until the output buffer is empty.
- MutableInt pending = new MutableInt(0);
+ Int32Ref pending = new Int32Ref(0);
while (true) {
try {
// See linux/net/unix/af_unix.c
diff --git a/android/net/MacAddress.java b/android/net/MacAddress.java
new file mode 100644
index 00000000..f6a69bac
--- /dev/null
+++ b/android/net/MacAddress.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.StringJoiner;
+
+/**
+ * Represents a mac address.
+ *
+ * @hide
+ */
+public final class MacAddress implements Parcelable {
+
+ private static final int ETHER_ADDR_LEN = 6;
+ private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
+
+ /** The broadcast mac address. */
+ public static final MacAddress BROADCAST_ADDRESS = new MacAddress(ETHER_ADDR_BROADCAST);
+
+ /** The zero mac address. */
+ public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
+
+ /** Represents categories of mac addresses. */
+ public enum MacAddressType {
+ UNICAST,
+ MULTICAST,
+ BROADCAST;
+ }
+
+ private static final long VALID_LONG_MASK = BROADCAST_ADDRESS.mAddr;
+ private static final long LOCALLY_ASSIGNED_MASK = new MacAddress("2:0:0:0:0:0").mAddr;
+ private static final long MULTICAST_MASK = new MacAddress("1:0:0:0:0:0").mAddr;
+ private static final long OUI_MASK = new MacAddress("ff:ff:ff:0:0:0").mAddr;
+ private static final long NIC_MASK = new MacAddress("0:0:0:ff:ff:ff").mAddr;
+ private static final MacAddress BASE_ANDROID_MAC = new MacAddress("da:a1:19:0:0:0");
+
+ // Internal representation of the mac address as a single 8 byte long.
+ // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
+ // mac address are encoded in the 6 least significant bytes of the long, where the first
+ // byte of the array is mapped to the 3rd highest logical byte of the long, the second
+ // byte of the array is mapped to the 4th highest logical byte of the long, and so on.
+ private final long mAddr;
+
+ private MacAddress(long addr) {
+ mAddr = addr;
+ }
+
+ /** Creates a MacAddress for the given byte representation. */
+ public MacAddress(byte[] addr) {
+ this(longAddrFromByteAddr(addr));
+ }
+
+ /** Creates a MacAddress for the given string representation. */
+ public MacAddress(String addr) {
+ this(longAddrFromByteAddr(byteAddrFromStringAddr(addr)));
+ }
+
+ /** Returns the MacAddressType of this MacAddress. */
+ public MacAddressType addressType() {
+ if (equals(BROADCAST_ADDRESS)) {
+ return MacAddressType.BROADCAST;
+ }
+ if (isMulticastAddress()) {
+ return MacAddressType.MULTICAST;
+ }
+ return MacAddressType.UNICAST;
+ }
+
+ /** Returns true if this MacAddress corresponds to a multicast address. */
+ public boolean isMulticastAddress() {
+ return (mAddr & MULTICAST_MASK) != 0;
+ }
+
+ /** Returns true if this MacAddress corresponds to a locally assigned address. */
+ public boolean isLocallyAssigned() {
+ return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
+ }
+
+ /** Returns a byte array representation of this MacAddress. */
+ public byte[] toByteArray() {
+ return byteAddrFromLongAddr(mAddr);
+ }
+
+ @Override
+ public String toString() {
+ return stringAddrFromByteAddr(byteAddrFromLongAddr(mAddr));
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) ((mAddr >> 32) ^ mAddr);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mAddr);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<MacAddress> CREATOR =
+ new Parcelable.Creator<MacAddress>() {
+ public MacAddress createFromParcel(Parcel in) {
+ return new MacAddress(in.readLong());
+ }
+
+ public MacAddress[] newArray(int size) {
+ return new MacAddress[size];
+ }
+ };
+
+ /** Return true if the given byte array is not null and has the length of a mac address. */
+ public static boolean isMacAddress(byte[] addr) {
+ return addr != null && addr.length == ETHER_ADDR_LEN;
+ }
+
+ /**
+ * Return the MacAddressType of the mac address represented by the given byte array,
+ * or null if the given byte array does not represent an mac address.
+ */
+ public static MacAddressType macAddressType(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ return null;
+ }
+ return new MacAddress(addr).addressType();
+ }
+
+ /** DOCME */
+ public static byte[] byteAddrFromStringAddr(String addr) {
+ if (addr == null) {
+ throw new IllegalArgumentException("cannot convert the null String");
+ }
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
+ }
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ int x = Integer.valueOf(parts[i], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ bytes[i] = (byte) x;
+ }
+ return bytes;
+ }
+
+ /** DOCME */
+ public static String stringAddrFromByteAddr(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ return null;
+ }
+ StringJoiner j = new StringJoiner(":");
+ for (byte b : addr) {
+ j.add(Integer.toHexString(BitUtils.uint8(b)));
+ }
+ return j.toString();
+ }
+
+ /** @hide */
+ public static byte[] byteAddrFromLongAddr(long addr) {
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ int index = ETHER_ADDR_LEN;
+ while (index-- > 0) {
+ bytes[index] = (byte) addr;
+ addr = addr >> 8;
+ }
+ return bytes;
+ }
+
+ /** @hide */
+ public static long longAddrFromByteAddr(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ throw new IllegalArgumentException(
+ Arrays.toString(addr) + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ for (byte b : addr) {
+ longAddr = (longAddr << 8) + BitUtils.uint8(b);
+ }
+ return longAddr;
+ }
+
+ /** @hide */
+ public static long longAddrFromStringAddr(String addr) {
+ if (addr == null) {
+ throw new IllegalArgumentException("cannot convert the null String");
+ }
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ int index = ETHER_ADDR_LEN;
+ while (index-- > 0) {
+ int x = Integer.valueOf(parts[index], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ longAddr = x + (longAddr << 8);
+ }
+ return longAddr;
+ }
+
+ /** @hide */
+ public static String stringAddrFromLongAddr(long addr) {
+ addr = Long.reverseBytes(addr) >> 16;
+ StringJoiner j = new StringJoiner(":");
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ j.add(Integer.toHexString((byte) addr));
+ addr = addr >> 8;
+ }
+ return j.toString();
+ }
+
+ /**
+ * Returns a randomely generated mac address with the Android OUI value "DA-A1-19".
+ * The locally assigned bit is always set to 1.
+ */
+ public static MacAddress getRandomAddress() {
+ return getRandomAddress(BASE_ANDROID_MAC, new Random());
+ }
+
+ /**
+ * Returns a randomely generated mac address using the given Random object and the same
+ * OUI values as the given MacAddress. The locally assigned bit is always set to 1.
+ */
+ public static MacAddress getRandomAddress(MacAddress base, Random r) {
+ long longAddr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()) | LOCALLY_ASSIGNED_MASK;
+ return new MacAddress(longAddr);
+ }
+
+ // Convenience function for working around the lack of byte literals.
+ private static byte[] addr(int... in) {
+ if (in.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(Arrays.toString(in)
+ + " was not an array with length equal to " + ETHER_ADDR_LEN);
+ }
+ byte[] out = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ out[i] = (byte) in[i];
+ }
+ return out;
+ }
+}
diff --git a/android/net/Network.java b/android/net/Network.java
index 3c868c39..903b602b 100644
--- a/android/net/Network.java
+++ b/android/net/Network.java
@@ -16,14 +16,14 @@
package android.net;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import libcore.net.http.Dns;
-import libcore.net.http.HttpURLConnectionFactory;
+import com.android.okhttp.internalandroidapi.Dns;
+import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -34,11 +34,12 @@ import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
-import java.net.UnknownHostException;
import java.net.URL;
import java.net.URLConnection;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
+
import javax.net.SocketFactory;
/**
diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java
index db12dd97..ee75fd44 100644
--- a/android/net/NetworkCapabilities.java
+++ b/android/net/NetworkCapabilities.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.IntDef;
+import android.net.ConnectivityManager.NetworkCallback;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,15 +31,24 @@ import java.util.Objects;
import java.util.StringJoiner;
/**
- * This class represents the capabilities of a network. This is used both to specify
- * needs to {@link ConnectivityManager} and when inspecting a network.
- *
- * Note that this replaces the old {@link ConnectivityManager#TYPE_MOBILE} method
- * of network selection. Rather than indicate a need for Wi-Fi because an application
- * needs high bandwidth and risk obsolescence when a new, fast network appears (like LTE),
- * the application should specify it needs high bandwidth. Similarly if an application
- * needs an unmetered network for a bulk transfer it can specify that rather than assuming
- * all cellular based connections are metered and all Wi-Fi based connections are not.
+ * Representation of the capabilities of a network. This object serves two
+ * purposes:
+ * <ul>
+ * <li>An expression of the current capabilities of an active network, typically
+ * expressed through
+ * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
+ * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
+ * <li>An expression of the future capabilities of a desired network, typically
+ * expressed through {@link NetworkRequest}.
+ * </ul>
+ * <p>
+ * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
+ * network selection. Rather than indicate a need for Wi-Fi because an
+ * application needs high bandwidth and risk obsolescence when a new, fast
+ * network appears (like LTE), the application should specify it needs high
+ * bandwidth. Similarly if an application needs an unmetered network for a bulk
+ * transfer it can specify that rather than assuming all cellular based
+ * connections are metered and all Wi-Fi based connections are not.
*/
public final class NetworkCapabilities implements Parcelable {
private static final String TAG = "NetworkCapabilities";
@@ -101,6 +111,7 @@ public final class NetworkCapabilities implements Parcelable {
NET_CAPABILITY_NOT_VPN,
NET_CAPABILITY_VALIDATED,
NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_NOT_ROAMING,
NET_CAPABILITY_FOREGROUND,
})
public @interface NetCapability { }
@@ -218,11 +229,16 @@ public final class NetworkCapabilities implements Parcelable {
public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
/**
+ * Indicates that this network is not roaming.
+ */
+ public static final int NET_CAPABILITY_NOT_ROAMING = 18;
+
+ /**
* Indicates that this network is available for use by apps, and not a network that is being
* kept up in the background to facilitate fast network switching.
* @hide
*/
- public static final int NET_CAPABILITY_FOREGROUND = 18;
+ public static final int NET_CAPABILITY_FOREGROUND = 19;
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_FOREGROUND;
@@ -237,6 +253,7 @@ public final class NetworkCapabilities implements Parcelable {
(1 << NET_CAPABILITY_TRUSTED) |
(1 << NET_CAPABILITY_VALIDATED) |
(1 << NET_CAPABILITY_CAPTIVE_PORTAL) |
+ (1 << NET_CAPABILITY_NOT_ROAMING) |
(1 << NET_CAPABILITY_FOREGROUND);
/**
@@ -316,6 +333,21 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets (or clears) the given capability on this {@link NetworkCapabilities}
+ * instance.
+ *
+ * @hide
+ */
+ public NetworkCapabilities setCapability(@NetCapability int capability, boolean value) {
+ if (value) {
+ addCapability(capability);
+ } else {
+ removeCapability(capability);
+ }
+ return this;
+ }
+
+ /**
* Gets all the capabilities set on this {@code NetworkCapability} instance.
*
* @return an array of capability values for this instance.
@@ -326,6 +358,15 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets all the capabilities set on this {@code NetworkCapability} instance.
+ *
+ * @hide
+ */
+ public void setCapabilities(@NetCapability int[] capabilities) {
+ mNetworkCapabilities = BitUtils.packBits(capabilities);
+ }
+
+ /**
* Tests for the presence of a capabilitity on this instance.
*
* @param capability the capabilities to be tested for.
@@ -515,6 +556,21 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets (or clears) the given transport on this {@link NetworkCapabilities}
+ * instance.
+ *
+ * @hide
+ */
+ public NetworkCapabilities setTransportType(@Transport int transportType, boolean value) {
+ if (value) {
+ addTransportType(transportType);
+ } else {
+ removeTransportType(transportType);
+ }
+ return this;
+ }
+
+ /**
* Gets all the transports set on this {@code NetworkCapability} instance.
*
* @return an array of transport type values for this instance.
@@ -525,6 +581,15 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets all the transports set on this {@code NetworkCapability} instance.
+ *
+ * @hide
+ */
+ public void setTransportTypes(@Transport int[] transportTypes) {
+ mTransportTypes = BitUtils.packBits(transportTypes);
+ }
+
+ /**
* Tests for the presence of a transport on this instance.
*
* @param transportType the transport type to be tested for.
@@ -549,12 +614,18 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Value indicating that link bandwidth is unspecified.
+ * @hide
+ */
+ public static final int LINK_BANDWIDTH_UNSPECIFIED = 0;
+
+ /**
* Passive link bandwidth. This is a rough guide of the expected peak bandwidth
* for the first hop on the given transport. It is not measured, but may take into account
* link parameters (Radio technology, allocated channels, etc).
*/
- private int mLinkUpBandwidthKbps;
- private int mLinkDownBandwidthKbps;
+ private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+ private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
/**
* Sets the upstream bandwidth for this network in Kbps. This always only refers to
@@ -571,8 +642,9 @@ public final class NetworkCapabilities implements Parcelable {
* @param upKbps the estimated first hop upstream (device to network) bandwidth.
* @hide
*/
- public void setLinkUpstreamBandwidthKbps(int upKbps) {
+ public NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) {
mLinkUpBandwidthKbps = upKbps;
+ return this;
}
/**
@@ -600,8 +672,9 @@ public final class NetworkCapabilities implements Parcelable {
* @param downKbps the estimated first hop downstream (network to device) bandwidth.
* @hide
*/
- public void setLinkDownstreamBandwidthKbps(int downKbps) {
+ public NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) {
mLinkDownBandwidthKbps = downKbps;
+ return this;
}
/**
@@ -628,6 +701,20 @@ public final class NetworkCapabilities implements Parcelable {
return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps &&
this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
}
+ /** @hide */
+ public static int minBandwidth(int a, int b) {
+ if (a == LINK_BANDWIDTH_UNSPECIFIED) {
+ return b;
+ } else if (b == LINK_BANDWIDTH_UNSPECIFIED) {
+ return a;
+ } else {
+ return Math.min(a, b);
+ }
+ }
+ /** @hide */
+ public static int maxBandwidth(int a, int b) {
+ return Math.max(a, b);
+ }
private NetworkSpecifier mNetworkSpecifier = null;
@@ -708,8 +795,9 @@ public final class NetworkCapabilities implements Parcelable {
* @param signalStrength the bearer-specific signal strength.
* @hide
*/
- public void setSignalStrength(int signalStrength) {
+ public NetworkCapabilities setSignalStrength(int signalStrength) {
mSignalStrength = signalStrength;
+ return this;
}
/**
@@ -968,6 +1056,7 @@ public final class NetworkCapabilities implements Parcelable {
case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
case NET_CAPABILITY_VALIDATED: return "VALIDATED";
case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+ case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING";
case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
default: return Integer.toString(capability);
}
diff --git a/android/net/NetworkIdentity.java b/android/net/NetworkIdentity.java
index acd7b560..d3b35998 100644
--- a/android/net/NetworkIdentity.java
+++ b/android/net/NetworkIdentity.java
@@ -189,7 +189,8 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> {
String subscriberId = null;
String networkId = null;
- boolean roaming = false;
+ boolean roaming = !state.networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
boolean metered = !state.networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -203,7 +204,6 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> {
}
subscriberId = state.subscriberId;
- roaming = state.networkInfo.isRoaming();
} else if (type == TYPE_WIFI) {
if (state.networkId != null) {
diff --git a/android/net/NetworkInfo.java b/android/net/NetworkInfo.java
index 818aa211..e6ad89a2 100644
--- a/android/net/NetworkInfo.java
+++ b/android/net/NetworkInfo.java
@@ -305,11 +305,17 @@ public class NetworkInfo implements Parcelable {
}
/**
- * Indicates whether the device is currently roaming on this network.
- * When {@code true}, it suggests that use of data on this network
- * may incur extra costs.
+ * Indicates whether the device is currently roaming on this network. When
+ * {@code true}, it suggests that use of data on this network may incur
+ * extra costs.
+ *
* @return {@code true} if roaming is in effect, {@code false} otherwise.
+ * @deprecated Callers should switch to checking
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING}
+ * instead, since that handles more complex situations, such as
+ * VPNs.
*/
+ @Deprecated
public boolean isRoaming() {
synchronized (this) {
return mIsRoaming;
@@ -318,6 +324,7 @@ public class NetworkInfo implements Parcelable {
/** {@hide} */
@VisibleForTesting
+ @Deprecated
public void setRoaming(boolean isRoaming) {
synchronized (this) {
mIsRoaming = isRoaming;
diff --git a/android/net/NetworkWatchlistManager.java b/android/net/NetworkWatchlistManager.java
new file mode 100644
index 00000000..42e43c8a
--- /dev/null
+++ b/android/net/NetworkWatchlistManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that manage network watchlist in system.
+ * @hide
+ */
+@SystemService(Context.NETWORK_WATCHLIST_SERVICE)
+public class NetworkWatchlistManager {
+
+ private static final String TAG = "NetworkWatchlistManager";
+ private static final String SHARED_MEMORY_TAG = "NETWORK_WATCHLIST_SHARED_MEMORY";
+
+ private final Context mContext;
+ private final INetworkWatchlistManager mNetworkWatchlistManager;
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context, INetworkWatchlistManager manager) {
+ mContext = context;
+ mNetworkWatchlistManager = manager;
+ }
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context) {
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mNetworkWatchlistManager = (INetworkWatchlistManager)
+ INetworkWatchlistManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_WATCHLIST_SERVICE));
+ }
+
+ /**
+ * Report network watchlist records if necessary.
+ *
+ * Watchlist report process will run summarize records into a single report, then the
+ * report will be processed by differential privacy framework and store it on disk.
+ *
+ * @hide
+ */
+ public void reportWatchlistIfNecessary() {
+ try {
+ mNetworkWatchlistManager.reportWatchlistIfNecessary();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot report records", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/net/SSLCertificateSocketFactory.java b/android/net/SSLCertificateSocketFactory.java
index 0b1569ca..4817813c 100644
--- a/android/net/SSLCertificateSocketFactory.java
+++ b/android/net/SSLCertificateSocketFactory.java
@@ -63,7 +63,12 @@ import javax.net.ssl.X509TrustManager;
* This implementation does check the server's certificate hostname, but only
* for createSocket variants that specify a hostname. When using methods that
* use {@link InetAddress} or which return an unconnected socket, you MUST
- * verify the server's identity yourself to ensure a secure connection.</p>
+ * verify the server's identity yourself to ensure a secure connection.
+ *
+ * Refer to
+ * <a href="https://developer.android.com/training/articles/security-gms-provider.html">
+ * Updating Your Security Provider to Protect Against SSL Exploits</a>
+ * for further information.</p>
*
* <p>One way to verify the server's identity is to use
* {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
diff --git a/android/net/Uri.java b/android/net/Uri.java
index d5377c71..9edcc0e9 100644
--- a/android/net/Uri.java
+++ b/android/net/Uri.java
@@ -1066,7 +1066,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
return null;
}
- int end = authority.indexOf('@');
+ int end = authority.lastIndexOf('@');
return end == NOT_FOUND ? null : authority.substring(0, end);
}
@@ -1090,7 +1090,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
// Parse out user info and then port.
- int userInfoSeparator = authority.indexOf('@');
+ int userInfoSeparator = authority.lastIndexOf('@');
int portSeparator = authority.indexOf(':', userInfoSeparator);
String encodedHost = portSeparator == NOT_FOUND
@@ -1116,7 +1116,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
// Make sure we look for the port separtor *after* the user info
// separator. We have URLs with a ':' in the user info.
- int userInfoSeparator = authority.indexOf('@');
+ int userInfoSeparator = authority.lastIndexOf('@');
int portSeparator = authority.indexOf(':', userInfoSeparator);
if (portSeparator == NOT_FOUND) {
diff --git a/android/net/apf/ApfFilter.java b/android/net/apf/ApfFilter.java
index 5c2b66f6..31a1abb3 100644
--- a/android/net/apf/ApfFilter.java
+++ b/android/net/apf/ApfFilter.java
@@ -86,6 +86,14 @@ import libcore.io.IoBridge;
*/
public class ApfFilter {
+ // Helper class for specifying functional filter parameters.
+ public static class ApfConfiguration {
+ public ApfCapabilities apfCapabilities;
+ public boolean multicastFilter;
+ public boolean ieee802_3Filter;
+ public int[] ethTypeBlackList;
+ }
+
// Enums describing the outcome of receiving an RA packet.
private static enum ProcessRaResult {
MATCH, // Received RA matched a known RA
@@ -261,17 +269,16 @@ public class ApfFilter {
private int mIPv4PrefixLength;
@VisibleForTesting
- ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
- IpClient.Callback ipClientCallback, boolean multicastFilter,
- boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
- mApfCapabilities = apfCapabilities;
+ ApfFilter(ApfConfiguration config, NetworkInterface networkInterface,
+ IpClient.Callback ipClientCallback, IpConnectivityLog log) {
+ mApfCapabilities = config.apfCapabilities;
mIpClientCallback = ipClientCallback;
mNetworkInterface = networkInterface;
- mMulticastFilter = multicastFilter;
- mDrop802_3Frames = ieee802_3Filter;
+ mMulticastFilter = config.multicastFilter;
+ mDrop802_3Frames = config.ieee802_3Filter;
// Now fill the black list from the passed array
- mEthTypeBlackList = filterEthTypeBlackList(ethTypeBlackList);
+ mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
mMetricsLog = log;
@@ -1160,9 +1167,10 @@ public class ApfFilter {
* Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
* filtering using APF programs.
*/
- public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
- NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
- boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
+ public static ApfFilter maybeCreate(ApfConfiguration config,
+ NetworkInterface networkInterface, IpClient.Callback ipClientCallback) {
+ if (config == null) return null;
+ ApfCapabilities apfCapabilities = config.apfCapabilities;
if (apfCapabilities == null || networkInterface == null) return null;
if (apfCapabilities.apfVersionSupported == 0) return null;
if (apfCapabilities.maximumApfProgramSize < 512) {
@@ -1178,8 +1186,7 @@ public class ApfFilter {
Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
return null;
}
- return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
- multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
+ return new ApfFilter(config, networkInterface, ipClientCallback, new IpConnectivityLog());
}
public synchronized void shutdown() {
diff --git a/android/net/ip/IpClient.java b/android/net/ip/IpClient.java
index 2359fab4..70983c86 100644
--- a/android/net/ip/IpClient.java
+++ b/android/net/ip/IpClient.java
@@ -310,12 +310,12 @@ public class IpClient extends StateMachine {
return this;
}
- public Builder withIPv6AddrGenModeEUI64() {
+ public Builder withRandomMacAddress() {
mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
return this;
}
- public Builder withIPv6AddrGenModeStablePrivacy() {
+ public Builder withStableMacAddress() {
mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
return this;
}
@@ -1429,15 +1429,15 @@ public class IpClient extends StateMachine {
@Override
public void enter() {
+ ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+ apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+ apfConfig.multicastFilter = mMulticastFiltering;
// Get the Configuration for ApfFilter from Context
- final boolean filter802_3Frames =
+ apfConfig.ieee802_3Filter =
mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
- final int[] ethTypeBlackList = mContext.getResources().getIntArray(
- R.array.config_apfEthTypeBlackList);
-
- mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
- mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+ apfConfig.ethTypeBlackList =
+ mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+ mApfFilter = ApfFilter.maybeCreate(apfConfig, mNetworkInterface, mCallback);
// TODO: investigate the effects of any multicast filtering racing/interfering with the
// rest of this IP configuration startup.
if (mApfFilter == null) {
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index b12cb32c..38981453 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -88,16 +88,6 @@ public class IpManager extends IpClient {
return this;
}
@Override
- public Builder withIPv6AddrGenModeEUI64() {
- super.withIPv6AddrGenModeEUI64();
- return this;
- }
- @Override
- public Builder withIPv6AddrGenModeStablePrivacy() {
- super.withIPv6AddrGenModeStablePrivacy();
- return this;
- }
- @Override
public Builder withNetwork(Network network) {
super.withNetwork(network);
return this;
diff --git a/android/net/metrics/ConnectStats.java b/android/net/metrics/ConnectStats.java
index 2495cab1..b320b755 100644
--- a/android/net/metrics/ConnectStats.java
+++ b/android/net/metrics/ConnectStats.java
@@ -119,7 +119,8 @@ public class ConnectStats {
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("ConnectStats(").append(netId).append(", ");
+ StringBuilder builder =
+ new StringBuilder("ConnectStats(").append("netId=").append(netId).append(", ");
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
diff --git a/android/net/metrics/DefaultNetworkEvent.java b/android/net/metrics/DefaultNetworkEvent.java
index eb61c153..8ff8e4f3 100644
--- a/android/net/metrics/DefaultNetworkEvent.java
+++ b/android/net/metrics/DefaultNetworkEvent.java
@@ -20,44 +20,72 @@ import static android.net.ConnectivityManager.NETID_UNSET;
import android.net.NetworkCapabilities;
+import com.android.internal.util.BitUtils;
+
+import java.util.StringJoiner;
+
/**
* An event recorded by ConnectivityService when there is a change in the default network.
* {@hide}
*/
public class DefaultNetworkEvent {
- // The ID of the network that has become the new default or NETID_UNSET if none.
+ // The creation time in milliseconds of this DefaultNetworkEvent.
+ public final long creationTimeMs;
+ // The network ID of the network or NETID_UNSET if none.
public int netId = NETID_UNSET;
- // The list of transport types of the new default network, for example TRANSPORT_WIFI, as
- // defined in NetworkCapabilities.java.
- public int[] transportTypes = new int[0];
- // The ID of the network that was the default before or NETID_UNSET if none.
- public int prevNetId = NETID_UNSET;
- // Whether the previous network had IPv4/IPv6 connectivity.
- public boolean prevIPv4;
- public boolean prevIPv6;
+ // The list of transport types, as defined in NetworkCapabilities.java.
+ public int transports;
+ // The list of transport types of the last previous default network.
+ public int previousTransports;
+ // Whether the network has IPv4/IPv6 connectivity.
+ public boolean ipv4;
+ public boolean ipv6;
+ // The initial network score when this network became the default network.
+ public int initialScore;
+ // The initial network score when this network stopped being the default network.
+ public int finalScore;
+ // The total duration in milliseconds this network was the default network.
+ public long durationMs;
+ // The total duration in milliseconds this network was the default network and was validated.
+ public long validatedMs;
+
+ public DefaultNetworkEvent(long timeMs) {
+ creationTimeMs = timeMs;
+ }
+
+ /** Update the durationMs of this DefaultNetworkEvent for the given current time. */
+ public void updateDuration(long timeMs) {
+ durationMs = timeMs - creationTimeMs;
+ }
@Override
public String toString() {
- String prevNetwork = String.valueOf(prevNetId);
- String newNetwork = String.valueOf(netId);
- if (prevNetId != 0) {
- prevNetwork += ":" + ipSupport();
+ StringJoiner j = new StringJoiner(", ", "DefaultNetworkEvent(", ")");
+ j.add("netId=" + netId);
+ for (int t : BitUtils.unpackBits(transports)) {
+ j.add(NetworkCapabilities.transportNameOf(t));
+ }
+ j.add("ip=" + ipSupport());
+ if (initialScore > 0) {
+ j.add("initial_score=" + initialScore);
}
- if (netId != 0) {
- newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+ if (finalScore > 0) {
+ j.add("final_score=" + finalScore);
}
- return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+ j.add(String.format("duration=%.0fs", durationMs / 1000.0));
+ j.add(String.format("validation=%4.1f%%", (validatedMs * 100.0) / durationMs));
+ return j.toString();
}
private String ipSupport() {
- if (prevIPv4 && prevIPv6) {
+ if (ipv4 && ipv6) {
return "IPv4v6";
}
- if (prevIPv6) {
+ if (ipv6) {
return "IPv6";
}
- if (prevIPv4) {
+ if (ipv4) {
return "IPv4";
}
return "NONE";
diff --git a/android/net/metrics/DnsEvent.java b/android/net/metrics/DnsEvent.java
index 81b098bb..5aa705b0 100644
--- a/android/net/metrics/DnsEvent.java
+++ b/android/net/metrics/DnsEvent.java
@@ -85,7 +85,8 @@ final public class DnsEvent {
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("DnsEvent(").append(netId).append(", ");
+ StringBuilder builder =
+ new StringBuilder("DnsEvent(").append("netId=").append(netId).append(", ");
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
diff --git a/android/net/metrics/NetworkEvent.java b/android/net/metrics/NetworkEvent.java
index 4df3bf09..1999e78d 100644
--- a/android/net/metrics/NetworkEvent.java
+++ b/android/net/metrics/NetworkEvent.java
@@ -60,29 +60,25 @@ public final class NetworkEvent implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
- public final int netId;
public final @EventType int eventType;
public final long durationMs;
- public NetworkEvent(int netId, @EventType int eventType, long durationMs) {
- this.netId = netId;
+ public NetworkEvent(@EventType int eventType, long durationMs) {
this.eventType = eventType;
this.durationMs = durationMs;
}
- public NetworkEvent(int netId, @EventType int eventType) {
- this(netId, eventType, 0);
+ public NetworkEvent(@EventType int eventType) {
+ this(eventType, 0);
}
private NetworkEvent(Parcel in) {
- netId = in.readInt();
eventType = in.readInt();
durationMs = in.readLong();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
out.writeInt(eventType);
out.writeLong(durationMs);
}
@@ -105,8 +101,8 @@ public final class NetworkEvent implements Parcelable {
@Override
public String toString() {
- return String.format("NetworkEvent(%d, %s, %dms)",
- netId, Decoder.constants.get(eventType), durationMs);
+ return String.format("NetworkEvent(%s, %dms)",
+ Decoder.constants.get(eventType), durationMs);
}
final static class Decoder {
diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java
index cbf3fc8c..8f1a5c42 100644
--- a/android/net/metrics/WakeupEvent.java
+++ b/android/net/metrics/WakeupEvent.java
@@ -16,6 +16,10 @@
package android.net.metrics;
+import android.net.MacAddress;
+
+import java.util.StringJoiner;
+
/**
* An event logged when NFLOG notifies userspace of a wakeup packet for
* watched interfaces.
@@ -23,12 +27,35 @@ package android.net.metrics;
*/
public class WakeupEvent {
public String iface;
- public long timestampMs;
public int uid;
+ public int ethertype;
+ public byte[] dstHwAddr;
+ public String srcIp;
+ public String dstIp;
+ public int ipNextHeader;
+ public int srcPort;
+ public int dstPort;
+ public long timestampMs;
@Override
public String toString() {
- return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)",
- timestampMs, timestampMs, iface, uid);
+ StringJoiner j = new StringJoiner(", ", "WakeupEvent(", ")");
+ j.add(String.format("%tT.%tL", timestampMs, timestampMs));
+ j.add(iface);
+ j.add("uid: " + Integer.toString(uid));
+ j.add("eth=0x" + Integer.toHexString(ethertype));
+ j.add("dstHw=" + MacAddress.stringAddrFromByteAddr(dstHwAddr));
+ if (ipNextHeader > 0) {
+ j.add("ipNxtHdr=" + ipNextHeader);
+ j.add("srcIp=" + srcIp);
+ j.add("dstIp=" + dstIp);
+ if (srcPort > -1) {
+ j.add("srcPort=" + srcPort);
+ }
+ if (dstPort > -1) {
+ j.add("dstPort=" + dstPort);
+ }
+ }
+ return j.toString();
}
}
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
index 97e83f96..1ba97771 100644
--- a/android/net/metrics/WakeupStats.java
+++ b/android/net/metrics/WakeupStats.java
@@ -16,8 +16,12 @@
package android.net.metrics;
+import android.net.MacAddress;
import android.os.Process;
import android.os.SystemClock;
+import android.util.SparseIntArray;
+
+import java.util.StringJoiner;
/**
* An event logged per interface and that aggregates WakeupEvents for that interface.
@@ -38,6 +42,13 @@ public class WakeupStats {
public long noUidWakeups = 0;
public long durationSec = 0;
+ public long l2UnicastCount = 0;
+ public long l2MulticastCount = 0;
+ public long l2BroadcastCount = 0;
+
+ public final SparseIntArray ethertypes = new SparseIntArray();
+ public final SparseIntArray ipNextHeaders = new SparseIntArray();
+
public WakeupStats(String iface) {
this.iface = iface;
}
@@ -68,20 +79,56 @@ public class WakeupStats {
}
break;
}
+
+ switch (MacAddress.macAddressType(ev.dstHwAddr)) {
+ case UNICAST:
+ l2UnicastCount++;
+ break;
+ case MULTICAST:
+ l2MulticastCount++;
+ break;
+ case BROADCAST:
+ l2BroadcastCount++;
+ break;
+ default:
+ break;
+ }
+
+ increment(ethertypes, ev.ethertype);
+ if (ev.ipNextHeader >= 0) {
+ increment(ipNextHeaders, ev.ipNextHeader);
+ }
}
@Override
public String toString() {
updateDuration();
- return new StringBuilder()
- .append("WakeupStats(").append(iface)
- .append(", total: ").append(totalWakeups)
- .append(", root: ").append(rootWakeups)
- .append(", system: ").append(systemWakeups)
- .append(", apps: ").append(applicationWakeups)
- .append(", non-apps: ").append(nonApplicationWakeups)
- .append(", no uid: ").append(noUidWakeups)
- .append(", ").append(durationSec).append("s)")
- .toString();
+ StringJoiner j = new StringJoiner(", ", "WakeupStats(", ")");
+ j.add(iface);
+ j.add("" + durationSec + "s");
+ j.add("total: " + totalWakeups);
+ j.add("root: " + rootWakeups);
+ j.add("system: " + systemWakeups);
+ j.add("apps: " + applicationWakeups);
+ j.add("non-apps: " + nonApplicationWakeups);
+ j.add("no uid: " + noUidWakeups);
+ j.add(String.format("l2 unicast/multicast/broadcast: %d/%d/%d",
+ l2UnicastCount, l2MulticastCount, l2BroadcastCount));
+ for (int i = 0; i < ethertypes.size(); i++) {
+ int eth = ethertypes.keyAt(i);
+ int count = ethertypes.valueAt(i);
+ j.add(String.format("ethertype 0x%x: %d", eth, count));
+ }
+ for (int i = 0; i < ipNextHeaders.size(); i++) {
+ int proto = ipNextHeaders.keyAt(i);
+ int count = ipNextHeaders.valueAt(i);
+ j.add(String.format("ipNxtHdr %d: %d", proto, count));
+ }
+ return j.toString();
+ }
+
+ private static void increment(SparseIntArray counters, int key) {
+ int newcount = counters.get(key, 0) + 1;
+ counters.put(key, newcount);
}
}
diff --git a/android/net/wifi/WifiInfo.java b/android/net/wifi/WifiInfo.java
index a367b231..bf8fed1c 100644
--- a/android/net/wifi/WifiInfo.java
+++ b/android/net/wifi/WifiInfo.java
@@ -348,6 +348,9 @@ public class WifiInfo implements Parcelable {
* quotation marks. Otherwise, it is returned as a string of hex digits. The
* SSID may be &lt;unknown ssid&gt; if there is no network currently connected,
* or if the caller has insufficient permissions to access the SSID.
+ *
+ * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method
+ * always returned the SSID with no quotes around it.
* @return the SSID
*/
public String getSSID() {
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index c2959d5e..66fabf33 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -1127,7 +1128,7 @@ public class WifiManager {
*/
private int addOrUpdateNetwork(WifiConfiguration config) {
try {
- return mService.addOrUpdateNetwork(config);
+ return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1148,7 +1149,7 @@ public class WifiManager {
*/
public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
try {
- if (!mService.addOrUpdatePasspointConfiguration(config)) {
+ if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1165,7 +1166,7 @@ public class WifiManager {
*/
public void removePasspointConfiguration(String fqdn) {
try {
- if (!mService.removePasspointConfiguration(fqdn)) {
+ if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1251,7 +1252,7 @@ public class WifiManager {
*/
public boolean removeNetwork(int netId) {
try {
- return mService.removeNetwork(netId);
+ return mService.removeNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1297,7 +1298,7 @@ public class WifiManager {
boolean success;
try {
- success = mService.enableNetwork(netId, attemptConnect);
+ success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1323,7 +1324,7 @@ public class WifiManager {
*/
public boolean disableNetwork(int netId) {
try {
- return mService.disableNetwork(netId);
+ return mService.disableNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1336,7 +1337,7 @@ public class WifiManager {
*/
public boolean disconnect() {
try {
- mService.disconnect();
+ mService.disconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1351,7 +1352,7 @@ public class WifiManager {
*/
public boolean reconnect() {
try {
- mService.reconnect();
+ mService.reconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1366,7 +1367,7 @@ public class WifiManager {
*/
public boolean reassociate() {
try {
- mService.reassociate();
+ mService.reassociate(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1739,7 +1740,7 @@ public class WifiManager {
@Deprecated
public boolean saveConfiguration() {
try {
- return mService.saveConfiguration();
+ return mService.saveConfiguration(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1748,13 +1749,12 @@ public class WifiManager {
/**
* Set the country code.
* @param countryCode country code in ISO 3166 format.
- * @param persist {@code true} if this needs to be remembered
*
* @hide
*/
- public void setCountryCode(String country, boolean persist) {
+ public void setCountryCode(@NonNull String country) {
try {
- mService.setCountryCode(country, persist);
+ mService.setCountryCode(country);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1803,18 +1803,14 @@ public class WifiManager {
/**
* Enable or disable Wi-Fi.
- *
- * Note: This method will return false if wifi cannot be enabled (e.g., an incompatible mode
- * where the user has enabled tethering or Airplane Mode).
- *
- * Applications need to have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
- * permission to toggle wifi. Callers without the permissions will trigger a
- * {@link java.lang.SecurityException}.
+ * <p>
+ * Applications must have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+ * permission to toggle wifi.
*
* @param enabled {@code true} to enable, {@code false} to disable.
- * @return {@code true} if the operation succeeds (or if the existing state
- * is the same as the requested state). False if wifi cannot be toggled on/off when the
- * request is made.
+ * @return {@code false} if the request cannot be satisfied; {@code true} indicates that wifi is
+ * either already in the requested state, or in progress toward the requested state.
+ * @throws {@link java.lang.SecurityException} if the caller is missing required permissions.
*/
public boolean setWifiEnabled(boolean enabled) {
try {
@@ -2060,7 +2056,7 @@ public class WifiManager {
}
mLOHSCallbackProxy = null;
try {
- mService.stopLocalOnlyHotspot();
+ mService.stopLocalOnlyHotspot(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2179,7 +2175,7 @@ public class WifiManager {
@RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
try {
- mService.setWifiApConfiguration(wifiConfig);
+ mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -2951,7 +2947,7 @@ public class WifiManager {
public void disableEphemeralNetwork(String SSID) {
if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
try {
- mService.disableEphemeralNetwork(SSID);
+ mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2990,7 +2986,7 @@ public class WifiManager {
*/
public Messenger getWifiServiceMessenger() {
try {
- return mService.getWifiServiceMessenger();
+ return mService.getWifiServiceMessenger(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3441,6 +3437,7 @@ public class WifiManager {
* Set wifi verbose log. Called from developer settings.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void enableVerboseLogging (int verbose) {
try {
mService.enableVerboseLogging(verbose);
@@ -3519,7 +3516,7 @@ public class WifiManager {
*/
public void factoryReset() {
try {
- mService.factoryReset();
+ mService.factoryReset(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3546,7 +3543,7 @@ public class WifiManager {
*/
public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
try {
- return mService.setEnableAutoJoinWhenAssociated(enabled);
+ return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
index 7405e82e..c8aea3c4 100644
--- a/android/net/wifi/rtt/RangingResultCallback.java
+++ b/android/net/wifi/rtt/RangingResultCallback.java
@@ -36,7 +36,7 @@ import java.util.List;
*/
public abstract class RangingResultCallback {
/** @hide */
- @IntDef({STATUS_CODE_FAIL})
+ @IntDef({STATUS_CODE_FAIL, STATUS_CODE_FAIL_RTT_NOT_AVAILABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface RangingOperationStatus {
}
@@ -47,6 +47,14 @@ public abstract class RangingResultCallback {
public static final int STATUS_CODE_FAIL = 1;
/**
+ * A failure code for the whole ranging request operation. Indicates that the request failed due
+ * to RTT not being available - e.g. Wi-Fi was disabled. Use the
+ * {@link WifiRttManager#isAvailable()} and {@link WifiRttManager#ACTION_WIFI_RTT_STATE_CHANGED}
+ * to track RTT availability.
+ */
+ public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2;
+
+ /**
* Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
* devices specified in the request was attempted.
*
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
index 435bb377..128d6c91 100644
--- a/android/net/wifi/rtt/WifiRttManager.java
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -3,15 +3,19 @@ package android.net.wifi.rtt;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static android.Manifest.permission.CHANGE_WIFI_STATE;
+import static android.Manifest.permission.LOCATION_HARDWARE;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.WorkSource;
import android.util.Log;
import java.util.List;
@@ -22,11 +26,18 @@ import java.util.List;
* <p>
* The devices which can be ranged include:
* <li>Access Points (APs)
+ * <li>Wi-Fi Aware peers
* <p>
* Ranging requests are triggered using
* {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of
* successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
* callback.
+ * <p>
+ * Wi-Fi RTT may not be usable at some points, e.g. when Wi-Fi is disabled. To validate that
+ * the functionality is available use the {@link #isAvailable()} function. To track
+ * changes in RTT usability register for the {@link #ACTION_WIFI_RTT_STATE_CHANGED}
+ * broadcast. Note that this broadcast is not sticky - you should register for it and then
+ * check the above API to avoid a race condition.
*
* @hide RTT_API
*/
@@ -38,6 +49,18 @@ public class WifiRttManager {
private final Context mContext;
private final IWifiRttManager mService;
+ /**
+ * Broadcast intent action to indicate that the state of Wi-Fi RTT availability has changed.
+ * Use the {@link #isAvailable()} to query the current status.
+ * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
+ * the broadcast to check the current state of Wi-Fi RTT.
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_RTT_STATE_CHANGED =
+ "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
+
/** @hide */
public WifiRttManager(Context context, IWifiRttManager service) {
mContext = context;
@@ -45,6 +68,22 @@ public class WifiRttManager {
}
/**
+ * Returns the current status of RTT API: whether or not RTT is available. To track
+ * changes in the state of RTT API register for the
+ * {@link #ACTION_WIFI_RTT_STATE_CHANGED} broadcast.
+ *
+ * @return A boolean indicating whether the app can use the RTT API at this time (true) or
+ * not (false).
+ */
+ public boolean isAvailable() {
+ try {
+ return mService.isAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
* Results will be returned in the {@link RangingResultCallback} set of callbacks.
*
@@ -58,21 +97,63 @@ public class WifiRttManager {
@RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
public void startRanging(RangingRequest request, RangingResultCallback callback,
@Nullable Handler handler) {
+ startRanging(null, request, callback, handler);
+ }
+
+ /**
+ * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+ * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+ *
+ * @param workSource A mechanism to specify an alternative work-source for the request.
+ * @param request A request specifying a set of devices whose distance measurements are
+ * requested.
+ * @param callback A callback for the result of the ranging request.
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code
+ * callback} object. If a null is provided then the application's main thread
+ * will be used.
+ *
+ * @hide (@SystemApi)
+ */
+ @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE,
+ ACCESS_WIFI_STATE})
+ public void startRanging(@Nullable WorkSource workSource, RangingRequest request,
+ RangingResultCallback callback, @Nullable Handler handler) {
if (VDBG) {
- Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler="
- + handler);
+ Log.v(TAG, "startRanging: workSource=" + workSource + ", request=" + request
+ + ", callback=" + callback + ", handler=" + handler);
}
Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
Binder binder = new Binder();
try {
- mService.startRanging(binder, mContext.getOpPackageName(), request,
+ mService.startRanging(binder, mContext.getOpPackageName(), workSource, request,
new RttCallbackProxy(looper, callback));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * Cancel all ranging requests for the specified work sources. The requests have been requested
+ * using {@link #startRanging(WorkSource, RangingRequest, RangingResultCallback, Handler)}.
+ *
+ * @param workSource The work-sources of the requesters.
+ *
+ * @hide (@SystemApi)
+ */
+ @RequiresPermission(allOf = {LOCATION_HARDWARE})
+ public void cancelRanging(WorkSource workSource) {
+ if (VDBG) {
+ Log.v(TAG, "cancelRanging: workSource=" + workSource);
+ }
+
+ try {
+ mService.cancelRanging(workSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static class RttCallbackProxy extends IRttCallback.Stub {
private final Handler mHandler;
private final RangingResultCallback mCallback;
diff --git a/android/os/BatteryManager.java b/android/os/BatteryManager.java
index f715f507..6e0f70c1 100644
--- a/android/os/BatteryManager.java
+++ b/android/os/BatteryManager.java
@@ -19,6 +19,7 @@ package android.os;
import android.annotation.SystemService;
import android.content.Context;
import android.hardware.health.V1_0.Constants;
+
import com.android.internal.app.IBatteryStats;
/**
@@ -33,39 +34,39 @@ public class BatteryManager {
* integer containing the current status constant.
*/
public static final String EXTRA_STATUS = "status";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the current health constant.
*/
public static final String EXTRA_HEALTH = "health";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* boolean indicating whether a battery is present.
*/
public static final String EXTRA_PRESENT = "present";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer field containing the current battery level, from 0 to
* {@link #EXTRA_SCALE}.
*/
public static final String EXTRA_LEVEL = "level";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the maximum battery level.
*/
public static final String EXTRA_SCALE = "scale";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the resource ID of a small status bar icon
* indicating the current battery state.
*/
public static final String EXTRA_ICON_SMALL = "icon-small";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer indicating whether the device is plugged in to a power
@@ -73,19 +74,19 @@ public class BatteryManager {
* types of power sources.
*/
public static final String EXTRA_PLUGGED = "plugged";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the current battery voltage level.
*/
public static final String EXTRA_VOLTAGE = "voltage";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the current battery temperature.
*/
public static final String EXTRA_TEMPERATURE = "temperature";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* String describing the technology of the current battery.
@@ -216,6 +217,7 @@ public class BatteryManager {
*/
public static final int BATTERY_PROPERTY_STATUS = 6;
+ private final Context mContext;
private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -223,6 +225,7 @@ public class BatteryManager {
* @removed Was previously made visible by accident.
*/
public BatteryManager() {
+ mContext = null;
mBatteryStats = IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME));
mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
@@ -230,8 +233,10 @@ public class BatteryManager {
}
/** {@hide} */
- public BatteryManager(IBatteryStats batteryStats,
+ public BatteryManager(Context context,
+ IBatteryStats batteryStats,
IBatteryPropertiesRegistrar batteryPropertiesRegistrar) {
+ mContext = context;
mBatteryStats = batteryStats;
mBatteryPropertiesRegistrar = batteryPropertiesRegistrar;
}
@@ -278,16 +283,23 @@ public class BatteryManager {
}
/**
- * Return the value of a battery property of integer type. If the
- * platform does not provide the property queried, this value will
- * be Integer.MIN_VALUE.
+ * Return the value of a battery property of integer type.
*
* @param id identifier of the requested property
*
- * @return the property value, or Integer.MIN_VALUE if not supported.
+ * @return the property value. If the property is not supported or there is any other error,
+ * return (a) 0 if {@code targetSdkVersion < VERSION_CODES.P} or (b) Integer.MIN_VALUE
+ * if {@code targetSdkVersion >= VERSION_CODES.P}.
*/
public int getIntProperty(int id) {
- return (int)queryProperty(id);
+ long value = queryProperty(id);
+ if (value == Long.MIN_VALUE && mContext != null
+ && mContext.getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.P) {
+ return Integer.MIN_VALUE;
+ }
+
+ return (int) value;
}
/**
diff --git a/android/os/BatteryStatsInternal.java b/android/os/BatteryStatsInternal.java
new file mode 100644
index 00000000..b0436eb5
--- /dev/null
+++ b/android/os/BatteryStatsInternal.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Battery stats local system service interface. This is used to pass internal data out of
+ * BatteryStatsImpl.
+ *
+ * @hide Only for use within Android OS.
+ */
+public abstract class BatteryStatsInternal {
+ /**
+ * Returns the wifi interfaces.
+ */
+ public abstract String[] getWifiIfaces();
+
+ /**
+ * Returns the mobile data interfaces.
+ */
+ public abstract String[] getMobileIfaces();
+}
diff --git a/android/os/Binder.java b/android/os/Binder.java
index 2bfb0138..b5bcd02c 100644
--- a/android/os/Binder.java
+++ b/android/os/Binder.java
@@ -193,6 +193,19 @@ public class Binder implements IBinder {
}
/**
+ * Reset the given interface back to the default blocking behavior,
+ * reverting any changes made by {@link #allowBlocking(IBinder)}.
+ *
+ * @hide
+ */
+ public static IBinder defaultBlocking(IBinder binder) {
+ if (binder instanceof BinderProxy) {
+ ((BinderProxy) binder).mWarnOnBlocking = sWarnOnBlocking;
+ }
+ return binder;
+ }
+
+ /**
* Inherit the current {@link #allowBlocking(IBinder)} value from one given
* interface to another.
*
diff --git a/android/os/Binder_Delegate.java b/android/os/Binder_Delegate.java
new file mode 100644
index 00000000..03596dee
--- /dev/null
+++ b/android/os/Binder_Delegate.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate overriding selected methods of android.os.Binder
+ *
+ * Through the layoutlib_create tool, selected methods of Binder have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Binder_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Binder_Delegate> sManager =
+ new DelegateManager<>(Binder_Delegate.class);
+ private static long sFinalizer = -1;
+
+ @LayoutlibDelegate
+ /*package*/ static long getNativeBBinderHolder() {
+ return sManager.addNewDelegate(new Binder_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long getNativeFinalizer() {
+ synchronized (Binder_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
+ }
+}
diff --git a/android/os/ConfigUpdate.java b/android/os/ConfigUpdate.java
index 13968779..94a44ec3 100644
--- a/android/os/ConfigUpdate.java
+++ b/android/os/ConfigUpdate.java
@@ -68,13 +68,6 @@ public final class ConfigUpdate {
= "android.intent.action.UPDATE_CT_LOGS";
/**
- * Update system wide timezone data.
- * @hide
- */
- @SystemApi
- public static final String ACTION_UPDATE_TZDATA = "android.intent.action.UPDATE_TZDATA";
-
- /**
* Update language detection model file.
* @hide
*/
diff --git a/android/os/Debug.java b/android/os/Debug.java
index 017c2134..2acf36fe 100644
--- a/android/os/Debug.java
+++ b/android/os/Debug.java
@@ -16,14 +16,16 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Context;
import android.util.Log;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.TypedProperties;
-import dalvik.bytecode.OpcodeInfo;
import dalvik.system.VMDebug;
import org.apache.harmony.dalvik.ddmc.Chunk;
@@ -48,8 +50,6 @@ import java.util.HashMap;
import java.util.Map;
-
-
/**
* Provides various debugging methods for Android applications, including
* tracing and allocation counts.
@@ -1959,13 +1959,7 @@ public final class Debug
*/
@Deprecated
public static class InstructionCount {
- private static final int NUM_INSTR =
- OpcodeInfo.MAXIMUM_PACKED_VALUE + 1;
-
- private int[] mCounts;
-
public InstructionCount() {
- mCounts = new int[NUM_INSTR];
}
/**
@@ -1975,13 +1969,7 @@ public final class Debug
* @return true if counting was started
*/
public boolean resetAndStart() {
- try {
- VMDebug.startInstructionCounting();
- VMDebug.resetInstructionCount();
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -1989,13 +1977,7 @@ public final class Debug
* counting process.
*/
public boolean collect() {
- try {
- VMDebug.stopInstructionCounting();
- VMDebug.getInstructionCount(mCounts);
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -2003,13 +1985,7 @@ public final class Debug
* all threads).
*/
public int globalTotal() {
- int count = 0;
-
- for (int i = 0; i < NUM_INSTR; i++) {
- count += mCounts[i];
- }
-
- return count;
+ return 0;
}
/**
@@ -2017,15 +1993,7 @@ public final class Debug
* executed globally.
*/
public int globalMethodInvocations() {
- int count = 0;
-
- for (int i = 0; i < NUM_INSTR; i++) {
- if (OpcodeInfo.isInvoke(i)) {
- count += mCounts[i];
- }
- }
-
- return count;
+ return 0;
}
}
@@ -2382,4 +2350,24 @@ public final class Debug
public static String getCaller() {
return getCaller(Thread.currentThread().getStackTrace(), 0);
}
+
+ /**
+ * Attach a library as a jvmti agent to the current runtime.
+ *
+ * @param library library containing the agent
+ * @param options options passed to the agent
+ *
+ * @throws IOException If the agent could not be attached
+ */
+ public static void attachJvmtiAgent(@NonNull String library, @Nullable String options)
+ throws IOException {
+ Preconditions.checkNotNull(library);
+ Preconditions.checkArgument(!library.contains("="));
+
+ if (options == null) {
+ VMDebug.attachAgent(library);
+ } else {
+ VMDebug.attachAgent(library + "=" + options);
+ }
+ }
}
diff --git a/android/os/Environment.java b/android/os/Environment.java
index 5b0e5bbc..f977c1de 100644
--- a/android/os/Environment.java
+++ b/android/os/Environment.java
@@ -836,7 +836,6 @@ public class Environment {
* physically removed.
*/
public static boolean isExternalStorageRemovable() {
- if (isStorageDisabled()) return false;
final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageRemovable(externalDir);
}
@@ -875,7 +874,6 @@ public class Environment {
* boolean)
*/
public static boolean isExternalStorageEmulated() {
- if (isStorageDisabled()) return false;
final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageEmulated(externalDir);
}
@@ -951,9 +949,6 @@ public class Environment {
return cur;
}
- private static boolean isStorageDisabled() {
- return SystemProperties.getBoolean("config.disable_storage", false);
- }
/**
* If the given path exists on emulated external storage, return the
diff --git a/android/os/FileUtils.java b/android/os/FileUtils.java
index 56d6e0a6..7c53ec19 100644
--- a/android/os/FileUtils.java
+++ b/android/os/FileUtils.java
@@ -320,8 +320,17 @@ public class FileUtils {
* is {@code filename}.
*/
public static void bytesToFile(String filename, byte[] content) throws IOException {
- try (FileOutputStream fos = new FileOutputStream(filename)) {
- fos.write(content);
+ if (filename.startsWith("/proc/")) {
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try (FileOutputStream fos = new FileOutputStream(filename)) {
+ fos.write(content);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ } else {
+ try (FileOutputStream fos = new FileOutputStream(filename)) {
+ fos.write(content);
+ }
}
}
diff --git a/android/os/HidlSupport.java b/android/os/HidlSupport.java
index 7dec4d72..3544ea1e 100644
--- a/android/os/HidlSupport.java
+++ b/android/os/HidlSupport.java
@@ -156,4 +156,27 @@ public class HidlSupport {
// Should not reach here.
throw new UnsupportedOperationException();
}
+
+ /**
+ * Test that two interfaces are equal. This is the Java equivalent to C++
+ * interfacesEqual function.
+ * This essentially calls .equals on the internal binder objects (via Binder()).
+ * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder}
+ * object, and they are compared in {@link HwRemoteBinder#equals}.
+ * - If both interfaces are stubs, asBinder() returns the object itself. By default,
+ * auto-generated IFoo.Stub does not override equals(), but an implementation can
+ * optionally override it, and {@code interfacesEqual} will use it here.
+ */
+ public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
+ if (lft == rgt) {
+ return true;
+ }
+ if (lft == null || rgt == null) {
+ return false;
+ }
+ if (!(rgt instanceof IHwInterface)) {
+ return false;
+ }
+ return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder());
+ }
}
diff --git a/android/os/HwBinder.java b/android/os/HwBinder.java
index 270e63f4..5e2a0815 100644
--- a/android/os/HwBinder.java
+++ b/android/os/HwBinder.java
@@ -16,10 +16,10 @@
package android.os;
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
import libcore.util.NativeAllocationRegistry;
+import java.util.NoSuchElementException;
+
/** @hide */
public abstract class HwBinder implements IHwBinder {
private static final String TAG = "HwBinder";
@@ -46,9 +46,16 @@ public abstract class HwBinder implements IHwBinder {
public native final void registerService(String serviceName)
throws RemoteException;
- public static native final IHwBinder getService(
+ public static final IHwBinder getService(
String iface,
String serviceName)
+ throws RemoteException, NoSuchElementException {
+ return getService(iface, serviceName, false /* retry */);
+ }
+ public static native final IHwBinder getService(
+ String iface,
+ String serviceName,
+ boolean retry)
throws RemoteException, NoSuchElementException;
public static native final void configureRpcThreadpool(
diff --git a/android/os/HwBlob.java b/android/os/HwBlob.java
index 88226f0a..5e9b9ae3 100644
--- a/android/os/HwBlob.java
+++ b/android/os/HwBlob.java
@@ -43,6 +43,18 @@ public class HwBlob {
public native final double getDouble(long offset);
public native final String getString(long offset);
+ /**
+ The copyTo... methods copy the blob's data, starting from the given
+ byte offset, into the array. A total of "size" _elements_ are copied.
+ */
+ public native final void copyToBoolArray(long offset, boolean[] array, int size);
+ public native final void copyToInt8Array(long offset, byte[] array, int size);
+ public native final void copyToInt16Array(long offset, short[] array, int size);
+ public native final void copyToInt32Array(long offset, int[] array, int size);
+ public native final void copyToInt64Array(long offset, long[] array, int size);
+ public native final void copyToFloatArray(long offset, float[] array, int size);
+ public native final void copyToDoubleArray(long offset, double[] array, int size);
+
public native final void putBool(long offset, boolean x);
public native final void putInt8(long offset, byte x);
public native final void putInt16(long offset, short x);
@@ -52,6 +64,14 @@ public class HwBlob {
public native final void putDouble(long offset, double x);
public native final void putString(long offset, String x);
+ public native final void putBoolArray(long offset, boolean[] x);
+ public native final void putInt8Array(long offset, byte[] x);
+ public native final void putInt16Array(long offset, short[] x);
+ public native final void putInt32Array(long offset, int[] x);
+ public native final void putInt64Array(long offset, long[] x);
+ public native final void putFloatArray(long offset, float[] x);
+ public native final void putDoubleArray(long offset, double[] x);
+
public native final void putBlob(long offset, HwBlob blob);
public native final long handle();
diff --git a/android/os/HwRemoteBinder.java b/android/os/HwRemoteBinder.java
index 2f89ce62..a07e42c7 100644
--- a/android/os/HwRemoteBinder.java
+++ b/android/os/HwRemoteBinder.java
@@ -63,4 +63,9 @@ public class HwRemoteBinder implements IHwBinder {
}
private long mNativeContext;
+
+ @Override
+ public final native boolean equals(Object other);
+ @Override
+ public final native int hashCode();
}
diff --git a/android/os/LocaleList.java b/android/os/LocaleList.java
index 2dc3bebb..ca9cbec9 100644
--- a/android/os/LocaleList.java
+++ b/android/os/LocaleList.java
@@ -295,7 +295,11 @@ public final class LocaleList implements Parcelable {
return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale);
}
- private static boolean isPseudoLocale(Locale locale) {
+ /**
+ * Returns true if locale is a pseudo-locale, false otherwise.
+ * {@hide}
+ */
+ public static boolean isPseudoLocale(Locale locale) {
return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
}
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index c2cf3967..10adb5a6 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -2020,8 +2020,6 @@ public final class Parcel {
@Deprecated
static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
- static native void clearFileDescriptor(FileDescriptor desc);
-
/**
* Read a byte value from the parcel at the current dataPosition().
*/
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
index 7f588adb..7556f092 100644
--- a/android/os/ParcelFileDescriptor.java
+++ b/android/os/ParcelFileDescriptor.java
@@ -683,7 +683,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
throw new IllegalStateException("Already closed");
}
final int fd = getFd();
- Parcel.clearFileDescriptor(mFd);
+ mFd.setInt$(-1);
writeCommStatusAndClose(Status.DETACHED, null);
mClosed = true;
mGuard.close();
diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java
index 7f4dee6e..dd4825ef 100644
--- a/android/os/PowerManager.java
+++ b/android/os/PowerManager.java
@@ -513,6 +513,53 @@ public final class PowerManager {
*/
public static final int SHUTDOWN_REASON_BATTERY_THERMAL = 6;
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ServiceType.GPS,
+ ServiceType.VIBRATION,
+ ServiceType.ANIMATION,
+ ServiceType.FULL_BACKUP,
+ ServiceType.KEYVALUE_BACKUP,
+ ServiceType.NETWORK_FIREWALL,
+ ServiceType.SCREEN_BRIGHTNESS,
+ ServiceType.SOUND,
+ ServiceType.BATTERY_STATS,
+ ServiceType.DATA_SAVER,
+ ServiceType.FORCE_ALL_APPS_STANDBY_JOBS,
+ ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS,
+ ServiceType.OPTIONAL_SENSORS,
+ })
+ public @interface ServiceType {
+ int NULL = 0;
+ int GPS = 1;
+ int VIBRATION = 2;
+ int ANIMATION = 3;
+ int FULL_BACKUP = 4;
+ int KEYVALUE_BACKUP = 5;
+ int NETWORK_FIREWALL = 6;
+ int SCREEN_BRIGHTNESS = 7;
+ int SOUND = 8;
+ int BATTERY_STATS = 9;
+ int DATA_SAVER = 10;
+
+ /**
+ * Whether the job scheduler should force app standby on all apps on battery saver or not.
+ */
+ int FORCE_ALL_APPS_STANDBY_JOBS = 11;
+
+ /**
+ * Whether the alarm manager should force app standby on all apps on battery saver or not.
+ */
+ int FORCE_ALL_APPS_STANDBY_ALARMS = 12;
+
+ /**
+ * Whether to disable non-essential sensors. (e.g. edge sensors.)
+ */
+ int OPTIONAL_SENSORS = 13;
+ }
+
final Context mContext;
final IPowerManager mService;
final Handler mHandler;
@@ -1055,15 +1102,14 @@ public final class PowerManager {
/**
* Get data about the battery saver mode for a specific service
- * @param serviceType unique key for the service, one of
- * {@link com.android.server.power.BatterySaverPolicy.ServiceType}
+ * @param serviceType unique key for the service, one of {@link ServiceType}
* @return Battery saver state data.
*
* @hide
* @see com.android.server.power.BatterySaverPolicy
* @see PowerSaveState
*/
- public PowerSaveState getPowerSaveState(int serviceType) {
+ public PowerSaveState getPowerSaveState(@ServiceType int serviceType) {
try {
return mService.getPowerSaveState(serviceType);
} catch (RemoteException e) {
diff --git a/android/os/PowerManagerInternal.java b/android/os/PowerManagerInternal.java
index a01b8ed2..77ac2651 100644
--- a/android/os/PowerManagerInternal.java
+++ b/android/os/PowerManagerInternal.java
@@ -18,6 +18,8 @@ package android.os;
import android.view.Display;
+import java.util.function.Consumer;
+
/**
* Power manager local system service interface.
*
@@ -125,6 +127,23 @@ public abstract class PowerManagerInternal {
public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
+ /**
+ * Same as {@link #registerLowPowerModeObserver} but can take a lambda.
+ */
+ public void registerLowPowerModeObserver(int serviceType, Consumer<PowerSaveState> listener) {
+ registerLowPowerModeObserver(new LowPowerModeListener() {
+ @Override
+ public int getServiceType() {
+ return serviceType;
+ }
+
+ @Override
+ public void onLowPowerModeChanged(PowerSaveState state) {
+ listener.accept(state);
+ }
+ });
+ }
+
public interface LowPowerModeListener {
int getServiceType();
void onLowPowerModeChanged(PowerSaveState state);
diff --git a/android/os/PowerSaveState.java b/android/os/PowerSaveState.java
index 7058a1dc..de1128df 100644
--- a/android/os/PowerSaveState.java
+++ b/android/os/PowerSaveState.java
@@ -27,7 +27,7 @@ public class PowerSaveState implements Parcelable {
/**
* Whether we should enable battery saver for this service.
*
- * @see com.android.server.power.BatterySaverPolicy.ServiceType
+ * @see com.android.server.power.BatterySaverPolicy
*/
public final boolean batterySaverEnabled;
/**
diff --git a/android/os/RemoteCallbackList.java b/android/os/RemoteCallbackList.java
index 2281fb6d..b9b9a18e 100644
--- a/android/os/RemoteCallbackList.java
+++ b/android/os/RemoteCallbackList.java
@@ -19,6 +19,7 @@ package android.os;
import android.util.ArrayMap;
import android.util.Slog;
+import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -399,6 +400,13 @@ public class RemoteCallbackList<E extends IInterface> {
}
}
+ /** @hide */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size());
+ pw.print(prefix); pw.print("killed: "); pw.println(mKilled);
+ pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount);
+ }
+
private void logExcessiveCallbacks() {
final long size = mCallbacks.size();
final long TOO_MANY = 3000;
diff --git a/android/os/ShellCallback.java b/android/os/ShellCallback.java
index e7fe697f..ad9fbfbf 100644
--- a/android/os/ShellCallback.java
+++ b/android/os/ShellCallback.java
@@ -35,8 +35,9 @@ public class ShellCallback implements Parcelable {
IShellCallback mShellCallback;
class MyShellCallback extends IShellCallback.Stub {
- public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
- return onOpenOutputFile(path, seLinuxContext);
+ public ParcelFileDescriptor openFile(String path, String seLinuxContext,
+ String mode) {
+ return onOpenFile(path, seLinuxContext, mode);
}
}
@@ -48,23 +49,27 @@ public class ShellCallback implements Parcelable {
}
/**
- * Ask the shell to open a file for writing. This will truncate the file if it
- * already exists. It will create the file if it doesn't exist.
+ * Ask the shell to open a file. If opening for writing, will truncate the file if it
+ * already exists and will create the file if it doesn't exist.
* @param path Path of the file to be opened/created.
* @param seLinuxContext Optional SELinux context that must be allowed to have
* access to the file; if null, nothing is required.
+ * @param mode Mode to open file in: "r" for input/reading an existing file,
+ * "r+" for reading/writing an existing file, "w" for output/writing a new file (either
+ * creating or truncating an existing one), "w+" for reading/writing a new file (either
+ * creating or truncating an existing one).
*/
- public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
- if (DEBUG) Log.d(TAG, "openOutputFile " + this + ": mLocal=" + mLocal
+ public ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode) {
+ if (DEBUG) Log.d(TAG, "openFile " + this + " mode=" + mode + ": mLocal=" + mLocal
+ " mShellCallback=" + mShellCallback);
if (mLocal) {
- return onOpenOutputFile(path, seLinuxContext);
+ return onOpenFile(path, seLinuxContext, mode);
}
if (mShellCallback != null) {
try {
- return mShellCallback.openOutputFile(path, seLinuxContext);
+ return mShellCallback.openFile(path, seLinuxContext, mode);
} catch (RemoteException e) {
Log.w(TAG, "Failure opening " + path, e);
}
@@ -72,7 +77,7 @@ public class ShellCallback implements Parcelable {
return null;
}
- public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, String mode) {
return null;
}
diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java
index 6223235e..d75219fd 100644
--- a/android/os/ShellCommand.java
+++ b/android/os/ShellCommand.java
@@ -226,10 +226,10 @@ public abstract class ShellCommand {
* Helper for just system services to ask the shell to open an output file.
* @hide
*/
- public ParcelFileDescriptor openOutputFileForSystem(String path) {
+ public ParcelFileDescriptor openFileForSystem(String path, String mode) {
try {
- ParcelFileDescriptor pfd = getShellCallback().openOutputFile(path,
- "u:r:system_server:s0");
+ ParcelFileDescriptor pfd = getShellCallback().openFile(path,
+ "u:r:system_server:s0", mode);
if (pfd != null) {
return pfd;
}
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index ee3e5bc9..f90604ab 100644
--- a/android/os/StrictMode.java
+++ b/android/os/StrictMode.java
@@ -16,6 +16,7 @@
package android.os;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
@@ -25,8 +26,26 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.net.TrafficStats;
import android.net.Uri;
+import android.os.strictmode.CleartextNetworkViolation;
+import android.os.strictmode.ContentUriWithoutPermissionViolation;
+import android.os.strictmode.CustomViolation;
+import android.os.strictmode.DiskReadViolation;
+import android.os.strictmode.DiskWriteViolation;
+import android.os.strictmode.FileUriExposedViolation;
+import android.os.strictmode.InstanceCountViolation;
+import android.os.strictmode.IntentReceiverLeakedViolation;
+import android.os.strictmode.LeakedClosableViolation;
+import android.os.strictmode.NetworkViolation;
+import android.os.strictmode.ResourceMismatchViolation;
+import android.os.strictmode.ServiceConnectionLeakedViolation;
+import android.os.strictmode.SqliteObjectLeakedViolation;
+import android.os.strictmode.UnbufferedIoViolation;
+import android.os.strictmode.UntaggedSocketViolation;
+import android.os.strictmode.Violation;
+import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Printer;
@@ -35,6 +54,7 @@ import android.util.Slog;
import android.view.IWindowManager;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.RuntimeInit;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -53,6 +73,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -140,6 +162,15 @@ public final class StrictMode {
*/
private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
+ /**
+ * Quick feature-flag that can be used to disable the defaults provided by {@link
+ * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
+ */
+ private static final boolean DISABLE = false;
+
+ // Only apply VM penalties for the same violation at this interval.
+ private static final long MIN_VM_INTERVAL_MS = 1000;
+
// Only log a duplicate stack trace to the logs every second.
private static final long MIN_LOG_INTERVAL_MS = 1000;
@@ -156,36 +187,30 @@ public final class StrictMode {
// Byte 1: Thread-policy
/** @hide */
- @TestApi
- public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
+ @TestApi public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
/** @hide */
- @TestApi
- public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
+ @TestApi public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
/** @hide */
- @TestApi
- public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
+ @TestApi public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
/**
* For StrictMode.noteSlowCall()
*
* @hide
*/
- @TestApi
- public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
+ @TestApi public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
/**
* For StrictMode.noteResourceMismatch()
*
* @hide
*/
- @TestApi
- public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
+ @TestApi public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
/** @hide */
- @TestApi
- public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
+ @TestApi public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
private static final int ALL_THREAD_DETECT_BITS =
DETECT_DISK_WRITE
@@ -202,48 +227,40 @@ public final class StrictMode {
*
* @hide
*/
- @TestApi
- public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
/**
* Note, a "VM_" bit, not thread.
*
* @hide
*/
- @TestApi
- public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
/**
* Note, a "VM_" bit, not thread.
*
* @hide
*/
- @TestApi
- public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
/** @hide */
@TestApi
public static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
+ @TestApi public static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
private static final int ALL_VM_DETECT_BITS =
DETECT_VM_CURSOR_LEAKS
@@ -354,15 +371,33 @@ public final class StrictMode {
} else {
msg = "StrictMode policy violation:";
}
- if (info.hasStackTrace()) {
- Log.d(TAG, msg + " " + info.getStackTrace());
- } else {
- Log.d(TAG, msg + " missing stack trace!");
- }
+ Log.d(TAG, msg + " " + info.getStackTrace());
};
private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
+ private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
+ new ThreadLocal<>();
+ private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();
+
+ /**
+ * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+ * provided executor when a Thread violation occurs.
+ */
+ public interface OnThreadViolationListener {
+ /** Called on a thread policy violation. */
+ void onThreadViolation(Violation v);
+ }
+
+ /**
+ * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+ * provided executor when a VM violation occurs.
+ */
+ public interface OnVmViolationListener {
+ /** Called on a VM policy violation. */
+ void onVmViolation(Violation v);
+ }
+
/** {@hide} */
@TestApi
public static void setViolationLogger(ViolationLogger listener) {
@@ -392,12 +427,16 @@ public final class StrictMode {
*/
public static final class ThreadPolicy {
/** The default, lax policy which doesn't catch anything. */
- public static final ThreadPolicy LAX = new ThreadPolicy(0);
+ public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
final int mask;
+ final OnThreadViolationListener mListener;
+ final Executor mCallbackExecutor;
- private ThreadPolicy(int mask) {
+ private ThreadPolicy(int mask, OnThreadViolationListener listener, Executor executor) {
this.mask = mask;
+ mListener = listener;
+ mCallbackExecutor = executor;
}
@Override
@@ -425,6 +464,8 @@ public final class StrictMode {
*/
public static final class Builder {
private int mMask = 0;
+ private OnThreadViolationListener mListener;
+ private Executor mExecutor;
/**
* Create a Builder that detects nothing and has no violations. (but note that {@link
@@ -590,6 +631,20 @@ public final class StrictMode {
return enable(PENALTY_DROPBOX);
}
+ /**
+ * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+ * executor every violation.
+ */
+ public Builder penaltyListener(
+ @NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
+ if (executor == null) {
+ throw new NullPointerException("executor must not be null");
+ }
+ mListener = listener;
+ mExecutor = executor;
+ return this;
+ }
+
private Builder enable(int bit) {
mMask |= bit;
return this;
@@ -609,7 +664,8 @@ public final class StrictMode {
public ThreadPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0
+ if (mListener == null
+ && mMask != 0
&& (mMask
& (PENALTY_DEATH
| PENALTY_LOG
@@ -618,7 +674,7 @@ public final class StrictMode {
== 0) {
penaltyLog();
}
- return new ThreadPolicy(mMask);
+ return new ThreadPolicy(mMask, mListener, mExecutor);
}
}
}
@@ -630,19 +686,27 @@ public final class StrictMode {
*/
public static final class VmPolicy {
/** The default, lax policy which doesn't catch anything. */
- public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
+ public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
final int mask;
+ final OnVmViolationListener mListener;
+ final Executor mCallbackExecutor;
// Map from class to max number of allowed instances in memory.
final HashMap<Class, Integer> classInstanceLimit;
- private VmPolicy(int mask, HashMap<Class, Integer> classInstanceLimit) {
+ private VmPolicy(
+ int mask,
+ HashMap<Class, Integer> classInstanceLimit,
+ OnVmViolationListener listener,
+ Executor executor) {
if (classInstanceLimit == null) {
throw new NullPointerException("classInstanceLimit == null");
}
this.mask = mask;
this.classInstanceLimit = classInstanceLimit;
+ mListener = listener;
+ mCallbackExecutor = executor;
}
@Override
@@ -670,6 +734,8 @@ public final class StrictMode {
*/
public static final class Builder {
private int mMask;
+ private OnVmViolationListener mListener;
+ private Executor mExecutor;
private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
@@ -683,6 +749,8 @@ public final class StrictMode {
mMask = base.mask;
mClassInstanceLimitNeedCow = true;
mClassInstanceLimit = base.classInstanceLimit;
+ mListener = base.mListener;
+ mExecutor = base.mCallbackExecutor;
}
/**
@@ -714,6 +782,11 @@ public final class StrictMode {
return enable(DETECT_VM_ACTIVITY_LEAKS);
}
+ /** @hide */
+ public Builder permitActivityLeaks() {
+ return disable(DETECT_VM_ACTIVITY_LEAKS);
+ }
+
/**
* Detect everything that's potentially suspect.
*
@@ -847,6 +920,11 @@ public final class StrictMode {
return enable(DETECT_VM_UNTAGGED_SOCKET);
}
+ /** @hide */
+ public Builder permitUntaggedSockets() {
+ return disable(DETECT_VM_UNTAGGED_SOCKET);
+ }
+
/**
* Crashes the whole process on violation. This penalty runs at the end of all enabled
* penalties so you'll still get your logging or other violations before the process
@@ -889,6 +967,19 @@ public final class StrictMode {
return enable(PENALTY_DROPBOX);
}
+ /**
+ * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+ */
+ public Builder penaltyListener(
+ @NonNull OnVmViolationListener listener, @NonNull Executor executor) {
+ if (executor == null) {
+ throw new NullPointerException("executor must not be null");
+ }
+ mListener = listener;
+ mExecutor = executor;
+ return this;
+ }
+
private Builder enable(int bit) {
mMask |= bit;
return this;
@@ -908,7 +999,8 @@ public final class StrictMode {
public VmPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0
+ if (mListener == null
+ && mMask != 0
&& (mMask
& (PENALTY_DEATH
| PENALTY_LOG
@@ -919,7 +1011,9 @@ public final class StrictMode {
}
return new VmPolicy(
mMask,
- mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
+ mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
+ mListener,
+ mExecutor);
}
}
}
@@ -952,9 +1046,12 @@ public final class StrictMode {
*/
public static void setThreadPolicy(final ThreadPolicy policy) {
setThreadPolicyMask(policy.mask);
+ sThreadViolationListener.set(policy.mListener);
+ sThreadViolationExecutor.set(policy.mCallbackExecutor);
}
- private static void setThreadPolicyMask(final int policyMask) {
+ /** @hide */
+ public static void setThreadPolicyMask(final int policyMask) {
// In addition to the Java-level thread-local in Dalvik's
// BlockGuard, we also need to keep a native thread-local in
// Binder in order to propagate the value across Binder calls,
@@ -991,55 +1088,6 @@ public final class StrictMode {
CloseGuard.setEnabled(enabled);
}
- /** @hide */
- public static class StrictModeViolation extends BlockGuard.BlockGuardPolicyException {
- public StrictModeViolation(int policyState, int policyViolated, String message) {
- super(policyState, policyViolated, message);
- }
- }
-
- /** @hide */
- public static class StrictModeNetworkViolation extends StrictModeViolation {
- public StrictModeNetworkViolation(int policyMask) {
- super(policyMask, DETECT_NETWORK, null);
- }
- }
-
- /** @hide */
- private static class StrictModeDiskReadViolation extends StrictModeViolation {
- public StrictModeDiskReadViolation(int policyMask) {
- super(policyMask, DETECT_DISK_READ, null);
- }
- }
-
- /** @hide */
- private static class StrictModeDiskWriteViolation extends StrictModeViolation {
- public StrictModeDiskWriteViolation(int policyMask) {
- super(policyMask, DETECT_DISK_WRITE, null);
- }
- }
-
- /** @hide */
- private static class StrictModeCustomViolation extends StrictModeViolation {
- public StrictModeCustomViolation(int policyMask, String name) {
- super(policyMask, DETECT_CUSTOM, name);
- }
- }
-
- /** @hide */
- private static class StrictModeResourceMismatchViolation extends StrictModeViolation {
- public StrictModeResourceMismatchViolation(int policyMask, Object tag) {
- super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null);
- }
- }
-
- /** @hide */
- private static class StrictModeUnbufferedIOViolation extends StrictModeViolation {
- public StrictModeUnbufferedIOViolation(int policyMask) {
- super(policyMask, DETECT_UNBUFFERED_IO, null);
- }
- }
-
/**
* Returns the bitmask of the current thread's policy.
*
@@ -1056,7 +1104,10 @@ public final class StrictMode {
// introduce VmPolicy cleanly) but this isn't particularly
// optimal for users who might call this method often. This
// should be in a thread-local and not allocate on each call.
- return new ThreadPolicy(getThreadPolicyMask());
+ return new ThreadPolicy(
+ getThreadPolicyMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
}
/**
@@ -1069,12 +1120,20 @@ public final class StrictMode {
* end of a block
*/
public static ThreadPolicy allowThreadDiskWrites() {
+ return new ThreadPolicy(
+ allowThreadDiskWritesMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
+ }
+
+ /** @hide */
+ public static int allowThreadDiskWritesMask() {
int oldPolicyMask = getThreadPolicyMask();
int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_WRITE | DETECT_DISK_READ);
if (newPolicyMask != oldPolicyMask) {
setThreadPolicyMask(newPolicyMask);
}
- return new ThreadPolicy(oldPolicyMask);
+ return oldPolicyMask;
}
/**
@@ -1085,31 +1144,66 @@ public final class StrictMode {
* @return the old policy, to be passed to setThreadPolicy to restore the policy.
*/
public static ThreadPolicy allowThreadDiskReads() {
+ return new ThreadPolicy(
+ allowThreadDiskReadsMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
+ }
+
+ /** @hide */
+ public static int allowThreadDiskReadsMask() {
int oldPolicyMask = getThreadPolicyMask();
int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_READ);
if (newPolicyMask != oldPolicyMask) {
setThreadPolicyMask(newPolicyMask);
}
- return new ThreadPolicy(oldPolicyMask);
+ return oldPolicyMask;
}
- // We don't want to flash the screen red in the system server
- // process, nor do we want to modify all the call sites of
- // conditionallyEnableDebugLogging() in the system server,
- // so instead we use this to determine if we are the system server.
- private static boolean amTheSystemServerProcess() {
- // Fast path. Most apps don't have the system server's UID.
- if (Process.myUid() != Process.SYSTEM_UID) {
- return false;
- }
+ private static ThreadPolicy allowThreadViolations() {
+ ThreadPolicy oldPolicy = getThreadPolicy();
+ setThreadPolicyMask(0);
+ return oldPolicy;
+ }
- // The settings app, though, has the system server's UID so
- // look up our stack to see if we came from the system server.
- Throwable stack = new Throwable();
- stack.fillInStackTrace();
- for (StackTraceElement ste : stack.getStackTrace()) {
- String clsName = ste.getClassName();
- if (clsName != null && clsName.startsWith("com.android.server.")) {
+ private static VmPolicy allowVmViolations() {
+ VmPolicy oldPolicy = getVmPolicy();
+ sVmPolicy = VmPolicy.LAX;
+ return oldPolicy;
+ }
+
+ /**
+ * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+ * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
+ * chase any {@link StrictMode} regressions by enabling detection when running on {@link
+ * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
+ *
+ * <p>Unbundled apps included in the system image are expected to detect and triage their own
+ * {@link StrictMode} issues separate from the OS release process, which is why we don't enable
+ * them here.
+ *
+ * @hide
+ */
+ public static boolean isBundledSystemApp(ApplicationInfo ai) {
+ if (ai == null || ai.packageName == null) {
+ // Probably system server
+ return true;
+ } else if (ai.isSystemApp()) {
+ // Ignore unbundled apps living in the wrong namespace
+ if (ai.packageName.equals("com.android.vending")
+ || ai.packageName.equals("com.android.chrome")) {
+ return false;
+ }
+
+ // Ignore bundled apps that are way too spammy
+ // STOPSHIP: burn this list down to zero
+ if (ai.packageName.equals("com.android.phone")) {
+ return false;
+ }
+
+ if (ai.packageName.equals("android")
+ || ai.packageName.startsWith("android.")
+ || ai.packageName.startsWith("com.android.")) {
return true;
}
}
@@ -1117,81 +1211,81 @@ public final class StrictMode {
}
/**
- * Enable DropBox logging for debug phone builds.
+ * Initialize default {@link ThreadPolicy} for the current thread.
*
* @hide
*/
- public static boolean conditionallyEnableDebugLogging() {
- boolean doFlashes =
- SystemProperties.getBoolean(VISUAL_PROPERTY, false) && !amTheSystemServerProcess();
- final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false);
-
- // For debug builds, log event loop stalls to dropbox for analysis.
- // Similar logic also appears in ActivityThread.java for system apps.
- if (!doFlashes && (Build.IS_USER || suppress)) {
- setCloseGuardEnabled(false);
- return false;
+ public static void initThreadDefaults(ApplicationInfo ai) {
+ final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
+ final int targetSdkVersion =
+ (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ // Starting in HC, we don't allow network usage on the main thread
+ if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
+ builder.detectNetwork();
+ builder.penaltyDeathOnNetwork();
+ }
+
+ if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+ // Detect nothing extra
+ } else if (Build.IS_USERDEBUG) {
+ // Detect everything in bundled apps
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.penaltyDropBox();
+ if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
+ builder.penaltyFlashScreen();
+ }
+ }
+ } else if (Build.IS_ENG) {
+ // Detect everything in bundled apps
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.penaltyDropBox();
+ builder.penaltyLog();
+ builder.penaltyFlashScreen();
+ }
}
- // Eng builds have flashes on all the time. The suppression property
- // overrides this, so we force the behavior only after the short-circuit
- // check above.
- if (Build.IS_ENG) {
- doFlashes = true;
- }
+ setThreadPolicy(builder.build());
+ }
- // Thread policy controls BlockGuard.
- int threadPolicyMask =
- StrictMode.DETECT_DISK_WRITE
- | StrictMode.DETECT_DISK_READ
- | StrictMode.DETECT_NETWORK;
+ /**
+ * Initialize default {@link VmPolicy} for the current VM.
+ *
+ * @hide
+ */
+ public static void initVmDefaults(ApplicationInfo ai) {
+ final VmPolicy.Builder builder = new VmPolicy.Builder();
+ final int targetSdkVersion =
+ (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
- if (!Build.IS_USER) {
- threadPolicyMask |= StrictMode.PENALTY_DROPBOX;
- }
- if (doFlashes) {
- threadPolicyMask |= StrictMode.PENALTY_FLASH;
+ // Starting in N, we don't allow file:// Uri exposure
+ if (targetSdkVersion >= Build.VERSION_CODES.N) {
+ builder.detectFileUriExposure();
+ builder.penaltyDeathOnFileUriExposure();
}
- StrictMode.setThreadPolicyMask(threadPolicyMask);
-
- // VM Policy controls CloseGuard, detection of Activity leaks,
- // and instance counting.
- if (Build.IS_USER) {
- setCloseGuardEnabled(false);
- } else {
- VmPolicy.Builder policyBuilder = new VmPolicy.Builder().detectAll();
- if (!Build.IS_ENG) {
- // Activity leak detection causes too much slowdown for userdebug because of the
- // GCs.
- policyBuilder = policyBuilder.disable(DETECT_VM_ACTIVITY_LEAKS);
- }
- policyBuilder = policyBuilder.penaltyDropBox();
- if (Build.IS_ENG) {
- policyBuilder.penaltyLog();
- }
- // All core system components need to tag their sockets to aid
- // system health investigations
- if (android.os.Process.myUid() < android.os.Process.FIRST_APPLICATION_UID) {
- policyBuilder.enable(DETECT_VM_UNTAGGED_SOCKET);
- } else {
- policyBuilder.disable(DETECT_VM_UNTAGGED_SOCKET);
+ if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+ // Detect nothing extra
+ } else if (Build.IS_USERDEBUG) {
+ // Detect everything in bundled apps (except activity leaks, which
+ // are expensive to track)
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.permitActivityLeaks();
+ builder.penaltyDropBox();
+ }
+ } else if (Build.IS_ENG) {
+ // Detect everything in bundled apps
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.penaltyDropBox();
+ builder.penaltyLog();
}
- setVmPolicy(policyBuilder.build());
- setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
}
- return true;
- }
- /**
- * Used by the framework to make network usage on the main thread a fatal error.
- *
- * @hide
- */
- public static void enableDeathOnNetwork() {
- int oldPolicy = getThreadPolicyMask();
- int newPolicy = oldPolicy | DETECT_NETWORK | PENALTY_DEATH_ON_NETWORK;
- setThreadPolicyMask(newPolicy);
+ setVmPolicy(builder.build());
}
/**
@@ -1205,7 +1299,9 @@ public final class StrictMode {
sVmPolicy.mask
| DETECT_VM_FILE_URI_EXPOSURE
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
- sVmPolicy.classInstanceLimit);
+ sVmPolicy.classInstanceLimit,
+ sVmPolicy.mListener,
+ sVmPolicy.mCallbackExecutor);
}
/**
@@ -1220,7 +1316,9 @@ public final class StrictMode {
sVmPolicy.mask
& ~(DETECT_VM_FILE_URI_EXPOSURE
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
- sVmPolicy.classInstanceLimit);
+ sVmPolicy.classInstanceLimit,
+ sVmPolicy.mListener,
+ sVmPolicy.mCallbackExecutor);
}
/**
@@ -1308,9 +1406,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new DiskWriteViolation());
}
// Not part of BlockGuard.Policy; just part of StrictMode:
@@ -1321,10 +1417,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e =
- new StrictModeCustomViolation(mPolicyMask, name);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new CustomViolation(name));
}
// Not part of BlockGuard.Policy; just part of StrictMode:
@@ -1335,13 +1428,10 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e =
- new StrictModeResourceMismatchViolation(mPolicyMask, tag);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new ResourceMismatchViolation(tag));
}
- // Part of BlockGuard.Policy; just part of StrictMode:
+ // Not part of BlockGuard.Policy; just part of StrictMode:
public void onUnbufferedIO() {
if ((mPolicyMask & DETECT_UNBUFFERED_IO) == 0) {
return;
@@ -1349,10 +1439,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e =
- new StrictModeUnbufferedIOViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new UnbufferedIoViolation());
}
// Part of BlockGuard.Policy interface:
@@ -1363,9 +1450,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new DiskReadViolation());
}
// Part of BlockGuard.Policy interface:
@@ -1379,9 +1464,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new NetworkViolation());
}
public void setPolicyMask(int policyMask) {
@@ -1393,8 +1476,8 @@ public final class StrictMode {
// has yet occurred). This sees if we're in an event loop
// thread and, if so, uses it to roughly measure how long the
// violation took.
- void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) {
- final ViolationInfo info = new ViolationInfo(e, e.getPolicy());
+ void startHandlingViolationException(Violation e) {
+ final ViolationInfo info = new ViolationInfo(e, mPolicyMask);
info.violationUptimeMillis = SystemClock.uptimeMillis();
handleViolationWithTimingAttempt(info);
}
@@ -1423,9 +1506,9 @@ public final class StrictMode {
//
// TODO: if in gather mode, ignore Looper.myLooper() and always
// go into this immediate mode?
- if (looper == null || (info.policy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
+ if (looper == null || (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
info.durationMillis = -1; // unknown (redundant, already set)
- handleViolation(info);
+ onThreadPolicyViolation(info);
return;
}
@@ -1443,7 +1526,7 @@ public final class StrictMode {
}
final IWindowManager windowManager =
- (info.policy & PENALTY_FLASH) != 0 ? sWindowManager.get() : null;
+ info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null;
if (windowManager != null) {
try {
windowManager.showStrictModeViolation(true);
@@ -1463,30 +1546,28 @@ public final class StrictMode {
THREAD_HANDLER
.get()
.postAtFrontOfQueue(
- new Runnable() {
- public void run() {
- long loopFinishTime = SystemClock.uptimeMillis();
-
- // Note: we do this early, before handling the
- // violation below, as handling the violation
- // may include PENALTY_DEATH and we don't want
- // to keep the red border on.
- if (windowManager != null) {
- try {
- windowManager.showStrictModeViolation(false);
- } catch (RemoteException unused) {
- }
+ () -> {
+ long loopFinishTime = SystemClock.uptimeMillis();
+
+ // Note: we do this early, before handling the
+ // violation below, as handling the violation
+ // may include PENALTY_DEATH and we don't want
+ // to keep the red border on.
+ if (windowManager != null) {
+ try {
+ windowManager.showStrictModeViolation(false);
+ } catch (RemoteException unused) {
}
+ }
- for (int n = 0; n < records.size(); ++n) {
- ViolationInfo v = records.get(n);
- v.violationNumThisLoop = n + 1;
- v.durationMillis =
- (int) (loopFinishTime - v.violationUptimeMillis);
- handleViolation(v);
- }
- records.clear();
+ for (int n = 0; n < records.size(); ++n) {
+ ViolationInfo v = records.get(n);
+ v.violationNumThisLoop = n + 1;
+ v.durationMillis =
+ (int) (loopFinishTime - v.violationUptimeMillis);
+ onThreadPolicyViolation(v);
}
+ records.clear();
});
}
@@ -1495,18 +1576,13 @@ public final class StrictMode {
// violation fired and now (after the violating code ran) due
// to people who push/pop temporary policy in regions of code,
// hence the policy being passed around.
- void handleViolation(final ViolationInfo info) {
- if (info == null || !info.hasStackTrace()) {
- Log.wtf(TAG, "unexpected null stacktrace");
- return;
- }
-
- if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.policy);
+ void onThreadPolicyViolation(final ViolationInfo info) {
+ if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; policy=" + info.mPolicy);
- if ((info.policy & PENALTY_GATHER) != 0) {
+ if (info.penaltyEnabled(PENALTY_GATHER)) {
ArrayList<ViolationInfo> violations = gatheredViolations.get();
if (violations == null) {
- violations = new ArrayList<ViolationInfo>(1);
+ violations = new ArrayList<>(1);
gatheredViolations.set(violations);
}
for (ViolationInfo previous : violations) {
@@ -1535,31 +1611,32 @@ public final class StrictMode {
long timeSinceLastViolationMillis =
lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
- if ((info.policy & PENALTY_LOG) != 0
+ if (info.penaltyEnabled(PENALTY_LOG)
&& timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
sLogger.log(info);
}
+ final Violation violation = info.mViolation;
+
// The violationMaskSubset, passed to ActivityManager, is a
// subset of the original StrictMode policy bitmask, with
// only the bit violated and penalty bits to be executed
// by the ActivityManagerService remaining set.
int violationMaskSubset = 0;
- if ((info.policy & PENALTY_DIALOG) != 0
+ if (info.penaltyEnabled(PENALTY_DIALOG)
&& timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
violationMaskSubset |= PENALTY_DIALOG;
}
- if ((info.policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
+ if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) {
violationMaskSubset |= PENALTY_DROPBOX;
}
if (violationMaskSubset != 0) {
violationMaskSubset |= info.getViolationBit();
- final int savedPolicyMask = getThreadPolicyMask();
- final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
+ final boolean justDropBox = (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
if (justDropBox) {
// If all we're going to ask the activity manager
// to do is dropbox it (the common case during
@@ -1568,42 +1645,38 @@ public final class StrictMode {
// isn't always super fast, despite the implementation
// in the ActivityManager trying to be mostly async.
dropboxViolationAsync(violationMaskSubset, info);
- return;
+ } else {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
}
+ }
- // Normal synchronous call to the ActivityManager.
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
- }
+ if ((info.getPolicyMask() & PENALTY_DEATH) != 0) {
+ throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
}
- if ((info.policy & PENALTY_DEATH) != 0) {
- executeDeathPenalty(info);
+ // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
+ // executor finishes before crashing.
+ final OnThreadViolationListener listener = sThreadViolationListener.get();
+ final Executor executor = sThreadViolationExecutor.get();
+ if (listener != null && executor != null) {
+ try {
+ executor.execute(
+ () -> {
+ // Lift violated policy to prevent infinite recursion.
+ ThreadPolicy oldPolicy = allowThreadViolations();
+ try {
+ listener.onThreadViolation(violation);
+ } finally {
+ setThreadPolicy(oldPolicy);
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
+ }
}
}
}
- private static void executeDeathPenalty(ViolationInfo info) {
- throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
- }
-
/**
* In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
* violations but not showing a dialog, not loggging, and not killing the process. In these
@@ -1622,33 +1695,44 @@ public final class StrictMode {
if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
- new Thread("callActivityManagerForStrictModeDropbox") {
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- try {
- IActivityManager am = ActivityManager.getService();
- if (am == null) {
- Log.d(TAG, "No activity manager; failed to Dropbox violation.");
- } else {
- am.handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- }
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException handling StrictMode violation", e);
- }
- }
- int outstanding = sDropboxCallsInFlight.decrementAndGet();
- if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstanding);
+ BackgroundThread.getHandler().post(() -> {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
+ int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
+ if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
+ });
+ }
+
+ private static void handleApplicationStrictModeViolation(int violationMaskSubset,
+ ViolationInfo info) {
+ final int oldMask = getThreadPolicyMask();
+ try {
+ // First, remove any policy before we call into the Activity Manager,
+ // otherwise we'll infinite recurse as we try to log policy violations
+ // to disk, thus violating policy, thus requiring logging, etc...
+ // We restore the current policy below, in the finally block.
+ setThreadPolicyMask(0);
+
+ IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ Log.w(TAG, "No activity manager; failed to Dropbox violation.");
+ } else {
+ am.handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(), violationMaskSubset, info);
}
- }.start();
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ // System process is dead; ignore
+ } else {
+ Log.e(TAG, "RemoteException handling StrictMode violation", e);
+ }
+ } finally {
+ setThreadPolicyMask(oldMask);
+ }
}
private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
public void report(String message, Throwable allocationSite) {
- onVmPolicyViolation(allocationSite);
+ onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
}
}
@@ -1686,8 +1770,7 @@ public final class StrictMode {
int limit = policy.classInstanceLimit.get(klass);
long instances = instanceCounts[i];
if (instances > limit) {
- Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr);
+ onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
}
}
}
@@ -1769,9 +1852,8 @@ public final class StrictMode {
* #setThreadPolicy}.
*/
public static void enableDefaults() {
- StrictMode.setThreadPolicy(
- new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+ setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
/** @hide */
@@ -1811,24 +1893,22 @@ public final class StrictMode {
/** @hide */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
- Throwable t = new Throwable(message);
- t.setStackTrace(originStack.getStackTrace());
- onVmPolicyViolation(t);
+ onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack));
}
/** @hide */
public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
- onVmPolicyViolation(originStack);
+ onVmPolicyViolation(new WebViewMethodCalledOnWrongThreadViolation(originStack));
}
/** @hide */
public static void onIntentReceiverLeaked(Throwable originStack) {
- onVmPolicyViolation(originStack);
+ onVmPolicyViolation(new IntentReceiverLeakedViolation(originStack));
}
/** @hide */
public static void onServiceConnectionLeaked(Throwable originStack) {
- onVmPolicyViolation(originStack);
+ onVmPolicyViolation(new ServiceConnectionLeakedViolation(originStack));
}
/** @hide */
@@ -1837,19 +1917,13 @@ public final class StrictMode {
if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
throw new FileUriExposedException(message);
} else {
- onVmPolicyViolation(new Throwable(message));
+ onVmPolicyViolation(new FileUriExposedViolation(message));
}
}
/** @hide */
public static void onContentUriWithoutPermission(Uri uri, String location) {
- final String message =
- uri
- + " exposed beyond app through "
- + location
- + " without permission grant flags; did you forget"
- + " FLAG_GRANT_READ_URI_PERMISSION?";
- onVmPolicyViolation(new Throwable(message));
+ onVmPolicyViolation(new ContentUriWithoutPermissionViolation(uri, location));
}
/** @hide */
@@ -1881,33 +1955,28 @@ public final class StrictMode {
}
msg += HexDump.dumpHexString(firstPacket).trim() + " ";
final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
- onVmPolicyViolation(new Throwable(msg), forceDeath);
+ onVmPolicyViolation(new CleartextNetworkViolation(msg), forceDeath);
}
/** @hide */
- public static final String UNTAGGED_SOCKET_VIOLATION_MESSAGE =
- "Untagged socket detected; use"
- + " TrafficStats.setThreadSocketTag() to track all network usage";
-
- /** @hide */
public static void onUntaggedSocket() {
- onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
+ onVmPolicyViolation(new UntaggedSocketViolation());
}
// Map from VM violation fingerprint to uptime millis.
- private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
+ private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();
/** @hide */
- public static void onVmPolicyViolation(Throwable originStack) {
+ public static void onVmPolicyViolation(Violation originStack) {
onVmPolicyViolation(originStack, false);
}
/** @hide */
- public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) {
+ public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
- final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
+ final ViolationInfo info = new ViolationInfo(violation, sVmPolicy.mask);
// Erase stuff not relevant for process-wide violations
info.numAnimationsRunning = 0;
@@ -1916,60 +1985,36 @@ public final class StrictMode {
final Integer fingerprint = info.hashCode();
final long now = SystemClock.uptimeMillis();
- long lastViolationTime = 0;
+ long lastViolationTime;
long timeSinceLastViolationMillis = Long.MAX_VALUE;
synchronized (sLastVmViolationTime) {
if (sLastVmViolationTime.containsKey(fingerprint)) {
lastViolationTime = sLastVmViolationTime.get(fingerprint);
timeSinceLastViolationMillis = now - lastViolationTime;
}
- if (timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+ if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
sLastVmViolationTime.put(fingerprint, now);
}
}
-
- if (penaltyLog && sLogger != null) {
- sLogger.log(info);
+ if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
+ // Rate limit all penalties.
+ return;
}
- if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+
+ if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
sLogger.log(info);
}
int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);
- if (penaltyDropbox && !penaltyDeath) {
- // Common case for userdebug/eng builds. If no death and
- // just dropboxing, we can do the ActivityManager call
- // asynchronously.
- dropboxViolationAsync(violationMaskSubset, info);
- return;
- }
-
- if (penaltyDropbox && lastViolationTime == 0) {
- // The violationMask, passed to ActivityManager, is a
- // subset of the original StrictMode policy bitmask, with
- // only the bit violated and penalty bits to be executed
- // by the ActivityManagerService remaining set.
- final int savedPolicyMask = getThreadPolicyMask();
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
+ if (penaltyDropbox) {
+ if (penaltyDeath) {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
+ } else {
+ // Common case for userdebug/eng builds. If no death and
+ // just dropboxing, we can do the ActivityManager call
+ // asynchronously.
+ dropboxViolationAsync(violationMaskSubset, info);
}
}
@@ -1978,6 +2023,26 @@ public final class StrictMode {
Process.killProcess(Process.myPid());
System.exit(10);
}
+
+ // If penaltyDeath, we can't guarantee this callback finishes before the process dies for
+ // all executors. penaltyDeath supersedes penaltyCallback.
+ if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) {
+ final OnVmViolationListener listener = sVmPolicy.mListener;
+ try {
+ sVmPolicy.mCallbackExecutor.execute(
+ () -> {
+ // Lift violated policy to prevent infinite recursion.
+ VmPolicy oldPolicy = allowVmViolations();
+ try {
+ listener.onVmViolation(violation);
+ } finally {
+ setVmPolicy(oldPolicy);
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ Log.e(TAG, "VmPolicy penaltyCallback failed", e);
+ }
+ }
}
/** Called from Parcel.writeNoException() */
@@ -1998,14 +2063,12 @@ public final class StrictMode {
gatheredViolations.set(null);
}
- private static class LogStackTrace extends Exception {}
-
/**
* Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here
* read back all the encoded violations.
*/
/* package */ static void readAndHandleBinderCallViolations(Parcel p) {
- LogStackTrace localCallSite = new LogStackTrace();
+ Throwable localCallSite = new Throwable();
final int policyMask = getThreadPolicyMask();
final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
@@ -2323,8 +2386,7 @@ public final class StrictMode {
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
- Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr);
+ onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
}
}
@@ -2337,17 +2399,16 @@ public final class StrictMode {
@TestApi
public static final class ViolationInfo implements Parcelable {
/** Stack and violation details. */
- @Nullable private final Throwable mThrowable;
+ private final Violation mViolation;
- private final Deque<Throwable> mBinderStack = new ArrayDeque<>();
+ /** Path leading to a violation that occurred across binder. */
+ private final Deque<StackTraceElement[]> mBinderStack = new ArrayDeque<>();
/** Memoized stack trace of full violation. */
@Nullable private String mStackTrace;
- /** Memoized violation bit. */
- private int mViolationBit;
/** The strict mode policy mask at the time of violation. */
- public final int policy;
+ private final int mPolicy;
/** The wall time duration of the violation, when known. -1 when not known. */
public int durationMillis = -1;
@@ -2377,17 +2438,11 @@ public final class StrictMode {
/** If this is a instance count violation, the number of instances in memory, else -1. */
public long numInstances = -1;
- /** Create an uninitialized instance of ViolationInfo */
- public ViolationInfo() {
- mThrowable = null;
- policy = 0;
- }
-
/** Create an instance of ViolationInfo initialized from an exception. */
- public ViolationInfo(Throwable tr, int policy) {
- this.mThrowable = tr;
+ ViolationInfo(Violation tr, int policy) {
+ this.mViolation = tr;
+ this.mPolicy = policy;
violationUptimeMillis = SystemClock.uptimeMillis();
- this.policy = policy;
this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
Intent broadcastIntent = ActivityThread.getIntentBeingBroadcast();
if (broadcastIntent != null) {
@@ -2395,7 +2450,7 @@ public final class StrictMode {
}
ThreadSpanState state = sThisThreadSpanState.get();
if (tr instanceof InstanceCountViolation) {
- this.numInstances = ((InstanceCountViolation) tr).mInstances;
+ this.numInstances = ((InstanceCountViolation) tr).getNumberOfInstances();
}
synchronized (state) {
int spanActiveCount = state.mActiveSize;
@@ -2417,13 +2472,17 @@ public final class StrictMode {
/** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
public String getStackTrace() {
- if (mThrowable != null && mStackTrace == null) {
+ if (mStackTrace == null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 256);
- mThrowable.printStackTrace(pw);
- for (Throwable t : mBinderStack) {
+ mViolation.printStackTrace(pw);
+ for (StackTraceElement[] traces : mBinderStack) {
pw.append("# via Binder call with stack:\n");
- t.printStackTrace(pw);
+ for (StackTraceElement traceElement : traces) {
+ pw.append("\tat ");
+ pw.append(traceElement.toString());
+ pw.append('\n');
+ }
}
pw.flush();
pw.close();
@@ -2439,29 +2498,31 @@ public final class StrictMode {
*/
@TestApi
public String getViolationDetails() {
- if (mThrowable != null) {
- return mThrowable.getMessage();
- } else {
- return "";
- }
+ return mViolation.getMessage();
}
/**
- * If this violation has a useful stack trace.
+ * Policy mask at time of violation.
*
* @hide
*/
- public boolean hasStackTrace() {
- return mThrowable != null;
+ @TestApi
+ public int getPolicyMask() {
+ return mPolicy;
+ }
+
+ boolean penaltyEnabled(int p) {
+ return (mPolicy & p) != 0;
}
/**
- * Add a {@link Throwable} from the current process that caused the underlying violation.
+ * Add a {@link Throwable} from the current process that caused the underlying violation. We
+ * only preserve the stack trace elements.
*
* @hide
*/
void addLocalStack(Throwable t) {
- mBinderStack.addFirst(t);
+ mBinderStack.addFirst(t.getStackTrace());
}
/**
@@ -2469,37 +2530,47 @@ public final class StrictMode {
*
* @hide
*/
- int getViolationBit() {
- if (mThrowable == null || mThrowable.getMessage() == null) {
- return 0;
- }
- if (mViolationBit != 0) {
- return mViolationBit;
- }
- String message = mThrowable.getMessage();
- int violationIndex = message.indexOf("violation=");
- if (violationIndex == -1) {
- return 0;
- }
- int numberStartIndex = violationIndex + "violation=".length();
- int numberEndIndex = message.indexOf(' ', numberStartIndex);
- if (numberEndIndex == -1) {
- numberEndIndex = message.length();
- }
- String violationString = message.substring(numberStartIndex, numberEndIndex);
- try {
- mViolationBit = Integer.parseInt(violationString);
- return mViolationBit;
- } catch (NumberFormatException e) {
- return 0;
- }
+ @TestApi
+ public int getViolationBit() {
+ if (mViolation instanceof DiskWriteViolation) {
+ return DETECT_DISK_WRITE;
+ } else if (mViolation instanceof DiskReadViolation) {
+ return DETECT_DISK_READ;
+ } else if (mViolation instanceof NetworkViolation) {
+ return DETECT_NETWORK;
+ } else if (mViolation instanceof CustomViolation) {
+ return DETECT_CUSTOM;
+ } else if (mViolation instanceof ResourceMismatchViolation) {
+ return DETECT_RESOURCE_MISMATCH;
+ } else if (mViolation instanceof UnbufferedIoViolation) {
+ return DETECT_UNBUFFERED_IO;
+ } else if (mViolation instanceof SqliteObjectLeakedViolation) {
+ return DETECT_VM_CURSOR_LEAKS;
+ } else if (mViolation instanceof LeakedClosableViolation) {
+ return DETECT_VM_CLOSABLE_LEAKS;
+ } else if (mViolation instanceof InstanceCountViolation) {
+ return DETECT_VM_INSTANCE_LEAKS;
+ } else if (mViolation instanceof IntentReceiverLeakedViolation) {
+ return DETECT_VM_REGISTRATION_LEAKS;
+ } else if (mViolation instanceof ServiceConnectionLeakedViolation) {
+ return DETECT_VM_REGISTRATION_LEAKS;
+ } else if (mViolation instanceof FileUriExposedViolation) {
+ return DETECT_VM_FILE_URI_EXPOSURE;
+ } else if (mViolation instanceof CleartextNetworkViolation) {
+ return DETECT_VM_CLEARTEXT_NETWORK;
+ } else if (mViolation instanceof ContentUriWithoutPermissionViolation) {
+ return DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION;
+ } else if (mViolation instanceof UntaggedSocketViolation) {
+ return DETECT_VM_UNTAGGED_SOCKET;
+ }
+ throw new IllegalStateException("missing violation bit");
}
@Override
public int hashCode() {
int result = 17;
- if (mThrowable != null) {
- result = 37 * result + mThrowable.hashCode();
+ if (mViolation != null) {
+ result = 37 * result + mViolation.hashCode();
}
if (numAnimationsRunning != 0) {
result *= 37;
@@ -2527,16 +2598,26 @@ public final class StrictMode {
* should be removed.
*/
public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
- mThrowable = (Throwable) in.readSerializable();
+ mViolation = (Violation) in.readSerializable();
int binderStackSize = in.readInt();
for (int i = 0; i < binderStackSize; i++) {
- mBinderStack.add((Throwable) in.readSerializable());
+ StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
+ for (int j = 0; j < traceElements.length; j++) {
+ StackTraceElement element =
+ new StackTraceElement(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readInt());
+ traceElements[j] = element;
+ }
+ mBinderStack.add(traceElements);
}
int rawPolicy = in.readInt();
if (unsetGatheringBit) {
- policy = rawPolicy & ~PENALTY_GATHER;
+ mPolicy = rawPolicy & ~PENALTY_GATHER;
} else {
- policy = rawPolicy;
+ mPolicy = rawPolicy;
}
durationMillis = in.readInt();
violationNumThisLoop = in.readInt();
@@ -2550,13 +2631,19 @@ public final class StrictMode {
/** Save a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeSerializable(mThrowable);
+ dest.writeSerializable(mViolation);
dest.writeInt(mBinderStack.size());
- for (Throwable t : mBinderStack) {
- dest.writeSerializable(t);
+ for (StackTraceElement[] traceElements : mBinderStack) {
+ dest.writeInt(traceElements.length);
+ for (StackTraceElement element : traceElements) {
+ dest.writeString(element.getClassName());
+ dest.writeString(element.getMethodName());
+ dest.writeString(element.getFileName());
+ dest.writeInt(element.getLineNumber());
+ }
}
int start = dest.dataPosition();
- dest.writeInt(policy);
+ dest.writeInt(mPolicy);
dest.writeInt(durationMillis);
dest.writeInt(violationNumThisLoop);
dest.writeInt(numAnimationsRunning);
@@ -2569,7 +2656,7 @@ public final class StrictMode {
Slog.d(
TAG,
"VIO: policy="
- + policy
+ + mPolicy
+ " dur="
+ durationMillis
+ " numLoop="
@@ -2588,10 +2675,8 @@ public final class StrictMode {
/** Dump a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
- if (mThrowable != null) {
- pw.println(prefix + "stackTrace: " + getStackTrace());
- }
- pw.println(prefix + "policy: " + policy);
+ pw.println(prefix + "stackTrace: " + getStackTrace());
+ pw.println(prefix + "policy: " + mPolicy);
if (durationMillis != -1) {
pw.println(prefix + "durationMillis: " + durationMillis);
}
@@ -2635,27 +2720,6 @@ public final class StrictMode {
};
}
- // Dummy throwable, for now, since we don't know when or where the
- // leaked instances came from. We might in the future, but for
- // now we suppress the stack trace because it's useless and/or
- // misleading.
- private static class InstanceCountViolation extends Throwable {
- private final long mInstances;
- private final int mLimit;
-
- private static final StackTraceElement[] FAKE_STACK = {
- new StackTraceElement(
- "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
- };
-
- public InstanceCountViolation(Class klass, long instances, int limit) {
- super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
- setStackTrace(FAKE_STACK);
- mInstances = instances;
- mLimit = limit;
- }
- }
-
private static final class InstanceTracker {
private static final HashMap<Class<?>, Integer> sInstanceCounts =
new HashMap<Class<?>, Integer>();
diff --git a/android/os/SystemClock.java b/android/os/SystemClock.java
index b3d76d73..c52c22d6 100644
--- a/android/os/SystemClock.java
+++ b/android/os/SystemClock.java
@@ -16,12 +16,18 @@
package android.os;
+import android.annotation.NonNull;
import android.app.IAlarmManager;
import android.content.Context;
import android.util.Slog;
import dalvik.annotation.optimization.CriticalNative;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
/**
* Core timekeeping facilities.
*
@@ -168,6 +174,31 @@ public final class SystemClock {
native public static long uptimeMillis();
/**
+ * Return {@link Clock} that starts at system boot, not counting time spent
+ * in deep sleep.
+ */
+ public static @NonNull Clock uptimeMillisClock() {
+ return new Clock() {
+ @Override
+ public ZoneId getZone() {
+ return ZoneOffset.UTC;
+ }
+ @Override
+ public Clock withZone(ZoneId zone) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long millis() {
+ return SystemClock.uptimeMillis();
+ }
+ @Override
+ public Instant instant() {
+ return Instant.ofEpochMilli(millis());
+ }
+ };
+ }
+
+ /**
* Returns milliseconds since boot, including time spent in sleep.
*
* @return elapsed milliseconds since boot.
@@ -176,6 +207,31 @@ public final class SystemClock {
native public static long elapsedRealtime();
/**
+ * Return {@link Clock} that starts at system boot, including time spent in
+ * sleep.
+ */
+ public static @NonNull Clock elapsedRealtimeClock() {
+ return new Clock() {
+ @Override
+ public ZoneId getZone() {
+ return ZoneOffset.UTC;
+ }
+ @Override
+ public Clock withZone(ZoneId zone) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long millis() {
+ return SystemClock.elapsedRealtime();
+ }
+ @Override
+ public Instant instant() {
+ return Instant.ofEpochMilli(millis());
+ }
+ };
+ }
+
+ /**
* Returns nanoseconds since boot, including time spent in sleep.
*
* @return elapsed nanoseconds since boot.
diff --git a/android/os/TokenWatcher.java b/android/os/TokenWatcher.java
index 9b3a2d68..00333dad 100644
--- a/android/os/TokenWatcher.java
+++ b/android/os/TokenWatcher.java
@@ -16,17 +16,23 @@
package android.os;
+import android.util.Log;
+
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.WeakHashMap;
import java.util.Set;
-import android.util.Log;
+import java.util.WeakHashMap;
/**
- * Helper class that helps you use IBinder objects as reference counted
- * tokens. IBinders make good tokens because we find out when they are
- * removed
+ * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added
+ * to the collection by calling {@link #acquire}, and removed by calling {@link
+ * #release}. IBinders are also implicitly removed when they become weakly
+ * reachable. Each IBinder may be added at most once.
*
+ * The {@link #acquired} method is invoked by posting to the specified handler
+ * whenever the size of the watched collection becomes nonzero. The {@link
+ * #released} method is invoked on the specified handler whenever the size of
+ * the watched collection becomes zero.
*/
public abstract class TokenWatcher
{
@@ -59,15 +65,23 @@ public abstract class TokenWatcher
* Record that this token has been acquired. When acquire is called, and
* the current count is 0, the acquired method is called on the given
* handler.
- *
- * @param token An IBinder object. If this token has already been acquired,
- * no action is taken.
+ *
+ * Note that the same {@code token} can only be acquired once. If this
+ * {@code token} has already been acquired, no action is taken. The first
+ * subsequent call to {@link #release} will release this {@code token}
+ * immediately.
+ *
+ * @param token An IBinder object.
* @param tag A string used by the {@link #dump} method for debugging,
* to see who has references.
*/
public void acquire(IBinder token, String tag)
{
synchronized (mTokens) {
+ if (mTokens.containsKey(token)) {
+ return;
+ }
+
// explicitly checked to avoid bogus sendNotification calls because
// of the WeakHashMap and the GC
int oldSize = mTokens.size();
diff --git a/android/os/UpdateEngine.java b/android/os/UpdateEngine.java
index ee0b6230..c6149bed 100644
--- a/android/os/UpdateEngine.java
+++ b/android/os/UpdateEngine.java
@@ -67,6 +67,7 @@ public class UpdateEngine {
public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10;
public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11;
public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12;
+ public static final int UPDATED_BUT_NOT_ACTIVE = 52;
}
/**
diff --git a/android/os/UserHandle.java b/android/os/UserHandle.java
index e8ebf631..6381b56a 100644
--- a/android/os/UserHandle.java
+++ b/android/os/UserHandle.java
@@ -27,6 +27,8 @@ import java.io.PrintWriter;
* Representation of a user on the device.
*/
public final class UserHandle implements Parcelable {
+ // NOTE: keep logic in sync with system/core/libcutils/multiuser.c
+
/**
* @hide Range of uids allocated for a user.
*/
@@ -88,6 +90,19 @@ public final class UserHandle implements Parcelable {
*/
public static final boolean MU_ENABLED = true;
+ /** @hide */
+ public static final int ERR_GID = -1;
+ /** @hide */
+ public static final int AID_ROOT = android.os.Process.ROOT_UID;
+ /** @hide */
+ public static final int AID_APP_START = android.os.Process.FIRST_APPLICATION_UID;
+ /** @hide */
+ public static final int AID_APP_END = android.os.Process.LAST_APPLICATION_UID;
+ /** @hide */
+ public static final int AID_SHARED_GID_START = android.os.Process.FIRST_SHARED_APPLICATION_GID;
+ /** @hide */
+ public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID;
+
final int mHandle;
/**
@@ -197,13 +212,20 @@ public final class UserHandle implements Parcelable {
return getUid(userId, Process.SHARED_USER_GID);
}
- /**
- * Returns the shared app gid for a given uid or appId.
- * @hide
- */
- public static int getSharedAppGid(int id) {
- return Process.FIRST_SHARED_APPLICATION_GID + (id % PER_USER_RANGE)
- - Process.FIRST_APPLICATION_UID;
+ /** @hide */
+ public static int getSharedAppGid(int uid) {
+ return getSharedAppGid(getUserId(uid), getAppId(uid));
+ }
+
+ /** @hide */
+ public static int getSharedAppGid(int userId, int appId) {
+ if (appId >= AID_APP_START && appId <= AID_APP_END) {
+ return (appId - AID_APP_START) + AID_SHARED_GID_START;
+ } else if (appId >= AID_ROOT && appId <= AID_APP_START) {
+ return appId;
+ } else {
+ return -1;
+ }
}
/**
@@ -219,13 +241,18 @@ public final class UserHandle implements Parcelable {
return appId;
}
- /**
- * Returns the cache GID for a given UID or appId.
- * @hide
- */
- public static int getCacheAppGid(int id) {
- return Process.FIRST_APPLICATION_CACHE_GID + (id % PER_USER_RANGE)
- - Process.FIRST_APPLICATION_UID;
+ /** @hide */
+ public static int getCacheAppGid(int uid) {
+ return getCacheAppGid(getUserId(uid), getAppId(uid));
+ }
+
+ /** @hide */
+ public static int getCacheAppGid(int userId, int appId) {
+ if (appId >= AID_APP_START && appId <= AID_APP_END) {
+ return getUid(userId, (appId - AID_APP_START) + AID_CACHE_GID_START);
+ } else {
+ return -1;
+ }
}
/**
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index c54b72d4..22967af7 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -140,6 +140,18 @@ public class UserManager {
public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
/**
+ * Specifies if a user is disallowed from changing the device
+ * language. The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_LOCALE = "no_config_locale";
+
+ /**
* Specifies if a user is disallowed from installing applications.
* The default value is <code>false</code>.
*
@@ -792,6 +804,19 @@ public class UserManager {
public static final String DISALLOW_AUTOFILL = "no_autofill";
/**
+ * Specifies if user switching is blocked on the current user.
+ *
+ * <p> This restriction can only be set by the device owner, it will be applied to all users.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+
+ /**
* Application restriction key that is used to indicate the pending arrival
* of real restrictions for the app.
*
@@ -917,7 +942,7 @@ public class UserManager {
/**
* Returns whether switching users is currently allowed.
* <p>For instance switching users is not allowed if the current user is in a phone call,
- * or system user hasn't been unlocked yet
+ * system user hasn't been unlocked yet, or {@link #DISALLOW_USER_SWITCH} is set.
* @hide
*/
public boolean canSwitchUsers() {
@@ -927,7 +952,9 @@ public class UserManager {
boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
boolean inCall = TelephonyManager.getDefault().getCallState()
!= TelephonyManager.CALL_STATE_IDLE;
- return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall;
+ boolean isUserSwitchDisallowed = hasUserRestriction(DISALLOW_USER_SWITCH);
+ return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
+ && !isUserSwitchDisallowed;
}
/**
@@ -1022,12 +1049,22 @@ public class UserManager {
}
/**
- * Used to check if the user making this call is linked to another user. Linked users may have
+ * @hide
+ * @deprecated Use {@link #isRestrictedProfile()}
+ */
+ @Deprecated
+ public boolean isLinkedUser() {
+ return isRestrictedProfile();
+ }
+
+ /**
+ * Returns whether the caller is running as restricted profile. Restricted profile may have
* a reduced number of available apps, app restrictions and account restrictions.
* @return whether the user making this call is a linked user
* @hide
*/
- public boolean isLinkedUser() {
+ @SystemApi
+ public boolean isRestrictedProfile() {
try {
return mService.isRestricted();
} catch (RemoteException re) {
@@ -1048,6 +1085,20 @@ public class UserManager {
}
/**
+ * Returns whether the calling user has at least one restricted profile associated with it.
+ * @return
+ * @hide
+ */
+ @SystemApi
+ public boolean hasRestrictedProfiles() {
+ try {
+ return mService.hasRestrictedProfiles();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if a user is a guest user.
* @return whether user is a guest user.
* @hide
@@ -1067,6 +1118,7 @@ public class UserManager {
return user != null && user.isGuest();
}
+
/**
* Checks if the calling app is running in a demo user. When running in a demo user,
* apps can be more helpful to the user, or explain their features in more detail.
@@ -2298,6 +2350,9 @@ public class UserManager {
if (!supportsMultipleUsers()) {
return false;
}
+ if (hasUserRestriction(DISALLOW_USER_SWITCH)) {
+ return false;
+ }
// If Demo Mode is on, don't show user switcher
if (isDeviceInDemoMode(mContext)) {
return false;
diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java
index 6594cd07..0b007ddf 100644
--- a/android/os/storage/StorageManager.java
+++ b/android/os/storage/StorageManager.java
@@ -42,10 +42,12 @@ import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IVold;
+import android.os.IVoldTaskListener;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
+import android.os.PersistableBundle;
import android.os.ProxyFileDescriptorCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -87,7 +89,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -884,9 +888,32 @@ public class StorageManager {
}
/** {@hide} */
+ @Deprecated
public long benchmark(String volId) {
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ result.complete(extras);
+ }
+ });
+ try {
+ // Convert ms to ns
+ return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
+ } catch (Exception e) {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /** {@hide} */
+ public void benchmark(String volId, IVoldTaskListener listener) {
try {
- return mStorageManager.benchmark(volId);
+ mStorageManager.benchmark(volId, listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/CleartextNetworkViolation.java
index e28e1b38..6a0d381d 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/CleartextNetworkViolation.java
@@ -11,16 +11,13 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
+package android.os.strictmode;
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class CleartextNetworkViolation extends Violation {
+ /** @hide */
+ public CleartextNetworkViolation(String msg) {
+ super(msg);
+ }
}
diff --git a/android/os/strictmode/ContentUriWithoutPermissionViolation.java b/android/os/strictmode/ContentUriWithoutPermissionViolation.java
new file mode 100644
index 00000000..e78dc79d
--- /dev/null
+++ b/android/os/strictmode/ContentUriWithoutPermissionViolation.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+import android.net.Uri;
+
+public final class ContentUriWithoutPermissionViolation extends Violation {
+ /** @hide */
+ public ContentUriWithoutPermissionViolation(Uri uri, String location) {
+ super(
+ uri
+ + " exposed beyond app through "
+ + location
+ + " without permission grant flags; did you forget"
+ + " FLAG_GRANT_READ_URI_PERMISSION?");
+ }
+}
diff --git a/android/os/strictmode/CustomViolation.java b/android/os/strictmode/CustomViolation.java
new file mode 100644
index 00000000..d4ad0671
--- /dev/null
+++ b/android/os/strictmode/CustomViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class CustomViolation extends Violation {
+ /** @hide */
+ public CustomViolation(String name) {
+ super(name);
+ }
+}
diff --git a/android/os/strictmode/DiskReadViolation.java b/android/os/strictmode/DiskReadViolation.java
new file mode 100644
index 00000000..fad32dbf
--- /dev/null
+++ b/android/os/strictmode/DiskReadViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class DiskReadViolation extends Violation {
+ /** @hide */
+ public DiskReadViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/DiskWriteViolation.java b/android/os/strictmode/DiskWriteViolation.java
new file mode 100644
index 00000000..cb9ca381
--- /dev/null
+++ b/android/os/strictmode/DiskWriteViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class DiskWriteViolation extends Violation {
+ /** @hide */
+ public DiskWriteViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/FileUriExposedViolation.java b/android/os/strictmode/FileUriExposedViolation.java
new file mode 100644
index 00000000..e3e6f833
--- /dev/null
+++ b/android/os/strictmode/FileUriExposedViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class FileUriExposedViolation extends Violation {
+ /** @hide */
+ public FileUriExposedViolation(String msg) {
+ super(msg);
+ }
+}
diff --git a/android/os/strictmode/InstanceCountViolation.java b/android/os/strictmode/InstanceCountViolation.java
new file mode 100644
index 00000000..9ee2c8e5
--- /dev/null
+++ b/android/os/strictmode/InstanceCountViolation.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public class InstanceCountViolation extends Violation {
+ private final long mInstances;
+
+ private static final StackTraceElement[] FAKE_STACK = {
+ new StackTraceElement(
+ "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
+ };
+
+ /** @hide */
+ public InstanceCountViolation(Class klass, long instances, int limit) {
+ super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
+ setStackTrace(FAKE_STACK);
+ mInstances = instances;
+ }
+
+ public long getNumberOfInstances() {
+ return mInstances;
+ }
+}
diff --git a/android/os/strictmode/IntentReceiverLeakedViolation.java b/android/os/strictmode/IntentReceiverLeakedViolation.java
new file mode 100644
index 00000000..f416c940
--- /dev/null
+++ b/android/os/strictmode/IntentReceiverLeakedViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class IntentReceiverLeakedViolation extends Violation {
+ /** @hide */
+ public IntentReceiverLeakedViolation(Throwable originStack) {
+ super(null);
+ setStackTrace(originStack.getStackTrace());
+ }
+}
diff --git a/android/os/strictmode/LeakedClosableViolation.java b/android/os/strictmode/LeakedClosableViolation.java
new file mode 100644
index 00000000..c795a6b8
--- /dev/null
+++ b/android/os/strictmode/LeakedClosableViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class LeakedClosableViolation extends Violation {
+ /** @hide */
+ public LeakedClosableViolation(String message, Throwable allocationSite) {
+ super(message);
+ initCause(allocationSite);
+ }
+}
diff --git a/android/os/strictmode/NetworkViolation.java b/android/os/strictmode/NetworkViolation.java
new file mode 100644
index 00000000..abcf009d
--- /dev/null
+++ b/android/os/strictmode/NetworkViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class NetworkViolation extends Violation {
+ /** @hide */
+ public NetworkViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/ResourceMismatchViolation.java b/android/os/strictmode/ResourceMismatchViolation.java
new file mode 100644
index 00000000..97c44993
--- /dev/null
+++ b/android/os/strictmode/ResourceMismatchViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class ResourceMismatchViolation extends Violation {
+ /** @hide */
+ public ResourceMismatchViolation(Object tag) {
+ super(tag.toString());
+ }
+}
diff --git a/android/os/strictmode/ServiceConnectionLeakedViolation.java b/android/os/strictmode/ServiceConnectionLeakedViolation.java
new file mode 100644
index 00000000..2d6b58f0
--- /dev/null
+++ b/android/os/strictmode/ServiceConnectionLeakedViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class ServiceConnectionLeakedViolation extends Violation {
+ /** @hide */
+ public ServiceConnectionLeakedViolation(Throwable originStack) {
+ super(null);
+ setStackTrace(originStack.getStackTrace());
+ }
+}
diff --git a/android/os/strictmode/SqliteObjectLeakedViolation.java b/android/os/strictmode/SqliteObjectLeakedViolation.java
new file mode 100644
index 00000000..02002207
--- /dev/null
+++ b/android/os/strictmode/SqliteObjectLeakedViolation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class SqliteObjectLeakedViolation extends Violation {
+
+ /** @hide */
+ public SqliteObjectLeakedViolation(String message, Throwable originStack) {
+ super(message);
+ initCause(originStack);
+ }
+}
diff --git a/android/os/strictmode/UnbufferedIoViolation.java b/android/os/strictmode/UnbufferedIoViolation.java
new file mode 100644
index 00000000..a5c326d1
--- /dev/null
+++ b/android/os/strictmode/UnbufferedIoViolation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+import android.os.StrictMode.ThreadPolicy.Builder;
+
+/**
+ * See #{@link Builder#detectUnbufferedIo()}
+ */
+public final class UnbufferedIoViolation extends Violation {
+ /** @hide */
+ public UnbufferedIoViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/UntaggedSocketViolation.java b/android/os/strictmode/UntaggedSocketViolation.java
new file mode 100644
index 00000000..836a8b9d
--- /dev/null
+++ b/android/os/strictmode/UntaggedSocketViolation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class UntaggedSocketViolation extends Violation {
+ /** @hide */
+ public static final String MESSAGE =
+ "Untagged socket detected; use"
+ + " TrafficStats.setThreadSocketTag() to track all network usage";
+
+ /** @hide */
+ public UntaggedSocketViolation() {
+ super(MESSAGE);
+ }
+}
diff --git a/android/os/strictmode/Violation.java b/android/os/strictmode/Violation.java
new file mode 100644
index 00000000..31c7d584
--- /dev/null
+++ b/android/os/strictmode/Violation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.strictmode;
+
+/** Root class for all StrictMode violations. */
+public abstract class Violation extends Throwable {
+ Violation(String message) {
+ super(message);
+ }
+}
diff --git a/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
new file mode 100644
index 00000000..c328d147
--- /dev/null
+++ b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class WebViewMethodCalledOnWrongThreadViolation extends Violation {
+ /** @hide */
+ public WebViewMethodCalledOnWrongThreadViolation(Throwable originStack) {
+ super(null);
+ setStackTrace(originStack.getStackTrace());
+ }
+}
diff --git a/android/preference/PreferenceFragment.java b/android/preference/PreferenceFragment.java
index 73fa01e5..4c556efa 100644
--- a/android/preference/PreferenceFragment.java
+++ b/android/preference/PreferenceFragment.java
@@ -23,7 +23,6 @@ import android.app.Fragment;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -105,7 +104,10 @@ import android.widget.TextView;
*
* @see Preference
* @see PreferenceScreen
+ *
+ * @deprecated Use {@link android.support.v7.preference.PreferenceFragmentCompat}
*/
+@Deprecated
public abstract class PreferenceFragment extends Fragment implements
PreferenceManager.OnPreferenceTreeClickListener {
@@ -146,7 +148,11 @@ public abstract class PreferenceFragment extends Fragment implements
* Interface that PreferenceFragment's containing activity should
* implement to be able to process preference items that wish to
* switch to a new fragment.
+ *
+ * @deprecated Use {@link
+ * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
*/
+ @Deprecated
public interface OnPreferenceStartFragmentCallback {
/**
* Called when the user has clicked on a Preference that has
diff --git a/android/provider/MediaStore.java b/android/provider/MediaStore.java
index 13e1e26b..32d68cd9 100644
--- a/android/provider/MediaStore.java
+++ b/android/provider/MediaStore.java
@@ -81,6 +81,13 @@ public final class MediaStore {
public static final String UNHIDE_CALL = "unhide";
/**
+ * The method name used by the media scanner service to reload all localized ringtone titles due
+ * to a locale change.
+ * @hide
+ */
+ public static final String RETRANSLATE_CALL = "update_titles";
+
+ /**
* This is for internal use by the media scanner only.
* Name of the (optional) Uri parameter that determines whether to skip deleting
* the file pointed to by the _data column, when deleting the database entry.
@@ -1358,6 +1365,18 @@ public final class MediaStore {
* @hide
*/
public static final String GENRE = "genre";
+
+ /**
+ * The resource URI of a localized title, if any
+ * <P>Type: TEXT</P>
+ * Conforms to this pattern:
+ * Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
+ * Authority: Package Name of ringtone title provider
+ * First Path Segment: Type of resource (must be "string")
+ * Second Path Segment: Resource ID of title
+ * @hide
+ */
+ public static final String TITLE_RESOURCE_URI = "title_resource_uri";
}
/**
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 62f4bf58..6decc305 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -66,12 +66,14 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.speech.tts.TextToSpeech;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.MemoryIntArray;
+import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -210,8 +212,13 @@ public final class Settings {
/** @hide */
public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
- /** @hide */
- public static final String EXTRA_SUB_ID = "sub_id";
+
+ /**
+ * An int extra specifying a subscription ID.
+ *
+ * @see android.telephony.SubscriptionInfo#getSubscriptionId
+ */
+ public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
/**
* Activity Action: Modify Airplane mode settings using a voice command.
@@ -915,6 +922,9 @@ public final class Settings {
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
+ * The subscription ID of the subscription for which available network operators should be
+ * displayed may be optionally specified with {@link #EXTRA_SUB_ID}.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -1886,7 +1896,11 @@ public final class Settings {
arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
IContentProvider cp = mProviderHolder.getProvider(cr);
+ String prevValue = getStringForUser(cr, name, userHandle);
cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+ String newValue = getStringForUser(cr, name, userHandle);
+ StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newValue, prevValue, tag,
+ makeDefault ? 1 : 0, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
return false;
@@ -2100,6 +2114,9 @@ public final class Settings {
* functions for accessing individual settings entries.
*/
public static final class System extends NameValueTable {
+ // NOTE: If you add new settings here, be sure to add them to
+ // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoSystemSettingsLocked.
+
private static final float DEFAULT_FONT_SCALE = 1.0f;
/** @hide */
@@ -4549,6 +4566,9 @@ public final class Settings {
* APIs for those values, not modified directly by applications.
*/
public static final class Secure extends NameValueTable {
+ // NOTE: If you add new settings here, be sure to add them to
+ // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoSecureSettingsLocked.
+
/**
* The content:// style URL for this table
*/
@@ -5304,6 +5324,15 @@ public final class Settings {
public static final String AUTOFILL_SERVICE = "autofill_service";
/**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): remove once feature is finished
+ * @hide
+ */
+ @TestApi
+ public static final String AUTOFILL_FEATURE_FIELD_DETECTION = "autofill_field_detection";
+
+ /**
* @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
*/
@Deprecated
@@ -7542,6 +7571,9 @@ public final class Settings {
* explicitly modify through the system UI or specialized APIs for those values.
*/
public static final class Global extends NameValueTable {
+ // NOTE: If you add new settings here, be sure to add them to
+ // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoGlobalSettingsLocked.
+
/**
* The content:// style URL for global secure settings items. Not public.
*/
@@ -8007,28 +8039,40 @@ public final class Settings {
public static final String HDMI_SYSTEM_AUDIO_CONTROL_ENABLED =
"hdmi_system_audio_control_enabled";
- /**
- * Whether TV will automatically turn on upon reception of the CEC command
- * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
- * @hide
- */
- public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
- "hdmi_control_auto_wakeup_enabled";
+ /**
+ * Whether TV will automatically turn on upon reception of the CEC command
+ * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
+ *
+ * @hide
+ */
+ public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
+ "hdmi_control_auto_wakeup_enabled";
- /**
- * Whether TV will also turn off other CEC devices when it goes to standby mode.
- * (0 = false, 1 = true)
- * @hide
- */
- public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
- "hdmi_control_auto_device_off_enabled";
+ /**
+ * Whether TV will also turn off other CEC devices when it goes to standby mode.
+ * (0 = false, 1 = true)
+ *
+ * @hide
+ */
+ public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
+ "hdmi_control_auto_device_off_enabled";
- /**
- * The interval in milliseconds at which location requests will be throttled when they are
- * coming from the background.
- * @hide
- */
- public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
+ /**
+ * If <b>true</b>, enables out-of-the-box execution for priv apps.
+ * Default: false
+ * Values: 0 = false, 1 = true
+ *
+ * @hide
+ */
+ public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
+
+ /**
+ * The interval in milliseconds at which location requests will be throttled when they are
+ * coming from the background.
+ *
+ * @hide
+ */
+ public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
"location_background_throttle_interval_ms";
/**
@@ -8488,6 +8532,13 @@ public final class Settings {
public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
"network_metered_multipath_preference";
+ /**
+ * Network watchlist last report time.
+ * @hide
+ */
+ public static final String NETWORK_WATCHLIST_LAST_REPORT_TIME =
+ "network_watchlist_last_report_time";
+
/**
* The thresholds of the wifi throughput badging (SD, HD etc.) as a comma-delimited list of
* colon-delimited key-value pairs. The key is the badging enum value defined in
@@ -9280,11 +9331,20 @@ public final class Settings {
public static final String DEFAULT_DNS_SERVER = "default_dns_server";
/**
- * Whether to disable DNS over TLS (boolean)
+ * The requested Private DNS mode (string), and an accompanying specifier (string).
+ *
+ * Currently, the specifier holds the chosen provider name when the mode requests
+ * a specific provider. It may be used to store the provider name even when the
+ * mode changes so that temporarily disabling and re-enabling the specific
+ * provider mode does not necessitate retyping the provider hostname.
*
* @hide
*/
- public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+ public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
/** {@hide} */
public static final String
@@ -9418,6 +9478,16 @@ public final class Settings {
public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
/**
+ * Battery Saver device specific settings
+ * This is encoded as a key=value list, separated by commas.
+ * See {@link com.android.server.power.BatterySaverPolicy} for the details.
+ *
+ * @hide
+ */
+ public static final String BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS =
+ "battery_saver_device_specific_constants";
+
+ /**
* Battery anomaly detection specific settings
* This is encoded as a key=value list, separated by commas.
* wakeup_blacklisted_tags is a string, encoded as a set of tags, encoded via
@@ -10094,12 +10164,17 @@ public final class Settings {
public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
/**
- * Whether the Volte is enabled
+ * Whether the Volte is enabled. If this setting is not set then we use the Carrier Config
+ * value {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
* <p>
* Type: int (0 for false, 1 for true)
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#ENHANCED_4G_MODE_ENABLED}
+ * instead.
*/
- public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+ @Deprecated
+ public static final String ENHANCED_4G_MODE_ENABLED =
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED;
/**
* Whether VT (Video Telephony over IMS) is enabled
@@ -10107,8 +10182,10 @@ public final class Settings {
* Type: int (0 for false, 1 for true)
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#VT_IMS_ENABLED} instead.
*/
- public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+ @Deprecated
+ public static final String VT_IMS_ENABLED = SubscriptionManager.VT_IMS_ENABLED;
/**
* Whether WFC is enabled
@@ -10116,8 +10193,10 @@ public final class Settings {
* Type: int (0 for false, 1 for true)
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ENABLED} instead.
*/
- public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+ @Deprecated
+ public static final String WFC_IMS_ENABLED = SubscriptionManager.WFC_IMS_ENABLED;
/**
* WFC mode on home/non-roaming network.
@@ -10125,8 +10204,10 @@ public final class Settings {
* Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_MODE} instead.
*/
- public static final String WFC_IMS_MODE = "wfc_ims_mode";
+ @Deprecated
+ public static final String WFC_IMS_MODE = SubscriptionManager.WFC_IMS_MODE;
/**
* WFC mode on roaming network.
@@ -10134,8 +10215,11 @@ public final class Settings {
* Type: int - see {@link #WFC_IMS_MODE} for values
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_MODE}
+ * instead.
*/
- public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+ @Deprecated
+ public static final String WFC_IMS_ROAMING_MODE = SubscriptionManager.WFC_IMS_ROAMING_MODE;
/**
* Whether WFC roaming is enabled
@@ -10143,8 +10227,12 @@ public final class Settings {
* Type: int (0 for false, 1 for true)
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_ENABLED}
+ * instead
*/
- public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+ @Deprecated
+ public static final String WFC_IMS_ROAMING_ENABLED =
+ SubscriptionManager.WFC_IMS_ROAMING_ENABLED;
/**
* Whether user can enable/disable LTE as a preferred network. A carrier might control
@@ -10370,7 +10458,9 @@ public final class Settings {
DOCK_AUDIO_MEDIA_ENABLED,
ENCODED_SURROUND_OUTPUT,
LOW_POWER_MODE_TRIGGER_LEVEL,
- BLUETOOTH_ON
+ BLUETOOTH_ON,
+ PRIVATE_DNS_MODE,
+ PRIVATE_DNS_SPECIFIER
};
/** @hide */
@@ -10823,7 +10913,7 @@ public final class Settings {
/** User preferred subscriptions setting.
* This holds the details of the user selected subscription from the card and
- * the activation status. Each settings string have the coma separated values
+ * the activation status. Each settings string have the comma separated values
* iccId,appType,appId,activationStatus,3gppIndex,3gpp2Index
* @hide
*/
diff --git a/android/provider/Telephony.java b/android/provider/Telephony.java
index 216d28cb..d7b6142a 100644
--- a/android/provider/Telephony.java
+++ b/android/provider/Telephony.java
@@ -2828,6 +2828,26 @@ public final class Telephony {
* @hide
*/
public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6;
+
+ /**
+ * The owner of the APN.
+ * <p>Type: INTEGER</p>
+ * @hide
+ */
+ public static final String OWNED_BY = "owned_by";
+
+ /**
+ * Possible value for the OWNED_BY field.
+ * APN is owned by DPC.
+ * @hide
+ */
+ public static final int OWNED_BY_DPC = 0;
+ /**
+ * Possible value for the OWNED_BY field.
+ * APN is owned by other sources.
+ * @hide
+ */
+ public static final int OWNED_BY_OTHERS = 1;
}
/**
@@ -3273,4 +3293,69 @@ public final class Telephony {
*/
public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
}
+
+ /**
+ * Contains carrier identification information.
+ * @hide
+ */
+ public static final class CarrierIdentification implements BaseColumns {
+ /**
+ * Numeric operator ID (as String). {@code MCC + MNC}
+ * <P>Type: TEXT </P>
+ */
+ public static final String MCCMNC = "mccmnc";
+
+ /**
+ * Group id level 1 (as String).
+ * <P>Type: TEXT </P>
+ */
+ public static final String GID1 = "gid1";
+
+ /**
+ * Group id level 2 (as String).
+ * <P>Type: TEXT </P>
+ */
+ public static final String GID2 = "gid2";
+
+ /**
+ * Public Land Mobile Network name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String PLMN = "plmn";
+
+ /**
+ * Prefix xpattern of IMSI (International Mobile Subscriber Identity).
+ * <P>Type: TEXT </P>
+ */
+ public static final String IMSI_PREFIX_XPATTERN = "imsi_prefix_xpattern";
+
+ /**
+ * Service Provider Name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String SPN = "spn";
+
+ /**
+ * Prefer APN name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String APN = "apn";
+
+ /**
+ * User facing carrier name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String NAME = "carrier_name";
+
+ /**
+ * A unique carrier id
+ * <P>Type: INTEGER </P>
+ */
+ public static final String CID = "carrier_id";
+
+ /**
+ * The {@code content://} URI for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://carrier_identification");
+ }
}
diff --git a/android/security/KeyStore.java b/android/security/KeyStore.java
index 7e959a87..399dddd7 100644
--- a/android/security/KeyStore.java
+++ b/android/security/KeyStore.java
@@ -20,6 +20,7 @@ import android.app.ActivityThread;
import android.app.Application;
import android.app.KeyguardManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.IBinder;
@@ -53,7 +54,7 @@ import java.util.Locale;
public class KeyStore {
private static final String TAG = "KeyStore";
- // ResponseCodes
+ // ResponseCodes - see system/security/keystore/include/keystore/keystore.h
public static final int NO_ERROR = 1;
public static final int LOCKED = 2;
public static final int UNINITIALIZED = 3;
@@ -167,10 +168,14 @@ public class KeyStore {
public byte[] get(String key, int uid) {
try {
+ key = key != null ? key : "";
return mBinder.get(key, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
}
@@ -184,6 +189,9 @@ public class KeyStore {
public int insert(String key, byte[] value, int uid, int flags) {
try {
+ if (value == null) {
+ value = new byte[0];
+ }
return mBinder.insert(key, value, uid, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -227,6 +235,9 @@ public class KeyStore {
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
}
@@ -275,6 +286,7 @@ public class KeyStore {
*/
public boolean unlock(int userId, String password) {
try {
+ password = password != null ? password : "";
mError = mBinder.unlock(userId, password);
return mError == NO_ERROR;
} catch (RemoteException e) {
@@ -329,16 +341,25 @@ public class KeyStore {
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
+
}
public boolean verify(String key, byte[] data, byte[] signature) {
try {
+ signature = signature != null ? signature : new byte[0];
return mBinder.verify(key, data, signature) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return false;
}
+
}
public String grant(String key, int uid) {
@@ -431,6 +452,8 @@ public class KeyStore {
public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
int flags, KeyCharacteristics outCharacteristics) {
try {
+ entropy = entropy != null ? entropy : new byte[0];
+ args = args != null ? args : new KeymasterArguments();
return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -446,6 +469,8 @@ public class KeyStore {
public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId,
int uid, KeyCharacteristics outCharacteristics) {
try {
+ clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+ appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
return mBinder.getKeyCharacteristics(alias, clientId, appId, uid, outCharacteristics);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -477,6 +502,8 @@ public class KeyStore {
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
KeymasterBlob appId, int uid) {
try {
+ clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+ appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
return mBinder.exportKey(alias, format, clientId, appId, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -491,6 +518,8 @@ public class KeyStore {
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy, int uid) {
try {
+ args = args != null ? args : new KeymasterArguments();
+ entropy = entropy != null ? entropy : new byte[0];
return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -500,11 +529,15 @@ public class KeyStore {
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy) {
+ entropy = entropy != null ? entropy : new byte[0];
+ args = args != null ? args : new KeymasterArguments();
return begin(alias, purpose, pruneable, args, entropy, UID_SELF);
}
public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
try {
+ arguments = arguments != null ? arguments : new KeymasterArguments();
+ input = input != null ? input : new byte[0];
return mBinder.update(token, arguments, input);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -515,6 +548,9 @@ public class KeyStore {
public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature,
byte[] entropy) {
try {
+ arguments = arguments != null ? arguments : new KeymasterArguments();
+ entropy = entropy != null ? entropy : new byte[0];
+ signature = signature != null ? signature : new byte[0];
return mBinder.finish(token, arguments, signature, entropy);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -631,6 +667,12 @@ public class KeyStore {
public int attestKey(
String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
try {
+ if (params == null) {
+ params = new KeymasterArguments();
+ }
+ if (outChain == null) {
+ outChain = new KeymasterCertificateChain();
+ }
return mBinder.attestKey(alias, params, outChain);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -640,6 +682,12 @@ public class KeyStore {
public int attestDeviceIds(KeymasterArguments params, KeymasterCertificateChain outChain) {
try {
+ if (params == null) {
+ params = new KeymasterArguments();
+ }
+ if (outChain == null) {
+ outChain = new KeymasterCertificateChain();
+ }
return mBinder.attestDeviceIds(params, outChain);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -762,6 +810,10 @@ public class KeyStore {
}
private long getFingerprintOnlySid() {
+ final PackageManager packageManager = mContext.getPackageManager();
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ return 0;
+ }
FingerprintManager fingerprintManager = mContext.getSystemService(FingerprintManager.class);
if (fingerprintManager == null) {
return 0;
diff --git a/android/service/autofill/FieldsDetection.java b/android/service/autofill/FieldsDetection.java
new file mode 100644
index 00000000..550ecf68
--- /dev/null
+++ b/android/service/autofill/FieldsDetection.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+
+/**
+ * Class by service to improve autofillable fields detection by tracking the meaning of fields
+ * manually edited by the user (when they match values provided by the service).
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - unhide / remove testApi
+ * - add FieldsDetection management so service can set it just once and reference it in further
+ * calls to improve performance (and also API to refresh it)
+ * - rename to FieldsDetectionInfo or FieldClassification? (same for CTS tests)
+ * - add FieldsDetectionUnitTest once API is well-defined
+ * @hide
+ */
+@TestApi
+public final class FieldsDetection implements Parcelable {
+
+ private final AutofillId mFieldId;
+ private final String mRemoteId;
+ private final String mValue;
+
+ /**
+ * Creates a field detection for just one field / value pair.
+ *
+ * @param fieldId autofill id of the field in the screen.
+ * @param remoteId id used by the service to identify the field later.
+ * @param value field value known to the service.
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - change signature to allow more fields / values / match methods
+ * - might also need to use a builder, where the constructor is the id for the fieldsdetector
+ * - might need id for values as well
+ * - add @NonNull / check it / add unit tests
+ * - make 'value' input more generic so it can accept distance-based match and other matches
+ * - throw exception if field value is less than X characters (somewhere between 7-10)
+ * - make sure to limit total number of fields to around 10 or so
+ * - use AutofillValue instead of String (so it can compare dates, for example)
+ */
+ public FieldsDetection(AutofillId fieldId, String remoteId, String value) {
+ mFieldId = fieldId;
+ mRemoteId = remoteId;
+ mValue = value;
+ }
+
+ /** @hide */
+ public AutofillId getFieldId() {
+ return mFieldId;
+ }
+
+ /** @hide */
+ public String getRemoteId() {
+ return mRemoteId;
+ }
+
+ /** @hide */
+ public String getValue() {
+ return mValue;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ // Cannot disclose remoteId or value because they could contain PII
+ return new StringBuilder("FieldsDetection: [field=").append(mFieldId)
+ .append(", remoteId_length=").append(mRemoteId.length())
+ .append(", value_length=").append(mValue.length())
+ .append("]").toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mFieldId, flags);
+ parcel.writeString(mRemoteId);
+ parcel.writeString(mValue);
+ }
+
+ public static final Parcelable.Creator<FieldsDetection> CREATOR =
+ new Parcelable.Creator<FieldsDetection>() {
+ @Override
+ public FieldsDetection createFromParcel(Parcel parcel) {
+ // TODO(b/67867469): remove comment below if it does not use a builder at the end
+ // Always go through the builder to ensure the data ingested by
+ // the system obeys the contract of the builder to avoid attacks
+ // using specially crafted parcels.
+ return new FieldsDetection(parcel.readParcelable(null), parcel.readString(),
+ parcel.readString());
+ }
+
+ @Override
+ public FieldsDetection[] newArray(int size) {
+ return new FieldsDetection[size];
+ }
+ };
+}
diff --git a/android/service/autofill/FillEventHistory.java b/android/service/autofill/FillEventHistory.java
index b1857b30..736d9ef4 100644
--- a/android/service/autofill/FillEventHistory.java
+++ b/android/service/autofill/FillEventHistory.java
@@ -19,6 +19,7 @@ package android.service.autofill;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
@@ -164,6 +165,10 @@ public final class FillEventHistory implements Parcelable {
dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
}
}
+ dest.writeString(event.mDetectedRemoteId);
+ if (event.mDetectedRemoteId != null) {
+ dest.writeInt(event.mDetectedFieldScore);
+ }
}
}
}
@@ -226,6 +231,7 @@ public final class FillEventHistory implements Parcelable {
* <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
* contexts.
*/
+ // TODO(b/67867469): update with field detection behavior
public static final int TYPE_CONTEXT_COMMITTED = 4;
/** @hide */
@@ -253,6 +259,9 @@ public final class FillEventHistory implements Parcelable {
@Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
@Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+ @Nullable private final String mDetectedRemoteId;
+ private final int mDetectedFieldScore;
+
/**
* Returns the type of the event.
*
@@ -355,6 +364,39 @@ public final class FillEventHistory implements Parcelable {
}
/**
+ * Gets the results of the last {@link FieldsDetection} request.
+ *
+ * @return map of edit-distance match ({@code 0} means full match,
+ * {@code 1} means 1 character different, etc...) by remote id (as set in the
+ * {@link FieldsDetection} constructor), or {@code null} if none of the user-input values
+ * matched the requested detection.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
+ * service requested {@link FillResponse.Builder#setFieldsDetection(FieldsDetection) fields
+ * detection}.
+ *
+ * TODO(b/67867469):
+ * - improve javadoc
+ * - refine score meaning (for example, should 1 be different of -1?)
+ * - mention when it's set
+ * - unhide
+ * - unhide / remove testApi
+ * - add @NonNull / check it / add unit tests
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull public Map<String, Integer> getDetectedFields() {
+ if (mDetectedRemoteId == null || mDetectedFieldScore == -1) {
+ return Collections.emptyMap();
+ }
+
+ final ArrayMap<String, Integer> map = new ArrayMap<>(1);
+ map.put(mDetectedRemoteId, mDetectedFieldScore);
+ return map;
+ }
+
+ /**
* Returns which fields were available on datasets provided by the service but manually
* entered by the user.
*
@@ -430,7 +472,6 @@ public final class FillEventHistory implements Parcelable {
* and belonged to datasets.
* @param manuallyFilledDatasetIds The ids of datasets that had values matching the
* respective entry on {@code manuallyFilledFieldIds}.
- *
* @throws IllegalArgumentException If the length of {@code changedFieldIds} and
* {@code changedDatasetIds} doesn't match.
* @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
@@ -438,13 +479,15 @@ public final class FillEventHistory implements Parcelable {
*
* @hide
*/
+ // TODO(b/67867469): document detection field parameters once stable
public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
@Nullable List<String> selectedDatasetIds,
@Nullable ArraySet<String> ignoredDatasetIds,
@Nullable ArrayList<AutofillId> changedFieldIds,
@Nullable ArrayList<String> changedDatasetIds,
@Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
- @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+ @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
+ @Nullable String detectedRemoteId, int detectedFieldScore) {
mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
"eventType");
mDatasetId = datasetId;
@@ -467,6 +510,8 @@ public final class FillEventHistory implements Parcelable {
}
mManuallyFilledFieldIds = manuallyFilledFieldIds;
mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
+ mDetectedRemoteId = detectedRemoteId;
+ mDetectedFieldScore = detectedFieldScore;
}
@Override
@@ -479,6 +524,8 @@ public final class FillEventHistory implements Parcelable {
+ ", changedDatasetsIds=" + mChangedDatasetIds
+ ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+ ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+ + ", detectedRemoteId=" + mDetectedRemoteId
+ + ", detectedFieldScore=" + mDetectedFieldScore
+ "]";
}
}
@@ -514,11 +561,15 @@ public final class FillEventHistory implements Parcelable {
} else {
manuallyFilledDatasetIds = null;
}
+ final String detectedRemoteId = parcel.readString();
+ final int detectedFieldScore = detectedRemoteId == null ? -1
+ : parcel.readInt();
selection.addEvent(new Event(eventType, datasetId, clientState,
selectedDatasetIds, ignoredDatasets,
changedFieldIds, changedDatasetIds,
- manuallyFilledFieldIds, manuallyFilledDatasetIds));
+ manuallyFilledFieldIds, manuallyFilledDatasetIds,
+ detectedRemoteId, detectedFieldScore));
}
return selection;
}
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index 2f6342af..4e6a8845 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -22,6 +22,7 @@ import static android.view.autofill.Helper.sDebug;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.Activity;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
@@ -75,6 +76,7 @@ public final class FillResponse implements Parcelable {
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
private final long mDisableDuration;
+ private final @Nullable FieldsDetection mFieldsDetection;
private final int mFlags;
private int mRequestId;
@@ -87,6 +89,7 @@ public final class FillResponse implements Parcelable {
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
mDisableDuration = builder.mDisableDuration;
+ mFieldsDetection = builder.mFieldsDetection;
mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -132,6 +135,11 @@ public final class FillResponse implements Parcelable {
}
/** @hide */
+ public @Nullable FieldsDetection getFieldsDetection() {
+ return mFieldsDetection;
+ }
+
+ /** @hide */
public int getFlags() {
return mFlags;
}
@@ -167,6 +175,7 @@ public final class FillResponse implements Parcelable {
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
private long mDisableDuration;
+ private FieldsDetection mFieldsDetection;
private int mFlags;
private boolean mDestroyed;
@@ -315,6 +324,25 @@ public final class FillResponse implements Parcelable {
}
/**
+ * TODO(b/67867469):
+ * - javadoc it
+ * - javadoc how to check results
+ * - unhide
+ * - unhide / remove testApi
+ * - throw exception (and document) if response has datasets or saveinfo
+ * - throw exception (and document) if id on fieldsDetection is ignored
+ *
+ * @hide
+ */
+ @TestApi
+ public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) {
+ throwIfDestroyed();
+ throwIfDisableAutofillCalled();
+ mFieldsDetection = Preconditions.checkNotNull(fieldsDetection);
+ return this;
+ }
+
+ /**
* Sets flags changing the response behavior.
*
* @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
@@ -365,7 +393,8 @@ public final class FillResponse implements Parcelable {
if (duration <= 0) {
throw new IllegalArgumentException("duration must be greater than 0");
}
- if (mAuthentication != null || mDatasets != null || mSaveInfo != null) {
+ if (mAuthentication != null || mDatasets != null || mSaveInfo != null
+ || mFieldsDetection != null) {
throw new IllegalStateException("disableAutofill() must be the only method called");
}
@@ -388,11 +417,11 @@ public final class FillResponse implements Parcelable {
*/
public FillResponse build() {
throwIfDestroyed();
-
if (mAuthentication == null && mDatasets == null && mSaveInfo == null
- && mDisableDuration == 0) {
- throw new IllegalStateException("need to provide at least one DataSet or a "
- + "SaveInfo or an authentication with a presentation or disable autofill");
+ && mDisableDuration == 0 && mFieldsDetection == null) {
+ throw new IllegalStateException("need to provide: at least one DataSet, or a "
+ + "SaveInfo, or an authentication with a presentation, "
+ + "or a FieldsDetection, or disable autofill");
}
mDestroyed = true;
return new FillResponse(this);
@@ -430,6 +459,7 @@ public final class FillResponse implements Parcelable {
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
.append(", disableDuration=").append(mDisableDuration)
.append(", flags=").append(mFlags)
+ .append(", fieldDetection=").append(mFieldsDetection)
.append("]")
.toString();
}
@@ -453,6 +483,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
parcel.writeLong(mDisableDuration);
+ parcel.writeParcelable(mFieldsDetection, flags);
parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -488,6 +519,10 @@ public final class FillResponse implements Parcelable {
if (disableDuration > 0) {
builder.disableAutofill(disableDuration);
}
+ final FieldsDetection fieldsDetection = parcel.readParcelable(null);
+ if (fieldsDetection != null) {
+ builder.setFieldsDetection(fieldsDetection);
+ }
builder.setFlags(parcel.readInt());
final FillResponse response = builder.build();
diff --git a/android/service/dreams/DreamService.java b/android/service/dreams/DreamService.java
index 6a15aded..2a245d04 100644
--- a/android/service/dreams/DreamService.java
+++ b/android/service/dreams/DreamService.java
@@ -680,8 +680,8 @@ public class DreamService extends Service implements Window.Callback {
*
* @return The screen state to use while dozing, such as {@link Display#STATE_ON},
* {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
- * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
- * behavior.
+ * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+ * for the default behavior.
*
* @see #setDozeScreenState
* @hide For use by system UI components only.
@@ -700,12 +700,18 @@ public class DreamService extends Service implements Window.Callback {
* perform transitions between states while dozing to conserve power and
* achieve various effects.
* </p><p>
- * It is recommended that the state be set to {@link Display#STATE_DOZE_SUSPEND}
- * once the dream has completely finished drawing and before it releases its wakelock
- * to allow the display hardware to be fully suspended. While suspended, the
- * display will preserve its on-screen contents or hand off control to dedicated
- * doze hardware if the devices supports it. If the doze suspend state is
- * used, the dream must make sure to set the mode back
+ * Some devices will have dedicated hardware ("Sidekick") to animate
+ * the display content while the CPU sleeps. If the dream and the hardware support
+ * this, {@link Display#STATE_ON_SUSPEND} or {@link Display#STATE_DOZE_SUSPEND}
+ * will switch control to the Sidekick.
+ * </p><p>
+ * If not using Sidekick, it is recommended that the state be set to
+ * {@link Display#STATE_DOZE_SUSPEND} once the dream has completely
+ * finished drawing and before it releases its wakelock
+ * to allow the display hardware to be fully suspended. While suspended,
+ * the display will preserve its on-screen contents.
+ * </p><p>
+ * If the doze suspend state is used, the dream must make sure to set the mode back
* to {@link Display#STATE_DOZE} or {@link Display#STATE_ON} before drawing again
* since the display updates may be ignored and not seen by the user otherwise.
* </p><p>
@@ -716,8 +722,8 @@ public class DreamService extends Service implements Window.Callback {
*
* @param state The screen state to use while dozing, such as {@link Display#STATE_ON},
* {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
- * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
- * behavior.
+ * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+ * for the default behavior.
*
* @hide For use by system UI components only.
*/
diff --git a/android/service/euicc/EuiccService.java b/android/service/euicc/EuiccService.java
index 0c2e4b7a..cd233b83 100644
--- a/android/service/euicc/EuiccService.java
+++ b/android/service/euicc/EuiccService.java
@@ -97,6 +97,10 @@ public abstract class EuiccService extends Service {
public static final String ACTION_RESOLVE_NO_PRIVILEGES =
"android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
+ /** Ask the user to input carrier confirmation code. */
+ public static final String ACTION_RESOLVE_CONFIRMATION_CODE =
+ "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
+
/** Intent extra set for resolution requests containing the package name of the calling app. */
public static final String EXTRA_RESOLUTION_CALLING_PACKAGE =
"android.service.euicc.extra.RESOLUTION_CALLING_PACKAGE";
@@ -105,6 +109,8 @@ public abstract class EuiccService extends Service {
public static final int RESULT_OK = 0;
/** Result code indicating that an active SIM must be deactivated to perform the operation. */
public static final int RESULT_MUST_DEACTIVATE_SIM = -1;
+ /** Result code indicating that the user must input a carrier confirmation code. */
+ public static final int RESULT_NEED_CONFIRMATION_CODE = -2;
// New predefined codes should have negative values.
/** Start of implementation-specific error results. */
@@ -119,10 +125,13 @@ public abstract class EuiccService extends Service {
RESOLUTION_ACTIONS = new ArraySet<>();
RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
+ RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE);
}
/** Boolean extra for resolution actions indicating whether the user granted consent. */
public static final String RESOLUTION_EXTRA_CONSENT = "consent";
+ /** String extra for resolution actions indicating the carrier confirmation code. */
+ public static final String RESOLUTION_EXTRA_CONFIRMATION_CODE = "confirmation_code";
private final IEuiccService.Stub mStubWrapper;
diff --git a/android/service/notification/ConditionProviderService.java b/android/service/notification/ConditionProviderService.java
index 3e992ec3..6fc689ab 100644
--- a/android/service/notification/ConditionProviderService.java
+++ b/android/service/notification/ConditionProviderService.java
@@ -18,6 +18,8 @@ package android.service.notification;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Service;
import android.content.ComponentName;
@@ -56,6 +58,8 @@ import android.util.Log;
* &lt;/meta-data>
* &lt;/service></pre>
*
+ * <p> Condition providers cannot be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
*/
public abstract class ConditionProviderService extends Service {
private final String TAG = ConditionProviderService.class.getSimpleName()
@@ -197,7 +201,11 @@ public abstract class ConditionProviderService extends Service {
return mProvider;
}
- private boolean isBound() {
+ /**
+ * @hide
+ */
+ @TestApi
+ public boolean isBound() {
if (mProvider == null) {
Log.w(TAG, "Condition provider service not yet bound.");
return false;
diff --git a/android/service/notification/NotificationListenerService.java b/android/service/notification/NotificationListenerService.java
index 08d3118b..dac663e7 100644
--- a/android/service/notification/NotificationListenerService.java
+++ b/android/service/notification/NotificationListenerService.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification.Builder;
@@ -82,6 +83,8 @@ import java.util.List;
* method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
* or after {@link #onListenerDisconnected()}.
* </p>
+ * <p> Notification listeners cannot get notification access or be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
*/
public abstract class NotificationListenerService extends Service {
diff --git a/android/service/voice/VoiceInteractionSession.java b/android/service/voice/VoiceInteractionSession.java
index 625dd9eb..cd177c42 100644
--- a/android/service/voice/VoiceInteractionSession.java
+++ b/android/service/voice/VoiceInteractionSession.java
@@ -16,6 +16,8 @@
package android.service.voice;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.app.Activity;
import android.app.Dialog;
@@ -46,7 +48,6 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -63,8 +64,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
/**
* An active voice interaction session, providing a facility for the implementation
* to interact with the user in the voice interaction layer. The user interface is
@@ -110,16 +109,6 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
*/
public static final int SHOW_SOURCE_ACTIVITY = 1<<4;
- // Keys for Bundle values
- /** @hide */
- public static final String KEY_DATA = "data";
- /** @hide */
- public static final String KEY_STRUCTURE = "structure";
- /** @hide */
- public static final String KEY_CONTENT = "content";
- /** @hide */
- public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
-
final Context mContext;
final HandlerCaller mHandlerCaller;
@@ -1423,9 +1412,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
public void setContentView(View view) {
ensureWindowCreated();
mContentFrame.removeAllViews();
- mContentFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ mContentFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentFrame.requestApplyInsets();
}
diff --git a/android/support/LibraryGroups.java b/android/support/LibraryGroups.java
new file mode 100644
index 00000000..feaefbc6
--- /dev/null
+++ b/android/support/LibraryGroups.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+/**
+ * The list of maven group names of all the libraries in this project.
+ */
+public class LibraryGroups {
+ public static final String SUPPORT = "com.android.support";
+ public static final String ROOM = "android.arch.persistence.room";
+ public static final String PERSISTENCE = "android.arch.persistence";
+ public static final String LIFECYCLE = "android.arch.lifecycle";
+ public static final String ARCH_CORE = "android.arch.core";
+ public static final String PAGING = "android.arch.paging";
+ public static final String NAVIGATION = "android.arch.navigation";
+}
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index 2f5730a2..efa0cbae 100644
--- a/android/support/LibraryVersions.java
+++ b/android/support/LibraryVersions.java
@@ -28,7 +28,7 @@ public class LibraryVersions {
/**
* Version code for flatfoot 1.0 projects (room, lifecycles)
*/
- private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-rc1");
+ private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0");
/**
* Version code for Room
diff --git a/android/support/SourceJarTaskHelper.java b/android/support/SourceJarTaskHelper.java
new file mode 100644
index 00000000..9fbd1dba
--- /dev/null
+++ b/android/support/SourceJarTaskHelper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+import com.android.build.gradle.LibraryExtension;
+import com.android.builder.core.BuilderConstants;
+
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.bundling.Jar;
+
+/**
+ * Helper class to handle creation of source jars.
+ */
+public class SourceJarTaskHelper {
+ /**
+ * Sets up a source jar task for an Android library project.
+ */
+ public static void setUpAndroidProject(Project project, LibraryExtension extension) {
+ // Create sources jar for release builds
+ extension.getLibraryVariants().all(libraryVariant -> {
+ if (!libraryVariant.getBuildType().getName().equals(BuilderConstants.RELEASE)) {
+ return; // Skip non-release builds.
+ }
+
+ Jar sourceJar = project.getTasks().create("sourceJarRelease", Jar.class);
+ sourceJar.setPreserveFileTimestamps(false);
+ sourceJar.setClassifier("sources");
+ sourceJar.from(extension.getSourceSets().findByName("main").getJava().getSrcDirs());
+ project.getArtifacts().add("archives", sourceJar);
+ });
+ }
+
+ /**
+ * Sets up a source jar task for a Java library project.
+ */
+ public static void setUpJavaProject(Project project) {
+ Jar sourceJar = project.getTasks().create("sourceJar", Jar.class);
+ sourceJar.setPreserveFileTimestamps(false);
+ sourceJar.setClassifier("sources");
+ JavaPluginConvention convention =
+ project.getConvention().getPlugin(JavaPluginConvention.class);
+ sourceJar.from(convention.getSourceSets().findByName("main").getAllSource().getSrcDirs());
+ project.getArtifacts().add("archives", sourceJar);
+ }
+}
diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java
index 7100218a..f46c652b 100644
--- a/android/support/car/drawer/CarDrawerActivity.java
+++ b/android/support/car/drawer/CarDrawerActivity.java
@@ -46,7 +46,7 @@ import android.view.ViewGroup;
*
* <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
* CarDrawerAdapter for the next level to
- * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}.
+ * {@link CarDrawerController#pushAdapter(CarDrawerAdapter)}.
*
* <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
* derivative.
diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java
index 4d9f4e99..7b23714c 100644
--- a/android/support/car/drawer/CarDrawerController.java
+++ b/android/support/car/drawer/CarDrawerController.java
@@ -19,16 +19,19 @@ package android.support.car.drawer;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.support.annotation.AnimRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.car.R;
import android.support.car.widget.PagedListView;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
+import android.view.animation.AnimationUtils;
import android.widget.ProgressBar;
import java.util.Stack;
@@ -39,13 +42,21 @@ import java.util.Stack;
* navigation.
*/
public class CarDrawerController {
+ /** An animation for when a user navigates into a submenu. */
+ @AnimRes
+ private static final int DRILL_DOWN_ANIM = R.anim.fade_in_trans_right_layout_anim;
+
+ /** An animation for when a user navigates up (when the back button is pressed). */
+ @AnimRes
+ private static final int NAVIGATE_UP_ANIM = R.anim.fade_in_trans_left_layout_anim;
+
/** The amount that the drawer has been opened before its color should be switched. */
private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
/**
* A representation of the hierarchy of navigation being displayed in the list. The ordering of
* this stack is the order that the user has visited each level. When the user navigates up,
- * the adapters are poopped from this list.
+ * the adapters are popped from this list.
*/
private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
@@ -78,16 +89,14 @@ public class CarDrawerController {
ActionBarDrawerToggle drawerToggle) {
mToolbar = toolbar;
mContext = drawerLayout.getContext();
-
+ mDrawerToggle = drawerToggle;
mDrawerLayout = drawerLayout;
mDrawerContent = drawerLayout.findViewById(R.id.drawer_content);
mDrawerList = drawerLayout.findViewById(R.id.drawer_list);
mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
-
mProgressBar = drawerLayout.findViewById(R.id.drawer_progress);
- mDrawerToggle = drawerToggle;
setupDrawerToggling();
}
@@ -104,7 +113,13 @@ public class CarDrawerController {
return;
}
- mAdapterStack.push(rootAdapter);
+ // The root adapter is always the last item in the stack.
+ if (mAdapterStack.size() > 0) {
+ mAdapterStack.set(0, rootAdapter);
+ } else {
+ mAdapterStack.push(rootAdapter);
+ }
+
setToolbarTitleFrom(rootAdapter);
mDrawerList.setAdapter(rootAdapter);
}
@@ -120,10 +135,11 @@ public class CarDrawerController {
*
* @param adapter Adapter for next level of content in the drawer.
*/
- public final void switchToAdapter(CarDrawerAdapter adapter) {
+ public final void pushAdapter(CarDrawerAdapter adapter) {
mAdapterStack.peek().setTitleChangeListener(null);
mAdapterStack.push(adapter);
- switchToAdapterInternal(adapter);
+ setDisplayAdapter(adapter);
+ runLayoutAnimation(DRILL_DOWN_ANIM);
}
/** Close the drawer. */
@@ -264,15 +280,15 @@ public class CarDrawerController {
}
/**
- * Sets the navigation drawer's title to be the one supplied by the given adapter and updates
- * the navigation drawer list with the adapter's contents.
+ * Sets the given adapter as the one displaying the current contents of the drawer.
+ *
+ * <p>The drawer's title will also be derived from the given adapter.
*/
- private void switchToAdapterInternal(CarDrawerAdapter adapter) {
+ private void setDisplayAdapter(CarDrawerAdapter adapter) {
setToolbarTitleFrom(adapter);
// NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
// car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
mDrawerList.getRecyclerView().setAdapter(adapter);
- scrollToPosition(0);
}
/**
@@ -290,7 +306,8 @@ public class CarDrawerController {
CarDrawerAdapter adapter = mAdapterStack.pop();
adapter.setTitleChangeListener(null);
adapter.cleanup();
- switchToAdapterInternal(mAdapterStack.peek());
+ setDisplayAdapter(mAdapterStack.peek());
+ runLayoutAnimation(NAVIGATE_UP_ANIM);
return true;
}
@@ -301,6 +318,18 @@ public class CarDrawerController {
adapter.setTitleChangeListener(null);
adapter.cleanup();
}
- switchToAdapterInternal(mAdapterStack.peek());
+ setDisplayAdapter(mAdapterStack.peek());
+ runLayoutAnimation(NAVIGATE_UP_ANIM);
+ }
+
+ /**
+ * Runs the given layout animation on the PagedListView. Running this animation will also
+ * refresh the contents of the list.
+ */
+ private void runLayoutAnimation(@AnimRes int animation) {
+ RecyclerView recyclerView = mDrawerList.getRecyclerView();
+ recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, animation));
+ recyclerView.getAdapter().notifyDataSetChanged();
+ recyclerView.scheduleLayoutAnimation();
}
}
diff --git a/android/support/car/utils/ColumnCalculator.java b/android/support/car/utils/ColumnCalculator.java
index 96e081b9..fa5dd432 100644
--- a/android/support/car/utils/ColumnCalculator.java
+++ b/android/support/car/utils/ColumnCalculator.java
@@ -65,7 +65,7 @@ public class ColumnCalculator {
private ColumnCalculator(Context context) {
Resources res = context.getResources();
- int marginSize = res.getDimensionPixelSize(R.dimen.car_screen_margin_size);
+ int marginSize = res.getDimensionPixelSize(R.dimen.car_margin);
mGutterSize = res.getDimensionPixelSize(R.dimen.car_screen_gutter_size);
mNumOfColumns = res.getInteger(R.integer.car_screen_num_of_columns);
diff --git a/android/support/car/widget/CarItemAnimator.java b/android/support/car/widget/CarItemAnimator.java
index 4dd32127..ef22c484 100644
--- a/android/support/car/widget/CarItemAnimator.java
+++ b/android/support/car/widget/CarItemAnimator.java
@@ -22,9 +22,9 @@ import android.support.v7.widget.RecyclerView;
/** {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior. */
public class CarItemAnimator extends DefaultItemAnimator {
- private final CarLayoutManager mLayoutManager;
+ private final PagedLayoutManager mLayoutManager;
- public CarItemAnimator(CarLayoutManager layoutManager) {
+ public CarItemAnimator(PagedLayoutManager layoutManager) {
mLayoutManager = layoutManager;
}
diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java
index 2684c58a..bb9cb71a 100644
--- a/android/support/car/widget/CarRecyclerView.java
+++ b/android/support/car/widget/CarRecyclerView.java
@@ -26,7 +26,7 @@ import android.view.View;
import android.view.ViewGroup;
/**
- * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate.
+ * Custom {@link RecyclerView} that helps {@link PagedLayoutManager} properly fling and paginate.
*
* <p>It also has the ability to fade children as they scroll off screen that can be set with {@link
* #setFadeLastItem(boolean)}.
@@ -57,7 +57,7 @@ public class CarRecyclerView extends RecyclerView {
@Override
public boolean fling(int velocityX, int velocityY) {
mWasFlingCalledForGesture = true;
- return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
+ return ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
}
@Override
@@ -69,7 +69,7 @@ public class CarRecyclerView extends RecyclerView {
int action = e.getActionMasked();
if (action == MotionEvent.ACTION_UP) {
if (!mWasFlingCalledForGesture) {
- ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
+ ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
}
mWasFlingCalledForGesture = false;
}
@@ -102,7 +102,7 @@ public class CarRecyclerView extends RecyclerView {
* number of items that fit completely on the screen.
*/
public void pageUp() {
- CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+ PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
int pageUpPosition = lm.getPageUpPosition();
if (pageUpPosition == -1) {
return;
@@ -116,7 +116,7 @@ public class CarRecyclerView extends RecyclerView {
* number of items that fit completely on the screen.
*/
public void pageDown() {
- CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+ PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
int pageDownPosition = lm.getPageDownPosition();
if (pageDownPosition == -1) {
return;
diff --git a/android/support/car/widget/CarLayoutManager.java b/android/support/car/widget/PagedLayoutManager.java
index d0d3a9e1..c4f469a3 100644
--- a/android/support/car/widget/CarLayoutManager.java
+++ b/android/support/car/widget/PagedLayoutManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package android.support.car.widget;
import android.content.Context;
import android.graphics.PointF;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
@@ -46,9 +48,9 @@ import java.util.ArrayList;
*
* <ol>
* <li>In a normal ListView, when views reach the top of the list, they are clipped. In
- * CarLayoutManager, views have the option of flying off of the top of the screen as the next
- * row settles in to place. This functionality can be enabled or disabled with {@link
- * #setOffsetRows(boolean)}.
+ * PagedLayoutManager, views have the option of flying off of the top of the screen as the
+ * next row settles in to place. This functionality can be enabled or disabled with
+ * {@link #setOffsetRows(boolean)}.
* <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle on the
* next page.
* <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that the
@@ -57,8 +59,8 @@ import java.util.ArrayList;
*
* This LayoutManger should be used with {@link CarRecyclerView}.
*/
-public class CarLayoutManager extends RecyclerView.LayoutManager {
- private static final String TAG = "CarLayoutManager";
+public class PagedLayoutManager extends RecyclerView.LayoutManager {
+ private static final String TAG = "PagedLayoutManager";
/**
* Any fling below the threshold will just scroll to the top fully visible row. The units is
@@ -166,7 +168,7 @@ public class CarLayoutManager extends RecyclerView.LayoutManager {
/** Set the anchor to the following position on the next layout pass. */
private int mPendingScrollPosition = -1;
- public CarLayoutManager(Context context) {
+ public PagedLayoutManager(Context context) {
mContext = context;
}
@@ -919,6 +921,55 @@ public class CarLayoutManager extends RecyclerView.LayoutManager {
return mLowerPageBreakPosition;
}
+ @Override
+ public Parcelable onSaveInstanceState() {
+ SavedState savedState = new SavedState();
+ savedState.mFirstChildPosition = getFirstFullyVisibleChildPosition();
+ return savedState;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof SavedState) {
+ scrollToPosition(((SavedState) state).mFirstChildPosition);
+ }
+ }
+
+ /** The state that will be saved across configuration changes. */
+ static class SavedState implements Parcelable {
+ /** The position of the first visible child view in the list. */
+ int mFirstChildPosition;
+
+ SavedState() {}
+
+ private SavedState(Parcel in) {
+ mFirstChildPosition = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFirstChildPosition);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
/**
* Layout the anchor row. The anchor row is the first fully visible row.
*
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 46527001..4695c45c 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -23,7 +23,11 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -33,6 +37,7 @@ import android.support.car.R;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -41,13 +46,19 @@ import android.widget.FrameLayout;
/**
* Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that
* resembles a {@link android.widget.ListView} but also has page up and page down arrows on the
- * right side.
+ * left side.
*/
public class PagedListView extends FrameLayout {
/** Default maximum number of clicks allowed on a list */
public static final int DEFAULT_MAX_CLICKS = 6;
/**
+ * Value to pass to {@link #setMaxPages(int)} to indicate there is no restriction on the
+ * maximum number of pages to show.
+ */
+ public static final int UNLIMITED_PAGES = -1;
+
+ /**
* The amount of time after settling to wait before autoscrolling to the next page when the user
* holds down a pagination button.
*/
@@ -57,7 +68,7 @@ public class PagedListView extends FrameLayout {
private static final int INVALID_RESOURCE_ID = -1;
protected final CarRecyclerView mRecyclerView;
- protected final CarLayoutManager mLayoutManager;
+ protected final PagedLayoutManager mLayoutManager;
protected final Handler mHandler = new Handler();
private final boolean mScrollBarEnabled;
private final PagedScrollBarView mScrollBarView;
@@ -65,8 +76,8 @@ public class PagedListView extends FrameLayout {
private int mRowsPerPage = -1;
protected RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
- /** Maximum number of pages to show. Values < 0 show all pages. */
- private int mMaxPages = -1;
+ /** Maximum number of pages to show. */
+ private int mMaxPages;
protected OnScrollListener mOnScrollListener;
@@ -115,8 +126,6 @@ public class PagedListView extends FrameLayout {
* the item in position 20 instead, for position 1 it will show the item in position 21 instead
* and so on.
*/
- // TODO(b/28003781): ItemPositionOffset and ItemCap interfaces should be merged once
- // we enable AlphaJump outside drawer.
public interface ItemPositionOffset {
/** Sets the position offset for the adapter. */
void setPositionOffset(int positionOffset);
@@ -151,7 +160,7 @@ public class PagedListView extends FrameLayout {
mMaxPages = getDefaultMaxPages();
- mLayoutManager = new CarLayoutManager(context);
+ mLayoutManager = new PagedLayoutManager(context);
mLayoutManager.setOffsetRows(offsetRows);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
@@ -162,7 +171,7 @@ public class PagedListView extends FrameLayout {
if (offsetScrollBar) {
MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
params.setMarginStart(getResources().getDimensionPixelSize(
- R.dimen.car_screen_margin_size));
+ R.dimen.car_margin));
params.setMarginEnd(
a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0));
mRecyclerView.setLayoutParams(params);
@@ -180,6 +189,11 @@ public class PagedListView extends FrameLayout {
dividerStartId, dividerEndId));
}
+ int itemSpacing = a.getDimensionPixelSize(R.styleable.PagedListView_itemSpacing, 0);
+ if (itemSpacing > 0) {
+ mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+ }
+
// Set this to true so that this view consumes clicks events and views underneath
// don't receive this click event. Without this it's possible to click places in the
// view that don't capture the event, and as a result, elements visually hidden consume
@@ -212,6 +226,16 @@ public class PagedListView extends FrameLayout {
}
});
+ Drawable upButtonIcon = a.getDrawable(R.styleable.PagedListView_upButtonIcon);
+ if (upButtonIcon != null) {
+ setUpButtonIcon(upButtonIcon);
+ }
+
+ Drawable downButtonIcon = a.getDrawable(R.styleable.PagedListView_downButtonIcon);
+ if (downButtonIcon != null) {
+ setDownButtonIcon(downButtonIcon);
+ }
+
mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
// Modify the layout the Scroll Bar is not visible.
@@ -236,7 +260,7 @@ public class PagedListView extends FrameLayout {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
// The user has interacted with the list using touch. All movements will now paginate
// the list.
- mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_PAGE);
+ mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_PAGE);
}
return super.onInterceptTouchEvent(e);
}
@@ -246,7 +270,7 @@ public class PagedListView extends FrameLayout {
super.requestChildFocus(child, focused);
// The user has interacted with the list using the controller. Movements through the list
// will now be one row at a time.
- mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
+ mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
}
/**
@@ -312,19 +336,25 @@ public class PagedListView extends FrameLayout {
mHandler.post(mUpdatePaginationRunnable);
}
+ /** Sets the icon to be used for the up button. */
+ public void setUpButtonIcon(Drawable icon) {
+ mScrollBarView.setUpButtonIcon(icon);
+ }
+
+ /** Sets the icon to be used for the down button. */
+ public void setDownButtonIcon(Drawable icon) {
+ mScrollBarView.setDownButtonIcon(icon);
+ }
+
/**
* Sets the adapter for the list.
*
- * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw an {@link
- * IllegalArgumentException}.
+ * <p>The given Adapter can implement {@link ItemCap} if it wishes to control the behavior of
+ * a max number of items. Otherwise, methods in the PagedListView to limit the content, such as
+ * {@link #setMaxPages(int)}, will do nothing.
*/
public void setAdapter(
@NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
- if (!(adapter instanceof ItemCap)) {
- throw new IllegalArgumentException("ERROR: adapter ["
- + adapter.getClass().getCanonicalName() + "] MUST implement ItemCap");
- }
-
mAdapter = adapter;
mRecyclerView.setAdapter(adapter);
updateMaxItems();
@@ -333,7 +363,7 @@ public class PagedListView extends FrameLayout {
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@NonNull
- public CarLayoutManager getLayoutManager() {
+ public PagedLayoutManager getLayoutManager() {
return mLayoutManager;
}
@@ -345,15 +375,19 @@ public class PagedListView extends FrameLayout {
/**
* Sets the maximum number of the pages that can be shown in the PagedListView. The size of a
- * page is defined as the number of items that fit completely on the screen at once.
+ * page is defined as the number of items that fit completely on the screen at once.
+ *
+ * <p>Passing {@link #UNLIMITED_PAGES} will remove any restrictions on a maximum number
+ * of pages.
+ *
+ * <p>Note that for any restriction on maximum pages to work, the adapter passed to this
+ * PagedListView needs to implement {@link ItemCap}.
*
- * @param maxPages The maximum number of pages that fit on the screen. Should be positive.
+ * @param maxPages The maximum number of pages that fit on the screen. Should be positive or
+ * {@link #UNLIMITED_PAGES}.
*/
public void setMaxPages(int maxPages) {
- if (maxPages < 0) {
- return;
- }
- mMaxPages = maxPages;
+ mMaxPages = Math.max(UNLIMITED_PAGES, maxPages);
updateMaxItems();
}
@@ -362,7 +396,8 @@ public class PagedListView extends FrameLayout {
* {@link #setMaxPages(int)}. If that method has not been called, then this value should match
* the default value.
*
- * @return The maximum number of pages to be shown.
+ * @return The maximum number of pages to be shown or {@link #UNLIMITED_PAGES} if there is
+ * no limit.
*/
public int getMaxPages() {
return mMaxPages;
@@ -370,7 +405,7 @@ public class PagedListView extends FrameLayout {
/**
* Gets the number of rows per page. Default value of mRowsPerPage is -1. If the first child of
- * CarLayoutManager is null or the height of the first child is 0, it will return 1.
+ * PagedLayoutManager is null or the height of the first child is 0, it will return 1.
*/
public int getRowsPerPage() {
return mRowsPerPage;
@@ -422,6 +457,32 @@ public class PagedListView extends FrameLayout {
}
/**
+ * Sets spacing between each item in the list. The spacing will not be added before the first
+ * item and after the last.
+ *
+ * @param itemSpacing the spacing between each item.
+ */
+ public void setItemSpacing(int itemSpacing) {
+ ItemSpacingDecoration existing = null;
+ for (int i = 0, count = mRecyclerView.getItemDecorationCount(); i < count; i++) {
+ RecyclerView.ItemDecoration itemDecoration = mRecyclerView.getItemDecorationAt(i);
+ if (itemDecoration instanceof ItemSpacingDecoration) {
+ existing = (ItemSpacingDecoration) itemDecoration;
+ break;
+ }
+ }
+
+ if (itemSpacing == 0 && existing != null) {
+ mRecyclerView.removeItemDecoration(existing);
+ } else if (existing == null) {
+ mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+ } else {
+ existing.setItemSpacing(itemSpacing);
+ }
+ mRecyclerView.invalidateItemDecorations();
+ }
+
+ /**
* Adds an {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} to this
* PagedListView.
*
@@ -520,6 +581,7 @@ public class PagedListView extends FrameLayout {
return;
}
mDefaultMaxPages = newDefault;
+ resetMaxPages();
}
/** Returns the default number of pages the list should have */
@@ -646,8 +708,15 @@ public class PagedListView extends FrameLayout {
return;
}
- final int originalCount = mAdapter.getItemCount();
+ // Ensure mRowsPerPage regardless of if the adapter implements ItemCap.
updateRowsPerPage();
+
+ // If the adapter does not implement ItemCap, then the max items on it cannot be updated.
+ if (!(mAdapter instanceof ItemCap)) {
+ return;
+ }
+
+ final int originalCount = mAdapter.getItemCount();
((ItemCap) mAdapter).setMaxItems(calculateMaxItemCount());
final int newCount = mAdapter.getItemCount();
if (newCount == originalCount) {
@@ -683,6 +752,78 @@ public class PagedListView extends FrameLayout {
}
}
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ SavedState savedState = new SavedState(super.onSaveInstanceState());
+ savedState.mLayoutManagerState = mLayoutManager.onSaveInstanceState();
+ return savedState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState savedState = (SavedState) state;
+ mLayoutManager.onRestoreInstanceState(savedState.mLayoutManagerState);
+ super.onRestoreInstanceState(savedState.getSuperState());
+ }
+
+ @Override
+ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+ // There is the possibility of multiple PagedListViews on a page. This means that the ids
+ // of the child Views of PagedListView are no longer unique, and onSaveInstanceState()
+ // cannot be used. As a result, PagedListViews needs to manually dispatch the instance
+ // states. Call dispatchFreezeSelfOnly() so that no child views have onSaveInstanceState()
+ // called by the system.
+ dispatchFreezeSelfOnly(container);
+ }
+
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ // Prevent onRestoreInstanceState() from being called on child Views. Instead, PagedListView
+ // will manually handle passing the state. See the comment in dispatchSaveInstanceState()
+ // for more information.
+ dispatchThawSelfOnly(container);
+ }
+
+ /** The state that will be saved across configuration changes. */
+ private static class SavedState extends BaseSavedState {
+ /** The state of the {@link #mLayoutManager} of this PagedListView. */
+ Parcelable mLayoutManagerState;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ mLayoutManagerState =
+ in.readParcelable(PagedLayoutManager.SavedState.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeParcelable(mLayoutManagerState, flags);
+ }
+
+ public static final ClassLoaderCreator<SavedState> CREATOR =
+ new ClassLoaderCreator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel source, ClassLoader loader) {
+ return new SavedState(source);
+ }
+
+ @Override
+ public SavedState createFromParcel(Parcel source) {
+ return createFromParcel(source, null /* loader */);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
new RecyclerView.OnScrollListener() {
@Override
@@ -766,16 +907,50 @@ public class PagedListView extends FrameLayout {
}
/**
+ * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will add spacing
+ * between each item in the RecyclerView that it is added to.
+ */
+ private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
+
+ private int mHalfItemSpacing;
+
+ private ItemSpacingDecoration(int itemSpacing) {
+ mHalfItemSpacing = itemSpacing / 2;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ // Skip top offset for first item and bottom offset for last.
+ int position = parent.getChildAdapterPosition(view);
+ if (position > 0) {
+ outRect.top = mHalfItemSpacing;
+ }
+ if (position < state.getItemCount() - 1) {
+ outRect.bottom = mHalfItemSpacing;
+ }
+ }
+
+ /**
+ * @param itemSpacing sets spacing between each item.
+ */
+ public void setItemSpacing(int itemSpacing) {
+ mHalfItemSpacing = itemSpacing / 2;
+ }
+ }
+
+ /**
* A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will draw a dividing
* line between each item in the RecyclerView that it is added to.
*/
- public static class DividerDecoration extends RecyclerView.ItemDecoration {
+ private static class DividerDecoration extends RecyclerView.ItemDecoration {
private final Context mContext;
private final Paint mPaint;
private final int mDividerHeight;
private final int mDividerStartMargin;
@IdRes private final int mDividerStartId;
- @IdRes private final int mDvidierEndId;
+ @IdRes private final int mDividerEndId;
/**
* @param dividerStartMargin The start offset of the dividing line. This offset will be
@@ -792,7 +967,7 @@ public class PagedListView extends FrameLayout {
mContext = context;
mDividerStartMargin = dividerStartMargin;
mDividerStartId = dividerStartId;
- mDvidierEndId = dividerEndId;
+ mDividerEndId = dividerEndId;
Resources res = context.getResources();
mPaint = new Paint();
@@ -807,16 +982,20 @@ public class PagedListView extends FrameLayout {
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
- for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++) {
+ // Draw a divider line between each item. No need to draw the line for the last item.
+ for (int i = 0, childCount = parent.getChildCount(); i < childCount - 1; i++) {
View container = parent.getChildAt(i);
+ View nextContainer = parent.getChildAt(i + 1);
+ int spacing = nextContainer.getTop() - container.getBottom();
+
View startChild =
mDividerStartId != INVALID_RESOURCE_ID
? container.findViewById(mDividerStartId)
: container;
View endChild =
- mDvidierEndId != INVALID_RESOURCE_ID
- ? container.findViewById(mDvidierEndId)
+ mDividerEndId != INVALID_RESOURCE_ID
+ ? container.findViewById(mDividerEndId)
: container;
if (startChild == null || endChild == null) {
@@ -825,14 +1004,24 @@ public class PagedListView extends FrameLayout {
int left = mDividerStartMargin + startChild.getLeft();
int right = endChild.getRight();
- int bottom = container.getBottom();
+ int bottom = container.getBottom() + spacing / 2 + mDividerHeight / 2;
int top = bottom - mDividerHeight;
- // Draw a divider line between each item. No need to draw the line for the last
- // item.
- if (i != childCount - 1) {
- c.drawRect(left, top, right, bottom, mPaint);
- }
+ c.drawRect(left, top, right, bottom, mPaint);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ // Skip top offset for first item and bottom offset for last.
+ int position = parent.getChildAdapterPosition(view);
+ if (position > 0) {
+ outRect.top = mDividerHeight / 2;
+ }
+ if (position < state.getItemCount() - 1) {
+ outRect.bottom = mDividerHeight / 2;
}
}
}
diff --git a/android/support/car/widget/PagedScrollBarView.java b/android/support/car/widget/PagedScrollBarView.java
index 125b354c..1c46b5d4 100644
--- a/android/support/car/widget/PagedScrollBarView.java
+++ b/android/support/car/widget/PagedScrollBarView.java
@@ -18,6 +18,7 @@ package android.support.car.widget;
import android.content.Context;
import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.support.car.R;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
@@ -98,6 +99,16 @@ public class PagedScrollBarView extends FrameLayout
return true;
}
+ /** Sets the icon to be used for the up button. */
+ public void setUpButtonIcon(Drawable icon) {
+ mUpButton.setImageDrawable(icon);
+ }
+
+ /** Sets the icon to be used for the down button. */
+ public void setDownButtonIcon(Drawable icon) {
+ mDownButton.setImageDrawable(icon);
+ }
+
/**
* Sets the listener that will be notified when the up and down buttons have been pressed.
*
@@ -119,7 +130,7 @@ public class PagedScrollBarView extends FrameLayout
/** Sets the range, offset and extent of the scroll bar. See {@link View}. */
public void setParameters(int range, int offset, int extent, boolean animate) {
- // This method is where we take the computed parameters from the CarLayoutManager and
+ // This method is where we take the computed parameters from the PagedLayoutManager and
// render it within the specified constraints ({@link #mMaxThumbLength} and
// {@link #mMinThumbLength}).
final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
deleted file mode 100644
index a18bcf32..00000000
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.testlib;
-
-/**
- * Constants used for sending intent between client and service apps.
- */
-public class IntentConstants {
- public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
- "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
- public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
- "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
- public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
- "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
- public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
- "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
- public static final String KEY_METHOD_ID = "method_id";
- public static final String KEY_ARGUMENT = "argument";
- public static final String KEY_SESSION_TOKEN = "session_token";
-}
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
index 95be1621..cbdccc1b 100644
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -40,7 +40,6 @@ public class MediaSessionConstants {
public static final int SET_RATING_TYPE = 117;
public static final String TEST_SESSION_TAG = "test-session-tag";
- public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
public static final String TEST_KEY = "test-key";
public static final String TEST_VALUE = "test-val";
public static final String TEST_SESSION_EVENT = "test-session-event";
diff --git a/android/support/mediacompat/testlib/VersionConstants.java b/android/support/mediacompat/testlib/VersionConstants.java
new file mode 100644
index 00000000..6533ee17
--- /dev/null
+++ b/android/support/mediacompat/testlib/VersionConstants.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib;
+
+/**
+ * Constants for getting support library version information.
+ */
+public class VersionConstants {
+ public static final String KEY_CLIENT_VERSION = "client_version";
+ public static final String KEY_SERVICE_VERSION = "service_version";
+}
diff --git a/android/support/mediacompat/testlib/util/IntentUtil.java b/android/support/mediacompat/testlib/util/IntentUtil.java
new file mode 100644
index 00000000..bbf97524
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/IntentUtil.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+/**
+ * Methods and constants used for sending intent between client and service apps.
+ */
+public class IntentUtil {
+
+ public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName(
+ "android.support.mediacompat.service.test",
+ "android.support.mediacompat.service.ServiceBroadcastReceiver");
+ public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName(
+ "android.support.mediacompat.client.test",
+ "android.support.mediacompat.client.ClientBroadcastReceiver");
+
+ public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
+ "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
+ public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
+ "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
+ public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
+ "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
+ public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
+ "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
+
+ public static final String KEY_METHOD_ID = "method_id";
+ public static final String KEY_ARGUMENT = "argument";
+ public static final String KEY_SESSION_TOKEN = "session_token";
+
+ /**
+ * Calls a method of MediaBrowserService. Used by client app.
+ */
+ public static void callMediaBrowserServiceMethod(int methodId, Object arg, Context context) {
+ Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ /**
+ * Calls a method of MediaSession. Used by client app.
+ */
+ public static void callMediaSessionMethod(int methodId, Object arg, Context context) {
+ Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_MEDIA_SESSION_METHOD);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ /**
+ * Calls a method of MediaController. Used by service app.
+ */
+ public static void callMediaControllerMethod(
+ int methodId, Object arg, Context context, Parcelable token) {
+ Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_MEDIA_CONTROLLER_METHOD);
+ intent.putExtra(KEY_SESSION_TOKEN, token);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ /**
+ * Calls a method of TransportControls. Used by service app.
+ */
+ public static void callTransportControlsMethod(
+ int methodId, Object arg, Context context, Parcelable token) {
+ Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_TRANSPORT_CONTROLS_METHOD);
+ intent.putExtra(KEY_SESSION_TOKEN, token);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ private static Intent createIntent(ComponentName componentName, int methodId, Object arg) {
+ Intent intent = new Intent();
+ intent.setComponent(componentName);
+ intent.putExtra(KEY_METHOD_ID, methodId);
+
+ if (arg instanceof String) {
+ intent.putExtra(KEY_ARGUMENT, (String) arg);
+ } else if (arg instanceof Integer) {
+ intent.putExtra(KEY_ARGUMENT, (int) arg);
+ } else if (arg instanceof Long) {
+ intent.putExtra(KEY_ARGUMENT, (long) arg);
+ } else if (arg instanceof Boolean) {
+ intent.putExtra(KEY_ARGUMENT, (boolean) arg);
+ } else if (arg instanceof Parcelable) {
+ intent.putExtra(KEY_ARGUMENT, (Parcelable) arg);
+ } else if (arg instanceof ArrayList<?>) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(KEY_ARGUMENT, (ArrayList<? extends Parcelable>) arg);
+ intent.putExtras(bundle);
+ } else if (arg instanceof Bundle) {
+ Bundle bundle = new Bundle();
+ bundle.putBundle(KEY_ARGUMENT, (Bundle) arg);
+ intent.putExtras(bundle);
+ }
+ return intent;
+ }
+}
diff --git a/android/support/mediacompat/testlib/util/PollingCheck.java b/android/support/mediacompat/testlib/util/PollingCheck.java
new file mode 100644
index 00000000..3412da02
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/PollingCheck.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import junit.framework.Assert;
+
+/**
+ * Utility used for testing that allows to poll for a certain condition to happen within a timeout.
+ * (Copied from testutils/src/main/java/android/support/testutils/PollingCheck.java.)
+ */
+public abstract class PollingCheck {
+ private static final long DEFAULT_TIMEOUT = 3000;
+ private static final long TIME_SLICE = 50;
+ private final long mTimeout;
+
+ /**
+ * The condition that the PollingCheck should use to proceed successfully.
+ */
+ public interface PollingCheckCondition {
+ /**
+ * @return Whether the polling condition has been met.
+ */
+ boolean canProceed();
+ }
+
+ public PollingCheck(long timeout) {
+ mTimeout = timeout;
+ }
+
+ protected abstract boolean check();
+
+ /**
+ * Start running the polling check.
+ */
+ public void run() {
+ if (check()) {
+ return;
+ }
+
+ long timeout = mTimeout;
+ while (timeout > 0) {
+ try {
+ Thread.sleep(TIME_SLICE);
+ } catch (InterruptedException e) {
+ Assert.fail("unexpected InterruptedException");
+ }
+
+ if (check()) {
+ return;
+ }
+
+ timeout -= TIME_SLICE;
+ }
+
+ Assert.fail("unexpected timeout");
+ }
+
+ /**
+ * Instantiate and start polling for a given condition with a default 3000ms timeout.
+ * @param condition The condition to check for success.
+ */
+ public static void waitFor(final PollingCheckCondition condition) {
+ new PollingCheck(DEFAULT_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return condition.canProceed();
+ }
+ }.run();
+ }
+
+ /**
+ * Instantiate and start polling for a given condition.
+ * @param timeout Time out in ms
+ * @param condition The condition to check for success.
+ */
+ public static void waitFor(long timeout, final PollingCheckCondition condition) {
+ new PollingCheck(timeout) {
+ @Override
+ protected boolean check() {
+ return condition.canProceed();
+ }
+ }.run();
+ }
+}
diff --git a/android/support/mediacompat/testlib/util/TestUtil.java b/android/support/mediacompat/testlib/util/TestUtil.java
new file mode 100644
index 00000000..d105510c
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/TestUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+
+import android.os.Bundle;
+
+/**
+ * Utility methods used for testing.
+ */
+public final class TestUtil {
+
+ /**
+ * Asserts that two Bundles are equal.
+ */
+ public static void assertBundleEquals(Bundle expected, Bundle observed) {
+ if (expected == null || observed == null) {
+ assertSame(expected, observed);
+ }
+ assertEquals(expected.size(), observed.size());
+ for (String key : expected.keySet()) {
+ assertEquals(expected.get(key), observed.get(key));
+ }
+ }
+}
diff --git a/android/support/text/emoji/widget/EmojiAppCompatEditText.java b/android/support/text/emoji/widget/EmojiAppCompatEditText.java
index 87c17c20..0ae4ea04 100644
--- a/android/support/text/emoji/widget/EmojiAppCompatEditText.java
+++ b/android/support/text/emoji/widget/EmojiAppCompatEditText.java
@@ -21,6 +21,7 @@ import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.text.emoji.EmojiCompat;
import android.support.v7.widget.AppCompatEditText;
+import android.text.method.KeyListener;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -67,8 +68,11 @@ public class EmojiAppCompatEditText extends AppCompatEditText {
}
@Override
- public void setKeyListener(android.text.method.KeyListener input) {
- super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input));
+ public void setKeyListener(@Nullable KeyListener keyListener) {
+ if (keyListener != null) {
+ keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+ }
+ super.setKeyListener(keyListener);
}
@Override
diff --git a/android/support/text/emoji/widget/EmojiEditText.java b/android/support/text/emoji/widget/EmojiEditText.java
index a0e8a69e..70ca7a66 100644
--- a/android/support/text/emoji/widget/EmojiEditText.java
+++ b/android/support/text/emoji/widget/EmojiEditText.java
@@ -21,6 +21,7 @@ import android.os.Build;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.text.emoji.EmojiCompat;
+import android.text.method.KeyListener;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -73,8 +74,11 @@ public class EmojiEditText extends EditText {
}
@Override
- public void setKeyListener(android.text.method.KeyListener keyListener) {
- super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+ public void setKeyListener(@Nullable KeyListener keyListener) {
+ if (keyListener != null) {
+ keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+ }
+ super.setKeyListener(keyListener);
}
@Override
diff --git a/android/support/text/emoji/widget/EmojiExtractEditText.java b/android/support/text/emoji/widget/EmojiExtractEditText.java
index ca1868e2..2e4d3caa 100644
--- a/android/support/text/emoji/widget/EmojiExtractEditText.java
+++ b/android/support/text/emoji/widget/EmojiExtractEditText.java
@@ -27,6 +27,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.text.emoji.EmojiCompat;
import android.support.text.emoji.EmojiSpan;
+import android.text.method.KeyListener;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -81,8 +82,11 @@ public class EmojiExtractEditText extends ExtractEditText {
}
@Override
- public void setKeyListener(android.text.method.KeyListener keyListener) {
- super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+ public void setKeyListener(@Nullable KeyListener keyListener) {
+ if (keyListener != null) {
+ keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+ }
+ super.setKeyListener(keyListener);
}
@Override
diff --git a/android/support/transition/Transition.java b/android/support/transition/Transition.java
index 04cc57bd..9c198a94 100644
--- a/android/support/transition/Transition.java
+++ b/android/support/transition/Transition.java
@@ -1017,7 +1017,7 @@ public abstract class Transition implements Cloneable {
*/
@NonNull
public Transition addTarget(@IdRes int targetId) {
- if (targetId > 0) {
+ if (targetId != 0) {
mTargetIds.add(targetId);
}
return this;
@@ -1107,7 +1107,7 @@ public abstract class Transition implements Cloneable {
*/
@NonNull
public Transition removeTarget(@IdRes int targetId) {
- if (targetId > 0) {
+ if (targetId != 0) {
mTargetIds.remove((Integer) targetId);
}
return this;
diff --git a/android/support/v17/leanback/app/BaseFragment.java b/android/support/v17/leanback/app/BaseFragment.java
index bdb213f2..ea460111 100644
--- a/android/support/v17/leanback/app/BaseFragment.java
+++ b/android/support/v17/leanback/app/BaseFragment.java
@@ -31,7 +31,9 @@ import android.view.ViewTreeObserver;
/**
* Base class for leanback Fragments. This class is not intended to be subclassed by apps.
+ * @deprecated use {@link BaseSupportFragment}
*/
+@Deprecated
@SuppressWarnings("FragmentNotInstantiable")
public class BaseFragment extends BrandedFragment {
diff --git a/android/support/v17/leanback/app/BaseRowFragment.java b/android/support/v17/leanback/app/BaseRowFragment.java
index 2d79f3e1..97a5b848 100644
--- a/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/android/support/v17/leanback/app/BaseRowFragment.java
@@ -34,7 +34,9 @@ import android.view.ViewGroup;
/**
* An internal base class for a fragment containing a list of rows.
+ * @deprecated use {@link BaseRowSupportFragment}
*/
+@Deprecated
abstract class BaseRowFragment extends Fragment {
private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
private ObjectAdapter mAdapter;
@@ -164,8 +166,10 @@ abstract class BaseRowFragment extends Fragment {
* Set the presenter selector used to create and bind views.
*/
public final void setPresenterSelector(PresenterSelector presenterSelector) {
- mPresenterSelector = presenterSelector;
- updateAdapter();
+ if (mPresenterSelector != presenterSelector) {
+ mPresenterSelector = presenterSelector;
+ updateAdapter();
+ }
}
/**
@@ -180,8 +184,10 @@ abstract class BaseRowFragment extends Fragment {
* @param rowsAdapter Adapter that represents list of rows.
*/
public final void setAdapter(ObjectAdapter rowsAdapter) {
- mAdapter = rowsAdapter;
- updateAdapter();
+ if (mAdapter != rowsAdapter) {
+ mAdapter = rowsAdapter;
+ updateAdapter();
+ }
}
/**
diff --git a/android/support/v17/leanback/app/BaseRowSupportFragment.java b/android/support/v17/leanback/app/BaseRowSupportFragment.java
index dba78daf..6a477ab0 100644
--- a/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -161,8 +161,10 @@ abstract class BaseRowSupportFragment extends Fragment {
* Set the presenter selector used to create and bind views.
*/
public final void setPresenterSelector(PresenterSelector presenterSelector) {
- mPresenterSelector = presenterSelector;
- updateAdapter();
+ if (mPresenterSelector != presenterSelector) {
+ mPresenterSelector = presenterSelector;
+ updateAdapter();
+ }
}
/**
@@ -177,8 +179,10 @@ abstract class BaseRowSupportFragment extends Fragment {
* @param rowsAdapter Adapter that represents list of rows.
*/
public final void setAdapter(ObjectAdapter rowsAdapter) {
- mAdapter = rowsAdapter;
- updateAdapter();
+ if (mAdapter != rowsAdapter) {
+ mAdapter = rowsAdapter;
+ updateAdapter();
+ }
}
/**
diff --git a/android/support/v17/leanback/app/BrandedFragment.java b/android/support/v17/leanback/app/BrandedFragment.java
index 1f6ad299..415c13e0 100644
--- a/android/support/v17/leanback/app/BrandedFragment.java
+++ b/android/support/v17/leanback/app/BrandedFragment.java
@@ -33,7 +33,9 @@ import android.view.ViewGroup;
/**
* Fragment class for managing search and branding using a view that implements
* {@link TitleViewAdapter.Provider}.
+ * @deprecated use {@link BrandedSupportFragment}
*/
+@Deprecated
public class BrandedFragment extends Fragment {
// BUNDLE attribute for title is showing
diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java
index ae31c4fb..c561ea99 100644
--- a/android/support/v17/leanback/app/BrowseFragment.java
+++ b/android/support/v17/leanback/app/BrowseFragment.java
@@ -81,7 +81,9 @@ import java.util.Map;
* The recommended theme to use with a BrowseFragment is
* {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
* </p>
+ * @deprecated use {@link BrowseSupportFragment}
*/
+@Deprecated
public class BrowseFragment extends BaseFragment {
// BUNDLE attribute for saving header show/hide status when backstack is used:
@@ -203,7 +205,9 @@ public class BrowseFragment extends BaseFragment {
/**
* Listener for transitions between browse headers and rows.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class BrowseTransitionListener {
/**
* Callback when headers transition starts.
@@ -267,7 +271,9 @@ public class BrowseFragment extends BaseFragment {
/**
* Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
* fragments can interact with {@link BrowseFragment} using this interface.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public interface FragmentHost {
/**
* Fragments are required to invoke this callback once their view is created
@@ -376,7 +382,9 @@ public class BrowseFragment extends BaseFragment {
* and provide that through {@link MainFragmentAdapterRegistry}.
* {@link MainFragmentAdapter} implementation can supply any fragment and override
* just those interactions that makes sense.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class MainFragmentAdapter<T extends Fragment> {
private boolean mScalingEnabled;
private final T mFragment;
@@ -466,7 +474,9 @@ public class BrowseFragment extends BaseFragment {
* Interface to be implemented by all fragments for providing an instance of
* {@link MainFragmentAdapter}. Both {@link RowsFragment} and custom fragment provided
* against {@link PageRow} will need to implement this interface.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public interface MainFragmentAdapterProvider {
/**
* Returns an instance of {@link MainFragmentAdapter} that {@link BrowseFragment}
@@ -478,7 +488,9 @@ public class BrowseFragment extends BaseFragment {
/**
* Interface to be implemented by {@link RowsFragment} and its subclasses for providing
* an instance of {@link MainFragmentRowsAdapter}.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public interface MainFragmentRowsAdapterProvider {
/**
* Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseFragment}
@@ -491,7 +503,9 @@ public class BrowseFragment extends BaseFragment {
* This is used to pass information to {@link RowsFragment} or its subclasses.
* {@link BrowseFragment} uses this interface to pass row based interaction events to
* the target fragment.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class MainFragmentRowsAdapter<T extends Fragment> {
private final T mFragment;
@@ -570,14 +584,27 @@ public class BrowseFragment extends BaseFragment {
}
boolean oldIsPageRow = mIsPageRow;
+ Object oldPageRow = mPageRow;
mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+ mPageRow = mIsPageRow ? item : null;
boolean swap;
if (mMainFragment == null) {
swap = true;
} else {
if (oldIsPageRow) {
- swap = true;
+ if (mIsPageRow) {
+ if (oldPageRow == null) {
+ // fragment is restored, page row object not yet set, so just set the
+ // mPageRow object and there is no need to replace the fragment
+ swap = false;
+ } else {
+ // swap if page row object changes
+ swap = oldPageRow != mPageRow;
+ }
+ } else {
+ swap = true;
+ }
} else {
swap = mIsPageRow;
}
@@ -590,37 +617,45 @@ public class BrowseFragment extends BaseFragment {
"Fragment must implement MainFragmentAdapterProvider");
}
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- mIsPageRow = mMainFragmentRowsAdapter == null;
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
return swap;
}
+ void setMainFragmentAdapter() {
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+ .getMainFragmentRowsAdapter());
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ mIsPageRow = mMainFragmentRowsAdapter == null;
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ }
+
/**
* Factory class responsible for creating fragment given the current item. {@link ListRow}
* should return {@link RowsFragment} or its subclass whereas {@link PageRow}
* can return any fragment class.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public abstract static class FragmentFactory<T extends Fragment> {
public abstract T createFragment(Object row);
}
/**
* FragmentFactory implementation for {@link ListRow}.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> {
@Override
public RowsFragment createFragment(Object row) {
@@ -634,7 +669,9 @@ public class BrowseFragment extends BaseFragment {
* handling {@link ListRow}. Developers can override that and also if they want to
* use custom fragment, they can register a custom {@link FragmentFactory}
* against {@link PageRow}.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public final static class MainFragmentAdapterRegistry {
private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>();
private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
@@ -678,7 +715,8 @@ public class BrowseFragment extends BaseFragment {
MainFragmentAdapter mMainFragmentAdapter;
Fragment mMainFragment;
HeadersFragment mHeadersFragment;
- private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ ListRowDataAdapter mMainFragmentListRowDataAdapter;
private ObjectAdapter mAdapter;
private PresenterSelector mAdapterPresenter;
@@ -701,6 +739,7 @@ public class BrowseFragment extends BaseFragment {
private int mSelectedPosition = -1;
private float mScaleFactor;
boolean mIsPageRow;
+ Object mPageRow;
private PresenterSelector mHeaderPresenterSelector;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -820,11 +859,45 @@ public class BrowseFragment extends BaseFragment {
return;
}
+ updateMainFragmentRowsAdapter();
+ mHeadersFragment.setAdapter(mAdapter);
+ }
+
+ void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+ if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+ return;
+ }
+ // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
if (mMainFragmentRowsAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(
- adapter == null ? null : new ListRowDataAdapter(adapter));
+ // RowsFragment cannot change click/select listeners after view created.
+ // The main fragment and adapter should be GCed as long as there is no reference from
+ // BrowseFragment to it.
+ mMainFragmentRowsAdapter.setAdapter(null);
+ }
+ mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+ new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+ updateMainFragmentRowsAdapter();
+ }
+
+ /**
+ * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+ * It also clears old mMainFragmentListRowDataAdapter.
+ */
+ void updateMainFragmentRowsAdapter() {
+ if (mMainFragmentListRowDataAdapter != null) {
+ mMainFragmentListRowDataAdapter.detach();
+ mMainFragmentListRowDataAdapter = null;
+ }
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentListRowDataAdapter = mAdapter == null
+ ? null : new ListRowDataAdapter(mAdapter);
+ mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
}
- mHeadersFragment.setAdapter(adapter);
}
public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1144,7 +1217,8 @@ public class BrowseFragment extends BaseFragment {
@Override
public void onDestroyView() {
- mMainFragmentRowsAdapter = null;
+ setMainFragmentRowsAdapter(null);
+ mPageRow = null;
mMainFragmentAdapter = null;
mMainFragment = null;
mHeadersFragment = null;
@@ -1198,26 +1272,17 @@ public class BrowseFragment extends BaseFragment {
mHeadersFragment = (HeadersFragment) getChildFragmentManager()
.findFragmentById(R.id.browse_headers_dock);
mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
mIsPageRow = savedInstanceState != null
&& savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+ // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+ // the case for restoring, later if setSelection() triggers a createMainFragment(),
+ // should not create fragment.
mSelectedPosition = savedInstanceState != null
? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
mHeadersFragment.setHeadersGone(!mCanShowHeaders);
@@ -1242,8 +1307,6 @@ public class BrowseFragment extends BaseFragment {
mScaleFrameLayout.setPivotX(0);
mScaleFrameLayout.setPivotY(mContainerListAlignTop);
- setupMainFragment();
-
if (mBrandColorSet) {
mHeadersFragment.setBackgroundColor(mBrandColor);
}
@@ -1270,17 +1333,6 @@ public class BrowseFragment extends BaseFragment {
return root;
}
- private void setupMainFragment() {
- if (mMainFragmentRowsAdapter != null) {
- if (mAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
- }
- mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
- new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
- mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
- }
- }
-
void createHeadersTransition() {
mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
mShowingHeaders
@@ -1470,10 +1522,10 @@ public class BrowseFragment extends BaseFragment {
};
void onRowSelected(int position) {
- if (position != mSelectedPosition) {
- mSetSelectionRunnable.post(
- position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
- }
+ // even position is same, it could be data changed, always post selection runnable
+ // to possibly swap main fragment.
+ mSetSelectionRunnable.post(
+ position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
}
void setSelection(int position, boolean smooth) {
@@ -1500,7 +1552,6 @@ public class BrowseFragment extends BaseFragment {
if (createMainFragment(mAdapter, position)) {
swapToMainFragment();
expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
- setupMainFragment();
}
}
diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java
index 4a2502a8..c28064ca 100644
--- a/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -567,14 +567,27 @@ public class BrowseSupportFragment extends BaseSupportFragment {
}
boolean oldIsPageRow = mIsPageRow;
+ Object oldPageRow = mPageRow;
mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+ mPageRow = mIsPageRow ? item : null;
boolean swap;
if (mMainFragment == null) {
swap = true;
} else {
if (oldIsPageRow) {
- swap = true;
+ if (mIsPageRow) {
+ if (oldPageRow == null) {
+ // fragment is restored, page row object not yet set, so just set the
+ // mPageRow object and there is no need to replace the fragment
+ swap = false;
+ } else {
+ // swap if page row object changes
+ swap = oldPageRow != mPageRow;
+ }
+ } else {
+ swap = true;
+ }
} else {
swap = mIsPageRow;
}
@@ -587,25 +600,29 @@ public class BrowseSupportFragment extends BaseSupportFragment {
"Fragment must implement MainFragmentAdapterProvider");
}
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- mIsPageRow = mMainFragmentRowsAdapter == null;
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
return swap;
}
+ void setMainFragmentAdapter() {
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+ .getMainFragmentRowsAdapter());
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ mIsPageRow = mMainFragmentRowsAdapter == null;
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ }
+
/**
* Factory class responsible for creating fragment given the current item. {@link ListRow}
* should return {@link RowsSupportFragment} or its subclass whereas {@link PageRow}
@@ -675,7 +692,8 @@ public class BrowseSupportFragment extends BaseSupportFragment {
MainFragmentAdapter mMainFragmentAdapter;
Fragment mMainFragment;
HeadersSupportFragment mHeadersSupportFragment;
- private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ ListRowDataAdapter mMainFragmentListRowDataAdapter;
private ObjectAdapter mAdapter;
private PresenterSelector mAdapterPresenter;
@@ -698,6 +716,7 @@ public class BrowseSupportFragment extends BaseSupportFragment {
private int mSelectedPosition = -1;
private float mScaleFactor;
boolean mIsPageRow;
+ Object mPageRow;
private PresenterSelector mHeaderPresenterSelector;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -817,11 +836,45 @@ public class BrowseSupportFragment extends BaseSupportFragment {
return;
}
+ updateMainFragmentRowsAdapter();
+ mHeadersSupportFragment.setAdapter(mAdapter);
+ }
+
+ void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+ if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+ return;
+ }
+ // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
+ if (mMainFragmentRowsAdapter != null) {
+ // RowsFragment cannot change click/select listeners after view created.
+ // The main fragment and adapter should be GCed as long as there is no reference from
+ // BrowseSupportFragment to it.
+ mMainFragmentRowsAdapter.setAdapter(null);
+ }
+ mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+ new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+ updateMainFragmentRowsAdapter();
+ }
+
+ /**
+ * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+ * It also clears old mMainFragmentListRowDataAdapter.
+ */
+ void updateMainFragmentRowsAdapter() {
+ if (mMainFragmentListRowDataAdapter != null) {
+ mMainFragmentListRowDataAdapter.detach();
+ mMainFragmentListRowDataAdapter = null;
+ }
if (mMainFragmentRowsAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(
- adapter == null ? null : new ListRowDataAdapter(adapter));
+ mMainFragmentListRowDataAdapter = mAdapter == null
+ ? null : new ListRowDataAdapter(mAdapter);
+ mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
}
- mHeadersSupportFragment.setAdapter(adapter);
}
public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1141,7 +1194,8 @@ public class BrowseSupportFragment extends BaseSupportFragment {
@Override
public void onDestroyView() {
- mMainFragmentRowsAdapter = null;
+ setMainFragmentRowsAdapter(null);
+ mPageRow = null;
mMainFragmentAdapter = null;
mMainFragment = null;
mHeadersSupportFragment = null;
@@ -1195,26 +1249,17 @@ public class BrowseSupportFragment extends BaseSupportFragment {
mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
.findFragmentById(R.id.browse_headers_dock);
mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
mIsPageRow = savedInstanceState != null
&& savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+ // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+ // the case for restoring, later if setSelection() triggers a createMainFragment(),
+ // should not create fragment.
mSelectedPosition = savedInstanceState != null
? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
@@ -1239,8 +1284,6 @@ public class BrowseSupportFragment extends BaseSupportFragment {
mScaleFrameLayout.setPivotX(0);
mScaleFrameLayout.setPivotY(mContainerListAlignTop);
- setupMainFragment();
-
if (mBrandColorSet) {
mHeadersSupportFragment.setBackgroundColor(mBrandColor);
}
@@ -1267,17 +1310,6 @@ public class BrowseSupportFragment extends BaseSupportFragment {
return root;
}
- private void setupMainFragment() {
- if (mMainFragmentRowsAdapter != null) {
- if (mAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
- }
- mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
- new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
- mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
- }
- }
-
void createHeadersTransition() {
mHeadersTransition = TransitionHelper.loadTransition(getContext(),
mShowingHeaders
@@ -1467,10 +1499,10 @@ public class BrowseSupportFragment extends BaseSupportFragment {
};
void onRowSelected(int position) {
- if (position != mSelectedPosition) {
- mSetSelectionRunnable.post(
- position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
- }
+ // even position is same, it could be data changed, always post selection runnable
+ // to possibly swap main fragment.
+ mSetSelectionRunnable.post(
+ position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
}
void setSelection(int position, boolean smooth) {
@@ -1497,7 +1529,6 @@ public class BrowseSupportFragment extends BaseSupportFragment {
if (createMainFragment(mAdapter, position)) {
swapToMainFragment();
expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
- setupMainFragment();
}
}
diff --git a/android/support/v17/leanback/app/DetailsFragment.java b/android/support/v17/leanback/app/DetailsFragment.java
index 36559637..18934f45 100644
--- a/android/support/v17/leanback/app/DetailsFragment.java
+++ b/android/support/v17/leanback/app/DetailsFragment.java
@@ -91,7 +91,9 @@ import java.lang.ref.WeakReference;
* DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable
* background and embedded video playing fragment.
* </p>
+ * @deprecated use {@link DetailsSupportFragment}
*/
+@Deprecated
public class DetailsFragment extends BaseFragment {
static final String TAG = "DetailsFragment";
static boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
index 223b8ef2..25ed723e 100644
--- a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
+++ b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
@@ -107,7 +107,9 @@ import android.app.Fragment;
* {@link #onCreateGlueHost()}.
* </p>
*
+ * @deprecated use {@link DetailsSupportFragmentBackgroundController}
*/
+@Deprecated
public class DetailsFragmentBackgroundController {
final DetailsFragment mFragment;
diff --git a/android/support/v17/leanback/app/ErrorFragment.java b/android/support/v17/leanback/app/ErrorFragment.java
index 2896d0f4..eda0de16 100644
--- a/android/support/v17/leanback/app/ErrorFragment.java
+++ b/android/support/v17/leanback/app/ErrorFragment.java
@@ -32,7 +32,9 @@ import android.widget.TextView;
/**
* A fragment for displaying an error indication.
+ * @deprecated use {@link ErrorSupportFragment}
*/
+@Deprecated
public class ErrorFragment extends BrandedFragment {
private ViewGroup mErrorFrame;
diff --git a/android/support/v17/leanback/app/GuidedStepFragment.java b/android/support/v17/leanback/app/GuidedStepFragment.java
index 2b7f2d0d..9be350d8 100644
--- a/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -27,6 +27,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
@@ -140,7 +141,9 @@ import java.util.List;
* @see GuidanceStylist.Guidance
* @see GuidedAction
* @see GuidedActionsStylist
+ * @deprecated use {@link GuidedStepSupportFragment}
*/
+@Deprecated
public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener {
private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
@@ -806,6 +809,8 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.
/**
* Sets the list of GuidedActions that the user may take in this fragment.
+ * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+ *
* @param actions The list of GuidedActions for this fragment.
*/
public void setActions(List<GuidedAction> actions) {
@@ -816,6 +821,18 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.
}
/**
+ * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+ * GuidedStepFragment uses
+ * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+ * Sets it to null if app wants to refresh the whole list.
+ *
+ * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+ */
+ public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+ mAdapter.setDiffCallback(diffCallback);
+ }
+
+ /**
* Notify an action has changed and update its UI.
* @param position Position of the GuidedAction in array.
*/
diff --git a/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index aeb2d334..e276d076 100644
--- a/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -24,6 +24,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
@@ -803,6 +804,8 @@ public class GuidedStepSupportFragment extends Fragment implements GuidedActionA
/**
* Sets the list of GuidedActions that the user may take in this fragment.
+ * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+ *
* @param actions The list of GuidedActions for this fragment.
*/
public void setActions(List<GuidedAction> actions) {
@@ -813,6 +816,18 @@ public class GuidedStepSupportFragment extends Fragment implements GuidedActionA
}
/**
+ * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+ * GuidedStepSupportFragment uses
+ * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+ * Sets it to null if app wants to refresh the whole list.
+ *
+ * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+ */
+ public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+ mAdapter.setDiffCallback(diffCallback);
+ }
+
+ /**
* Notify an action has changed and update its UI.
* @param position Position of the GuidedAction in array.
*/
diff --git a/android/support/v17/leanback/app/HeadersFragment.java b/android/support/v17/leanback/app/HeadersFragment.java
index dd037d2f..08780a50 100644
--- a/android/support/v17/leanback/app/HeadersFragment.java
+++ b/android/support/v17/leanback/app/HeadersFragment.java
@@ -52,12 +52,16 @@ import android.widget.FrameLayout;
* </ul>
* Use {@link #setPresenterSelector(PresenterSelector)} in subclass constructor to customize
* Presenters. App may override {@link BrowseFragment#onCreateHeadersFragment()}.
+ * @deprecated use {@link HeadersSupportFragment}
*/
+@Deprecated
public class HeadersFragment extends BaseRowFragment {
/**
* Interface definition for a callback to be invoked when a header item is clicked.
+ * @deprecated use {@link HeadersSupportFragment}
*/
+ @Deprecated
public interface OnHeaderClickedListener {
/**
* Called when a header item has been clicked.
@@ -70,7 +74,9 @@ public class HeadersFragment extends BaseRowFragment {
/**
* Interface definition for a callback to be invoked when a header item is selected.
+ * @deprecated use {@link HeadersSupportFragment}
*/
+ @Deprecated
public interface OnHeaderViewSelectedListener {
/**
* Called when a header item has been selected.
diff --git a/android/support/v17/leanback/app/ListRowDataAdapter.java b/android/support/v17/leanback/app/ListRowDataAdapter.java
index f9af12f3..03d948be 100644
--- a/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ b/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -13,6 +13,7 @@ import android.support.v17.leanback.widget.Row;
* thinks there are items even though they're invisible. This class takes care of filtering out
* the invisible rows at the end. In case the data inside the adapter changes, it adjusts the
* bounds to reflect the latest data.
+ * {@link #detach()} must be called to release DataObserver from Adapter.
*/
class ListRowDataAdapter extends ObjectAdapter {
public static final int ON_ITEM_RANGE_CHANGED = 2;
@@ -22,6 +23,7 @@ class ListRowDataAdapter extends ObjectAdapter {
private final ObjectAdapter mAdapter;
int mLastVisibleRowIndex;
+ final DataObserver mDataObserver;
public ListRowDataAdapter(ObjectAdapter adapter) {
super(adapter.getPresenterSelector());
@@ -34,10 +36,20 @@ class ListRowDataAdapter extends ObjectAdapter {
// operation. To handle this case, we use QueueBasedDataObserver which forces
// recyclerview to do a full data refresh after each update operation.
if (adapter.isImmediateNotifySupported()) {
- mAdapter.registerObserver(new SimpleDataObserver());
+ mDataObserver = new SimpleDataObserver();
} else {
- mAdapter.registerObserver(new QueueBasedDataObserver());
+ mDataObserver = new QueueBasedDataObserver();
}
+ attach();
+ }
+
+ void detach() {
+ mAdapter.unregisterObserver(mDataObserver);
+ }
+
+ void attach() {
+ initialize();
+ mAdapter.registerObserver(mDataObserver);
}
void initialize() {
diff --git a/android/support/v17/leanback/app/OnboardingFragment.java b/android/support/v17/leanback/app/OnboardingFragment.java
index b69d5a72..f352c413 100644
--- a/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/android/support/v17/leanback/app/OnboardingFragment.java
@@ -154,7 +154,9 @@ import java.util.List;
* @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
* @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
* @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
+ * @deprecated use {@link OnboardingSupportFragment}
*/
+@Deprecated
abstract public class OnboardingFragment extends Fragment {
private static final String TAG = "OnboardingF";
private static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/PlaybackFragment.java b/android/support/v17/leanback/app/PlaybackFragment.java
index 33e787c3..e2e6be48 100644
--- a/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/android/support/v17/leanback/app/PlaybackFragment.java
@@ -81,7 +81,9 @@ import android.view.animation.AccelerateInterpolator;
* {@link #setControlsOverlayAutoHideEnabled(boolean)} upon play/pause. The auto hiding timer will
* be cancelled upon {@link #tickle()} triggered by input event.
* </p>
+ * @deprecated use {@link PlaybackSupportFragment}
*/
+@Deprecated
public class PlaybackFragment extends Fragment {
static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
@@ -181,7 +183,9 @@ public class PlaybackFragment extends Fragment {
* Listener allowing the application to receive notification of fade in and/or fade out
* completion events.
* @hide
+ * @deprecated use {@link PlaybackSupportFragment}
*/
+ @Deprecated
public static class OnFadeCompleteListener {
public void onFadeInComplete() {
}
diff --git a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
index 4a9d10f8..9e342fdb 100644
--- a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -30,7 +30,9 @@ import android.view.View;
/**
* {@link PlaybackGlueHost} implementation
* the interaction between this class and {@link PlaybackFragment}.
+ * @deprecated use {@link PlaybackSupportFragmentGlueHost}
*/
+@Deprecated
public class PlaybackFragmentGlueHost extends PlaybackGlueHost implements PlaybackSeekUi {
private final PlaybackFragment mFragment;
diff --git a/android/support/v17/leanback/app/RowsFragment.java b/android/support/v17/leanback/app/RowsFragment.java
index a008ad60..aa346bd9 100644
--- a/android/support/v17/leanback/app/RowsFragment.java
+++ b/android/support/v17/leanback/app/RowsFragment.java
@@ -53,7 +53,9 @@ import java.util.ArrayList;
* of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
* of {@link RowPresenter}.
* </p>
+ * @deprecated use {@link RowsSupportFragment}
*/
+@Deprecated
public class RowsFragment extends BaseRowFragment implements
BrowseFragment.MainFragmentRowsAdapterProvider,
BrowseFragment.MainFragmentAdapterProvider {
@@ -634,7 +636,9 @@ public class RowsFragment extends BaseRowFragment implements
* The adapter that RowsFragment implements
* BrowseFragment.MainFragmentRowsAdapter.
* @see #getMainFragmentRowsAdapter().
+ * @deprecated use {@link RowsSupportFragment}
*/
+ @Deprecated
public static class MainFragmentRowsAdapter
extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
diff --git a/android/support/v17/leanback/app/SearchFragment.java b/android/support/v17/leanback/app/SearchFragment.java
index 2154ff28..00f2cca8 100644
--- a/android/support/v17/leanback/app/SearchFragment.java
+++ b/android/support/v17/leanback/app/SearchFragment.java
@@ -66,7 +66,9 @@ import java.util.List;
* not when fragment is restored from an instance state. Activity may manually
* call {@link #startRecognition()}, typically in onNewIntent().
* </p>
+ * @deprecated use {@link SearchSupportFragment}
*/
+@Deprecated
public class SearchFragment extends Fragment {
static final String TAG = SearchFragment.class.getSimpleName();
static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/VerticalGridFragment.java b/android/support/v17/leanback/app/VerticalGridFragment.java
index 5bc52ff5..bff3dbab 100644
--- a/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -39,7 +39,9 @@ import android.view.ViewGroup;
*
* <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
* an {@link ObjectAdapter}.
+ * @deprecated use {@link VerticalGridSupportFragment}
*/
+@Deprecated
public class VerticalGridFragment extends BaseFragment {
static final String TAG = "VerticalGF";
static boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/VideoFragment.java b/android/support/v17/leanback/app/VideoFragment.java
index 1b2b8d07..e4d75f30 100644
--- a/android/support/v17/leanback/app/VideoFragment.java
+++ b/android/support/v17/leanback/app/VideoFragment.java
@@ -27,7 +27,9 @@ import android.view.ViewGroup;
/**
* Subclass of {@link PlaybackFragment} that is responsible for providing a {@link SurfaceView}
* and rendering video.
+ * @deprecated use {@link VideoSupportFragment}
*/
+@Deprecated
public class VideoFragment extends PlaybackFragment {
static final int SURFACE_NOT_CREATED = 0;
static final int SURFACE_CREATED = 1;
diff --git a/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
index d123676f..546e581c 100644
--- a/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -24,7 +24,9 @@ import android.view.SurfaceHolder;
/**
* {@link PlaybackGlueHost} implementation
* the interaction between {@link PlaybackGlue} and {@link VideoFragment}.
+ * @deprecated use {@link VideoSupportFragmentGlueHost}
*/
+@Deprecated
public class VideoFragmentGlueHost extends PlaybackFragmentGlueHost
implements SurfaceHolderGlueHost {
private final VideoFragment mFragment;
diff --git a/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 00bc073d..2dcf51f7 100644
--- a/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -225,6 +225,8 @@ public class ArrayObjectAdapter extends ObjectAdapter {
return true;
}
+ ListUpdateCallback mListUpdateCallback;
+
/**
* Set a new item list to adapter. The DiffUtil will compute the difference and dispatch it to
* specified position.
@@ -280,39 +282,43 @@ public class ArrayObjectAdapter extends ObjectAdapter {
mItems.addAll(itemList);
// dispatch diff result
- diffResult.dispatchUpdatesTo(new ListUpdateCallback() {
-
- @Override
- public void onInserted(int position, int count) {
- if (DEBUG) {
- Log.d(TAG, "onInserted");
+ if (mListUpdateCallback == null) {
+ mListUpdateCallback = new ListUpdateCallback() {
+
+ @Override
+ public void onInserted(int position, int count) {
+ if (DEBUG) {
+ Log.d(TAG, "onInserted");
+ }
+ notifyItemRangeInserted(position, count);
}
- notifyItemRangeInserted(position, count);
- }
- @Override
- public void onRemoved(int position, int count) {
- if (DEBUG) {
- Log.d(TAG, "onRemoved");
+ @Override
+ public void onRemoved(int position, int count) {
+ if (DEBUG) {
+ Log.d(TAG, "onRemoved");
+ }
+ notifyItemRangeRemoved(position, count);
}
- notifyItemRangeRemoved(position, count);
- }
- @Override
- public void onMoved(int fromPosition, int toPosition) {
- if (DEBUG) {
- Log.d(TAG, "onMoved");
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ if (DEBUG) {
+ Log.d(TAG, "onMoved");
+ }
+ notifyItemMoved(fromPosition, toPosition);
}
- notifyItemMoved(fromPosition, toPosition);
- }
- @Override
- public void onChanged(int position, int count, Object payload) {
- if (DEBUG) {
- Log.d(TAG, "onChanged");
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ if (DEBUG) {
+ Log.d(TAG, "onChanged");
+ }
+ notifyItemRangeChanged(position, count, payload);
}
- notifyItemRangeChanged(position, count, payload);
- }
- });
+ };
+ }
+ diffResult.dispatchUpdatesTo(mListUpdateCallback);
+ mOldItems.clear();
}
}
diff --git a/android/support/v17/leanback/widget/BaseGridView.java b/android/support/v17/leanback/widget/BaseGridView.java
index f4e01c0b..2ebec47e 100644
--- a/android/support/v17/leanback/widget/BaseGridView.java
+++ b/android/support/v17/leanback/widget/BaseGridView.java
@@ -1134,7 +1134,7 @@ public abstract class BaseGridView extends RecyclerView {
@Override
public void scrollToPosition(int position) {
// dont abort the animateOut() animation, just record the position
- if (mLayoutManager.mIsSlidingChildViews) {
+ if (mLayoutManager.isSlidingChildViews()) {
mLayoutManager.setSelectionWithSub(position, 0, 0);
return;
}
@@ -1144,7 +1144,7 @@ public abstract class BaseGridView extends RecyclerView {
@Override
public void smoothScrollToPosition(int position) {
// dont abort the animateOut() animation, just record the position
- if (mLayoutManager.mIsSlidingChildViews) {
+ if (mLayoutManager.isSlidingChildViews()) {
mLayoutManager.setSelectionWithSub(position, 0, 0);
return;
}
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index dded0715..d7020e91 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -217,9 +217,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = getTargetPosition();
}
if (hasFocus()) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
targetView.requestFocus();
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
}
dispatchChildSelected();
dispatchChildSelectedAndPositioned();
@@ -320,9 +320,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
if (newSelected != null && hasFocus()) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
newSelected.requestFocus();
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
}
}
@@ -355,7 +355,8 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (mPendingMoves == 0) {
return null;
}
- int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0)
+ int direction = ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? mPendingMoves > 0 : mPendingMoves < 0)
? -1 : 1;
if (mOrientation == HORIZONTAL) {
return new PointF(direction, 0);
@@ -386,10 +387,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// effect smooth scrolling too over to bind an item view then drag the item view back.
final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
- // Represents whether child views are temporarily sliding out
- boolean mIsSlidingChildViews;
- boolean mLayoutEatenInSliding;
-
String getTag() {
return TAG + ":" + mBaseGridView.getId();
}
@@ -444,15 +441,101 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private static final Rect sTempRect = new Rect();
- boolean mInLayout;
- private boolean mInScroll;
- boolean mInFastRelayout;
+ // 2 bits mask is for 3 STAGEs: 0, PF_STAGE_LAYOUT or PF_STAGE_SCROLL.
+ static final int PF_STAGE_MASK = 0x3;
+ static final int PF_STAGE_LAYOUT = 0x1;
+ static final int PF_STAGE_SCROLL = 0x2;
+
+ // Flag for "in fast relayout", determined by layoutInit() result.
+ static final int PF_FAST_RELAYOUT = 1 << 2;
+
+ // Flag for the selected item being updated in fast relayout.
+ static final int PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION = 1 << 3;
/**
* During full layout pass, when GridView had focus: onLayoutChildren will
* skip non-focusable child and adjust mFocusPosition.
*/
- boolean mInLayoutSearchFocus;
- boolean mInSelection = false;
+ static final int PF_IN_LAYOUT_SEARCH_FOCUS = 1 << 4;
+
+ // flag to prevent reentry if it's already processing selection request.
+ static final int PF_IN_SELECTION = 1 << 5;
+
+ // Represents whether child views are temporarily sliding out
+ static final int PF_SLIDING = 1 << 6;
+ static final int PF_LAYOUT_EATEN_IN_SLIDING = 1 << 7;
+
+ /**
+ * Force a full layout under certain situations. E.g. Rows change, jump to invisible child.
+ */
+ static final int PF_FORCE_FULL_LAYOUT = 1 << 8;
+
+ /**
+ * True if layout is enabled.
+ */
+ static final int PF_LAYOUT_ENABLED = 1 << 9;
+
+ /**
+ * Flag controlling whether the current/next layout should
+ * be updating the secondary size of rows.
+ */
+ static final int PF_ROW_SECONDARY_SIZE_REFRESH = 1 << 10;
+
+ /**
+ * Allow DPAD key to navigate out at the front of the View (where position = 0),
+ * default is false.
+ */
+ static final int PF_FOCUS_OUT_FRONT = 1 << 11;
+
+ /**
+ * Allow DPAD key to navigate out at the end of the view, default is false.
+ */
+ static final int PF_FOCUS_OUT_END = 1 << 12;
+
+ static final int PF_FOCUS_OUT_MASKS = PF_FOCUS_OUT_FRONT | PF_FOCUS_OUT_END;
+
+ /**
+ * Allow DPAD key to navigate out of second axis.
+ * default is true.
+ */
+ static final int PF_FOCUS_OUT_SIDE_START = 1 << 13;
+
+ /**
+ * Allow DPAD key to navigate out of second axis.
+ */
+ static final int PF_FOCUS_OUT_SIDE_END = 1 << 14;
+
+ static final int PF_FOCUS_OUT_SIDE_MASKS = PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END;
+
+ /**
+ * True if focus search is disabled.
+ */
+ static final int PF_FOCUS_SEARCH_DISABLED = 1 << 15;
+
+ /**
+ * True if prune child, might be disabled during transition.
+ */
+ static final int PF_PRUNE_CHILD = 1 << 16;
+
+ /**
+ * True if scroll content, might be disabled during transition.
+ */
+ static final int PF_SCROLL_ENABLED = 1 << 17;
+
+ /**
+ * Set to true for RTL layout in horizontal orientation
+ */
+ static final int PF_REVERSE_FLOW_PRIMARY = 1 << 18;
+
+ /**
+ * Set to true for RTL layout in vertical orientation
+ */
+ static final int PF_REVERSE_FLOW_SECONDARY = 1 << 19;
+
+ static final int PF_REVERSE_FLOW_MASK = PF_REVERSE_FLOW_PRIMARY | PF_REVERSE_FLOW_SECONDARY;
+
+ int mFlag = PF_LAYOUT_ENABLED
+ | PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END
+ | PF_PRUNE_CHILD | PF_SCROLL_ENABLED;
private OnChildSelectedListener mChildSelectedListener = null;
@@ -493,16 +576,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private int mPrimaryScrollExtra;
/**
- * Force a full layout under certain situations. E.g. Rows change, jump to invisible child.
- */
- private boolean mForceFullLayout;
-
- /**
- * True if layout is enabled.
- */
- private boolean mLayoutEnabled = true;
-
- /**
* override child visibility
*/
@Visibility
@@ -535,12 +608,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private int[] mRowSizeSecondary;
/**
- * Flag controlling whether the current/next layout should
- * be updating the secondary size of rows.
- */
- private boolean mRowSecondarySizeRefresh;
-
- /**
* The maximum measured size of the view.
*/
private int mMaxSizeSecondary;
@@ -605,58 +672,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private int mExtraLayoutSpace;
/**
- * Allow DPAD key to navigate out at the front of the View (where position = 0),
- * default is false.
- */
- private boolean mFocusOutFront;
-
- /**
- * Allow DPAD key to navigate out at the end of the view, default is false.
- */
- private boolean mFocusOutEnd;
-
- /**
- * Allow DPAD key to navigate out of second axis.
- * default is true.
- */
- private boolean mFocusOutSideStart = true;
-
- /**
- * Allow DPAD key to navigate out of second axis.
- */
- private boolean mFocusOutSideEnd = true;
-
- /**
- * True if focus search is disabled.
- */
- private boolean mFocusSearchDisabled;
-
- /**
- * True if prune child, might be disabled during transition.
- */
- private boolean mPruneChild = true;
-
- /**
- * True if scroll content, might be disabled during transition.
- */
- private boolean mScrollEnabled = true;
-
- /**
* Temporary variable: an int array of length=2.
*/
static int[] sTwoInts = new int[2];
/**
- * Set to true for RTL layout in horizontal orientation
- */
- boolean mReverseFlowPrimary = false;
-
- /**
- * Set to true for RTL layout in vertical orientation
- */
- private boolean mReverseFlowSecondary = false;
-
- /**
* Temporaries used for measuring.
*/
private int[] mMeasuredDimension = new int[2];
@@ -685,24 +705,21 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
mWindowAlignment.setOrientation(orientation);
mItemAlignment.setOrientation(orientation);
- mForceFullLayout = true;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
}
public void onRtlPropertiesChanged(int layoutDirection) {
- boolean reversePrimary, reverseSecondary;
+ final int flags;
if (mOrientation == HORIZONTAL) {
- reversePrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
- reverseSecondary = false;
+ flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_PRIMARY : 0;
} else {
- reverseSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
- reversePrimary = false;
+ flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_SECONDARY : 0;
}
- if (mReverseFlowPrimary == reversePrimary && mReverseFlowSecondary == reverseSecondary) {
+ if ((mFlag & PF_REVERSE_FLOW_MASK) == flags) {
return;
}
- mReverseFlowPrimary = reversePrimary;
- mReverseFlowSecondary = reverseSecondary;
- mForceFullLayout = true;
+ mFlag = (mFlag & ~PF_REVERSE_FLOW_MASK) | flags;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
}
@@ -775,13 +792,15 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
- mFocusOutFront = throughFront;
- mFocusOutEnd = throughEnd;
+ mFlag = (mFlag & ~PF_FOCUS_OUT_MASKS)
+ | (throughFront ? PF_FOCUS_OUT_FRONT : 0)
+ | (throughEnd ? PF_FOCUS_OUT_END : 0);
}
public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
- mFocusOutSideStart = throughStart;
- mFocusOutSideEnd = throughEnd;
+ mFlag = (mFlag & ~PF_FOCUS_OUT_SIDE_MASKS)
+ | (throughStart ? PF_FOCUS_OUT_SIDE_START : 0)
+ | (throughEnd ? PF_FOCUS_OUT_SIDE_END : 0);
}
public void setNumRows(int numRows) {
@@ -971,7 +990,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// layout warning.
// If not in layout, we may be scrolling in which case the child layout request will be
// eaten by recyclerview. Post a requestLayout.
- if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && !mBaseGridView.isLayoutRequested()) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (getChildAt(i).isLayoutRequested()) {
@@ -1177,19 +1196,19 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mSubFocusPosition = 0;
}
if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
- && !mForceFullLayout && mGrid.getNumRows() == mNumRows) {
+ && (mFlag & PF_FORCE_FULL_LAYOUT) == 0 && mGrid.getNumRows() == mNumRows) {
updateScrollController();
updateSecondaryScrollLimits();
mGrid.setSpacing(mSpacingPrimary);
return true;
} else {
- mForceFullLayout = false;
+ mFlag &= ~PF_FORCE_FULL_LAYOUT;
if (mGrid == null || mNumRows != mGrid.getNumRows()
- || mReverseFlowPrimary != mGrid.isReversedFlow()) {
+ || ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) != mGrid.isReversedFlow()) {
mGrid = Grid.createGrid(mNumRows);
mGrid.setProvider(mGridProvider);
- mGrid.setReversedFlow(mReverseFlowPrimary);
+ mGrid.setReversedFlow((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0);
}
initScrollController();
updateSecondaryScrollLimits();
@@ -1216,7 +1235,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
int start = 0;
// Iterate from left to right, which is a different index traversal
// in RTL flow
- if (mReverseFlowSecondary) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
for (int i = mNumRows-1; i > rowIndex; i--) {
start += getRowSizeSecondary(i) + mSpacingSecondary;
}
@@ -1229,7 +1248,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private int getSizeSecondary() {
- int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
+ int rightmostIndex = (mFlag & PF_REVERSE_FLOW_SECONDARY) != 0 ? 0 : mNumRows - 1;
return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
}
@@ -1366,8 +1385,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
* Checks if we need to update row secondary sizes.
*/
private void updateRowSecondarySizeRefresh() {
- mRowSecondarySizeRefresh = processRowSizeSecondary(false);
- if (mRowSecondarySizeRefresh) {
+ mFlag = (mFlag & ~PF_ROW_SECONDARY_SIZE_REFRESH)
+ | (processRowSizeSecondary(false) ? PF_ROW_SECONDARY_SIZE_REFRESH : 0);
+ if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
forceRequestLayout();
}
@@ -1599,7 +1619,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
}
int subindex = getSubPositionByView(v, v.findFocus());
- if (!mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
// when we are appending item during scroll pass and the item's position
// matches the mFocusPosition, we should signal a childSelected event.
// However if we are still running PendingMoveSmoothScroller, we defer and
@@ -1610,20 +1630,20 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
&& mPendingMoveSmoothScroller == null) {
dispatchChildSelected();
}
- } else if (!mInFastRelayout) {
+ } else if ((mFlag & PF_FAST_RELAYOUT) == 0) {
// fastRelayout will dispatch event at end of onLayoutChildren().
// For full layout, two situations here:
// 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition.
// 2. mInLayoutSearchFocus is true: dispatchChildSelected() on first child
// equal to or after mFocusPosition that can take focus.
- if (!mInLayoutSearchFocus && index == mFocusPosition
+ if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) == 0 && index == mFocusPosition
&& subindex == mSubFocusPosition) {
dispatchChildSelected();
- } else if (mInLayoutSearchFocus && index >= mFocusPosition
+ } else if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) != 0 && index >= mFocusPosition
&& v.hasFocusable()) {
mFocusPosition = index;
mSubFocusPosition = subindex;
- mInLayoutSearchFocus = false;
+ mFlag &= ~PF_IN_LAYOUT_SEARCH_FOCUS;
dispatchChildSelected();
}
}
@@ -1663,7 +1683,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (!mState.isPreLayout()) {
updateScrollLimits();
}
- if (!mInLayout && mPendingMoveSmoothScroller != null) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && mPendingMoveSmoothScroller != null) {
mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
}
if (mChildLaidOutListener != null) {
@@ -1677,7 +1697,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
public void removeItem(int index) {
if (TRACE) TraceCompat.beginSection("removeItem");
View v = findViewByPosition(index - mPositionDeltaInPreLayout);
- if (mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
detachAndScrapView(v, mRecycler);
} else {
removeAndRecycleView(v, mRecycler);
@@ -1688,7 +1708,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public int getEdge(int index) {
View v = findViewByPosition(index - mPositionDeltaInPreLayout);
- return mReverseFlowPrimary ? getViewMax(v) : getViewMin(v);
+ return (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? getViewMax(v) : getViewMin(v);
}
@Override
@@ -1705,7 +1725,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
}
final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
- final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary)
+ final int horizontalGravity = (mFlag & PF_REVERSE_FLOW_MASK) != 0
? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
View.LAYOUT_DIRECTION_RTL)
: mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
@@ -1781,16 +1801,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private void removeInvisibleViewsAtEnd() {
- if (mPruneChild && !mIsSlidingChildViews) {
- mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
- mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
+ if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+ mGrid.removeInvisibleItemsAtEnd(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
}
}
private void removeInvisibleViewsAtFront() {
- if (mPruneChild && !mIsSlidingChildViews) {
- mGrid.removeInvisibleItemsAtFront(mFocusPosition,
- mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace);
+ if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+ mGrid.removeInvisibleItemsAtFront(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? mSizePrimary + mExtraLayoutSpace : -mExtraLayoutSpace);
}
}
@@ -1799,16 +1819,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
void slideIn() {
- if (mIsSlidingChildViews) {
- mIsSlidingChildViews = false;
+ if ((mFlag & PF_SLIDING) != 0) {
+ mFlag &= ~PF_SLIDING;
if (mFocusPosition >= 0) {
scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
} else {
- mLayoutEatenInSliding = false;
+ mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
requestLayout();
}
- if (mLayoutEatenInSliding) {
- mLayoutEatenInSliding = false;
+ if ((mFlag & PF_LAYOUT_EATEN_IN_SLIDING) != 0) {
+ mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
@@ -1838,7 +1858,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
} else {
- if (mReverseFlowPrimary) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
distance = getWidth();
if (getChildCount() > 0) {
int start = getChildAt(0).getRight();
@@ -1861,14 +1881,18 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
return distance;
}
+ boolean isSlidingChildViews() {
+ return (mFlag & PF_SLIDING) != 0;
+ }
+
/**
* Temporarily slide out child and block layout and scroll requests.
*/
void slideOut() {
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
return;
}
- mIsSlidingChildViews = true;
+ mFlag |= PF_SLIDING;
if (getChildCount() == 0) {
return;
}
@@ -1886,13 +1910,13 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private void appendVisibleItems() {
- mGrid.appendVisibleItems(mReverseFlowPrimary
+ mGrid.appendVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
: mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
}
private void prependVisibleItems() {
- mGrid.prependVisibleItems(mReverseFlowPrimary
+ mGrid.prependVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
: -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
}
@@ -1907,6 +1931,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
final int childCount = getChildCount();
int position = mGrid.getFirstVisibleIndex();
int index = 0;
+ mFlag &= ~PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
for (; index < childCount; index++, position++) {
View view = getChildAt(index);
// We don't hit fastRelayout() if State.didStructure() is true, but prelayout may add
@@ -1932,6 +1957,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (lp.viewNeedsUpdate()) {
+ mFlag |= PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
detachAndScrapView(view, mRecycler);
view = getViewForPosition(position);
addView(view, index);
@@ -1960,7 +1986,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
detachAndScrapView(v, mRecycler);
}
mGrid.invalidateItemsAfter(position);
- if (mPruneChild) {
+ if ((mFlag & PF_PRUNE_CHILD) != 0) {
// in regular prune child mode, we just append items up to edge limit
appendVisibleItems();
if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) {
@@ -2108,7 +2134,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
Log.v(getTag(), "layoutChildren start numRows " + mNumRows
+ " inPreLayout " + state.isPreLayout()
+ " didStructureChange " + state.didStructureChange()
- + " mForceFullLayout " + mForceFullLayout);
+ + " mForceFullLayout " + ((mFlag & PF_FORCE_FULL_LAYOUT) != 0));
Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
}
@@ -2121,20 +2147,20 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
return;
}
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
// if there is already children, delay the layout process until slideIn(), if it's
// first time layout children: scroll them offscreen at end of onLayoutChildren()
if (getChildCount() > 0) {
- mLayoutEatenInSliding = true;
+ mFlag |= PF_LAYOUT_EATEN_IN_SLIDING;
return;
}
}
- if (!mLayoutEnabled) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0) {
discardLayoutInfo();
removeAndRecycleAllViews(recycler);
return;
}
- mInLayout = true;
+ mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_LAYOUT;
saveContext(recycler, state);
if (state.isPreLayout()) {
@@ -2172,7 +2198,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
appendVisibleItems();
prependVisibleItems();
}
- mInLayout = false;
+ mFlag &= ~PF_STAGE_MASK;
leaveContext();
if (DEBUG) Log.v(getTag(), "layoutChildren end");
return;
@@ -2206,13 +2232,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
deltaSecondary = state.getRemainingScrollHorizontal();
deltaPrimary = state.getRemainingScrollVertical();
}
- if (mInFastRelayout = layoutInit()) {
+ if (layoutInit()) {
+ mFlag |= PF_FAST_RELAYOUT;
// If grid view is empty, we will start from mFocusPosition
mGrid.setStart(mFocusPosition);
fastRelayout();
} else {
+ mFlag &= ~PF_FAST_RELAYOUT;
// layoutInit() has detached all views, so start from scratch
- mInLayoutSearchFocus = hadFocus;
+ mFlag = (mFlag & ~PF_IN_LAYOUT_SEARCH_FOCUS)
+ | (hadFocus ? PF_IN_LAYOUT_SEARCH_FOCUS : 0);
int startFromPosition, endPos;
if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
|| mFocusPosition < firstVisibleIndex)) {
@@ -2270,27 +2299,30 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
Log.d(getTag(), sw.toString());
}
- if (mRowSecondarySizeRefresh) {
- mRowSecondarySizeRefresh = false;
+ if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
+ mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
} else {
updateRowSecondarySizeRefresh();
}
- // For fastRelayout, only dispatch event when focus position changes.
- if (mInFastRelayout && (mFocusPosition != savedFocusPos || mSubFocusPosition
- != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) {
+ // For fastRelayout, only dispatch event when focus position changes or selected item
+ // being updated.
+ if ((mFlag & PF_FAST_RELAYOUT) != 0 && (mFocusPosition != savedFocusPos || mSubFocusPosition
+ != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView
+ || (mFlag & PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION) != 0)) {
dispatchChildSelected();
- } else if (!mInFastRelayout && mInLayoutSearchFocus) {
+ } else if ((mFlag & (PF_FAST_RELAYOUT | PF_IN_LAYOUT_SEARCH_FOCUS))
+ == PF_IN_LAYOUT_SEARCH_FOCUS) {
// For full layout we dispatchChildSelected() in createItem() unless searched all
// children and found none is focusable then dispatchChildSelected() here.
dispatchChildSelected();
}
dispatchChildSelectedAndPositioned();
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
scrollDirectionPrimary(getSlideOutDistance());
}
- mInLayout = false;
+ mFlag &= ~PF_STAGE_MASK;
leaveContext();
if (DEBUG) Log.v(getTag(), "layoutChildren end");
}
@@ -2324,11 +2356,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
- if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
return 0;
}
saveContext(recycler, state);
- mInScroll = true;
+ mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
int result;
if (mOrientation == HORIZONTAL) {
result = scrollDirectionPrimary(dx);
@@ -2336,17 +2368,17 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
result = scrollDirectionSecondary(dx);
}
leaveContext();
- mInScroll = false;
+ mFlag &= ~PF_STAGE_MASK;
return result;
}
@Override
public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
- if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
return 0;
}
- mInScroll = true;
+ mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
saveContext(recycler, state);
int result;
if (mOrientation == VERTICAL) {
@@ -2355,7 +2387,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
result = scrollDirectionSecondary(dy);
}
leaveContext();
- mInScroll = false;
+ mFlag &= ~PF_STAGE_MASK;
return result;
}
@@ -2367,7 +2399,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// 2. During onLayoutChildren(), it may compensate the remaining scroll delta,
// we should honor the request regardless if it goes over minScroll / maxScroll.
// (see b/64931938 testScrollAndRemove and testScrollAndRemoveSample1)
- if (!mIsSlidingChildViews && !mInLayout) {
+ if ((mFlag & PF_SLIDING) == 0 && (mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
if (da > 0) {
if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
@@ -2389,7 +2421,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
return 0;
}
offsetChildrenPrimary(-da);
- if (mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
updateScrollLimits();
if (TRACE) TraceCompat.endSection();
return da;
@@ -2398,7 +2430,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
int childCount = getChildCount();
boolean updated;
- if (mReverseFlowPrimary ? da > 0 : da < 0) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
prependVisibleItems();
} else {
appendVisibleItems();
@@ -2407,7 +2439,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
childCount = getChildCount();
if (TRACE) TraceCompat.beginSection("remove");
- if (mReverseFlowPrimary ? da > 0 : da < 0) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
removeInvisibleViewsAtEnd();
} else {
removeInvisibleViewsAtFront();
@@ -2476,7 +2508,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
int highVisiblePos, lowVisiblePos;
int highMaxPos, lowMinPos;
- if (!mReverseFlowPrimary) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) == 0) {
highVisiblePos = mGrid.getLastVisibleIndex();
highMaxPos = mState.getItemCount() - 1;
lowVisiblePos = mGrid.getFirstVisibleIndex();
@@ -2614,14 +2646,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// scrollToView() is based on Adapter position. Only call scrollToView() when item
// is still valid.
if (view != null && getAdapterPositionByView(view) == position) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
scrollToView(view, smooth);
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
} else {
mFocusPosition = position;
mSubFocusPosition = subposition;
mFocusPositionOffset = Integer.MIN_VALUE;
- if (!mLayoutEnabled || mIsSlidingChildViews) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0 || (mFlag & PF_SLIDING) != 0) {
return;
}
if (smooth) {
@@ -2637,7 +2669,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mSubFocusPosition = 0;
}
} else {
- mForceFullLayout = true;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
requestLayout();
}
}
@@ -2654,7 +2686,8 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
final int firstChildPos = getPosition(getChildAt(0));
// TODO We should be able to deduce direction from bounds of current and target
// focus, rather than making assumptions about positions and directionality
- final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos
+ final boolean isStart = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? targetPosition > firstChildPos
: targetPosition < firstChildPos;
final int direction = isStart ? -1 : 1;
if (mOrientation == HORIZONTAL) {
@@ -2788,14 +2821,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
- if (mFocusSearchDisabled) {
+ if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
return true;
}
if (getAdapterPositionByView(child) == NO_POSITION) {
// This is could be the last view in DISAPPEARING animation.
return true;
}
- if (!mInLayout && !mInSelection && !mInScroll) {
+ if ((mFlag & (PF_STAGE_MASK | PF_IN_SELECTION)) == 0) {
scrollToView(child, focused, true);
}
return true;
@@ -2865,7 +2898,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
*/
private void scrollToView(View view, View childView, boolean smooth, int extraDelta,
int extraDeltaSecondary) {
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
return;
}
int newFocusPosition = getAdapterPositionByView(view);
@@ -2874,7 +2907,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = newFocusPosition;
mSubFocusPosition = newSubFocusPosition;
mFocusPositionOffset = 0;
- if (!mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
dispatchChildSelected();
}
if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
@@ -2889,7 +2922,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// by setSelection())
view.requestFocus();
}
- if (!mScrollEnabled && smooth) {
+ if ((mFlag & PF_SCROLL_ENABLED) == 0 && smooth) {
return;
}
if (getScrollPosition(view, childView, sTwoInts)
@@ -3007,7 +3040,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
- if (mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
scrollDirectionPrimary(scrollPrimary);
scrollDirectionSecondary(scrollSecondary);
} else {
@@ -3030,22 +3063,23 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
public void setPruneChild(boolean pruneChild) {
- if (mPruneChild != pruneChild) {
- mPruneChild = pruneChild;
- if (mPruneChild) {
+ if (((mFlag & PF_PRUNE_CHILD) != 0) != pruneChild) {
+ mFlag = (mFlag & ~PF_PRUNE_CHILD) | (pruneChild ? PF_PRUNE_CHILD : 0);
+ if (pruneChild) {
requestLayout();
}
}
}
public boolean getPruneChild() {
- return mPruneChild;
+ return (mFlag & PF_PRUNE_CHILD) != 0;
}
public void setScrollEnabled(boolean scrollEnabled) {
- if (mScrollEnabled != scrollEnabled) {
- mScrollEnabled = scrollEnabled;
- if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
+ if (((mFlag & PF_SCROLL_ENABLED) != 0) != scrollEnabled) {
+ mFlag = (mFlag & ~PF_SCROLL_ENABLED) | (scrollEnabled ? PF_SCROLL_ENABLED : 0);
+ if (((mFlag & PF_SCROLL_ENABLED) != 0)
+ && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
&& mFocusPosition != NO_POSITION) {
scrollToSelection(mFocusPosition, mSubFocusPosition,
true, mPrimaryScrollExtra);
@@ -3054,7 +3088,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
public boolean isScrollEnabled() {
- return mScrollEnabled;
+ return (mFlag & PF_SCROLL_ENABLED) != 0;
}
private int findImmediateChildIndex(View view) {
@@ -3088,16 +3122,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
void setFocusSearchDisabled(boolean disabled) {
- mFocusSearchDisabled = disabled;
+ mFlag = (mFlag & ~PF_FOCUS_SEARCH_DISABLED) | (disabled ? PF_FOCUS_SEARCH_DISABLED : 0);
}
boolean isFocusSearchDisabled() {
- return mFocusSearchDisabled;
+ return (mFlag & PF_FOCUS_SEARCH_DISABLED) != 0;
}
@Override
public View onInterceptFocusSearch(View focused, int direction) {
- if (mFocusSearchDisabled) {
+ if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
return focused;
}
@@ -3132,27 +3166,27 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
int movement = getMovement(direction);
final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
if (movement == NEXT_ITEM) {
- if (isScroll || !mFocusOutEnd) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_END) == 0) {
result = focused;
}
- if (mScrollEnabled && !hasCreatedLastItem()) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedLastItem()) {
processPendingMovement(true);
result = focused;
}
} else if (movement == PREV_ITEM) {
- if (isScroll || !mFocusOutFront) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_FRONT) == 0) {
result = focused;
}
- if (mScrollEnabled && !hasCreatedFirstItem()) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedFirstItem()) {
processPendingMovement(false);
result = focused;
}
} else if (movement == NEXT_ROW) {
- if (isScroll || !mFocusOutSideEnd) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_END) == 0) {
result = focused;
}
} else if (movement == PREV_ROW) {
- if (isScroll || !mFocusOutSideStart) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_START) == 0) {
result = focused;
}
}
@@ -3191,7 +3225,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public boolean onAddFocusables(RecyclerView recyclerView,
ArrayList<View> views, int direction, int focusableMode) {
- if (mFocusSearchDisabled) {
+ if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
return true;
}
// If this viewgroup or one of its children currently has focus then we
@@ -3423,10 +3457,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (mOrientation == HORIZONTAL) {
switch(direction) {
case View.FOCUS_LEFT:
- movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
+ movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? PREV_ITEM : NEXT_ITEM;
break;
case View.FOCUS_RIGHT:
- movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
+ movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? NEXT_ITEM : PREV_ITEM;
break;
case View.FOCUS_UP:
movement = PREV_ROW;
@@ -3438,10 +3472,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
} else if (mOrientation == VERTICAL) {
switch(direction) {
case View.FOCUS_LEFT:
- movement = (!mReverseFlowSecondary) ? PREV_ROW : NEXT_ROW;
+ movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? PREV_ROW : NEXT_ROW;
break;
case View.FOCUS_RIGHT:
- movement = (!mReverseFlowSecondary) ? NEXT_ROW : PREV_ROW;
+ movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? NEXT_ROW : PREV_ROW;
break;
case View.FOCUS_UP:
movement = PREV_ITEM;
@@ -3497,12 +3531,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private void discardLayoutInfo() {
mGrid = null;
mRowSizeSecondary = null;
- mRowSecondarySizeRefresh = false;
+ mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
}
public void setLayoutEnabled(boolean layoutEnabled) {
- if (mLayoutEnabled != layoutEnabled) {
- mLayoutEnabled = layoutEnabled;
+ if (((mFlag & PF_LAYOUT_ENABLED) != 0) != layoutEnabled) {
+ mFlag = (mFlag & ~PF_LAYOUT_ENABLED) | (layoutEnabled ? PF_LAYOUT_ENABLED : 0);
requestLayout();
}
}
@@ -3592,7 +3626,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = loadingState.index;
mFocusPositionOffset = 0;
mChildrenStates.loadFromBundle(loadingState.childStates);
- mForceFullLayout = true;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
requestLayout();
if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
}
@@ -3699,9 +3733,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (newSelected != null) {
if (preventScroll) {
if (hasFocus()) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
newSelected.requestFocus();
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
}
mFocusPosition = focusPosition;
mSubFocusPosition = 0;
@@ -3717,11 +3751,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
AccessibilityNodeInfoCompat info) {
saveContext(recycler, state);
int count = state.getItemCount();
- if (mScrollEnabled && count > 1 && !isItemFullyVisible(0)) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(0)) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
info.setScrollable(true);
}
- if (mScrollEnabled && count > 1 && !isItemFullyVisible(count - 1)) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(count - 1)) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
info.setScrollable(true);
}
diff --git a/android/support/v17/leanback/widget/GuidedActionAdapter.java b/android/support/v17/leanback/widget/GuidedActionAdapter.java
index 5b755f5e..51b29e21 100644
--- a/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -15,7 +15,9 @@ package android.support.v17.leanback.widget;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Log;
@@ -103,6 +105,7 @@ public class GuidedActionAdapter extends RecyclerView.Adapter {
private ClickListener mClickListener;
final GuidedActionsStylist mStylist;
GuidedActionAdapterGroup mGroup;
+ DiffCallback<GuidedAction> mDiffCallback;
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
@@ -145,20 +148,78 @@ public class GuidedActionAdapter extends RecyclerView.Adapter {
mActionOnFocusListener = new ActionOnFocusListener(focusListener);
mActionEditListener = new ActionEditListener();
mIsSubAdapter = isSubAdapter;
+ if (!isSubAdapter) {
+ mDiffCallback = GuidedActionDiffCallback.getInstance();
+ }
+ }
+
+ /**
+ * Change DiffCallback used in {@link #setActions(List)}. Set to null for firing a
+ * general {@link #notifyDataSetChanged()}.
+ *
+ * @param diffCallback
+ */
+ public void setDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+ mDiffCallback = diffCallback;
}
/**
- * Sets the list of actions managed by this adapter.
+ * Sets the list of actions managed by this adapter. Use {@link #setDiffCallback(DiffCallback)}
+ * to change DiffCallback.
* @param actions The list of actions to be managed.
*/
- public void setActions(List<GuidedAction> actions) {
+ public void setActions(final List<GuidedAction> actions) {
if (!mIsSubAdapter) {
mStylist.collapseAction(false);
}
mActionOnFocusListener.unFocus();
- mActions.clear();
- mActions.addAll(actions);
- notifyDataSetChanged();
+ if (mDiffCallback != null) {
+ // temporary variable used for DiffCallback
+ final List<GuidedAction> oldActions = new ArrayList();
+ oldActions.addAll(mActions);
+
+ // update items.
+ mActions.clear();
+ mActions.addAll(actions);
+
+ DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+ @Override
+ public int getOldListSize() {
+ return oldActions.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return mActions.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ return mDiffCallback.areItemsTheSame(oldActions.get(oldItemPosition),
+ mActions.get(newItemPosition));
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ return mDiffCallback.areContentsTheSame(oldActions.get(oldItemPosition),
+ mActions.get(newItemPosition));
+ }
+
+ @Nullable
+ @Override
+ public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+ return mDiffCallback.getChangePayload(oldActions.get(oldItemPosition),
+ mActions.get(newItemPosition));
+ }
+ });
+
+ // dispatch diff result
+ diffResult.dispatchUpdatesTo(this);
+ } else {
+ mActions.clear();
+ mActions.addAll(actions);
+ notifyDataSetChanged();
+ }
}
/**
diff --git a/android/support/v17/leanback/widget/GuidedActionDiffCallback.java b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
new file mode 100644
index 00000000..d4d4d77a
--- /dev/null
+++ b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+/**
+ * DiffCallback used for GuidedActions, see {@link
+ * android.support.v17.leanback.app.GuidedStepSupportFragment#setActionsDiffCallback(DiffCallback)}.
+ */
+public class GuidedActionDiffCallback extends DiffCallback<GuidedAction> {
+
+ static final GuidedActionDiffCallback sInstance = new GuidedActionDiffCallback();
+
+ /**
+ * Returns the singleton GuidedActionDiffCallback.
+ * @return The singleton GuidedActionDiffCallback.
+ */
+ public static final GuidedActionDiffCallback getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public boolean areItemsTheSame(@NonNull GuidedAction oldItem, @NonNull GuidedAction newItem) {
+ if (oldItem == null) {
+ return newItem == null;
+ } else if (newItem == null) {
+ return false;
+ }
+ return oldItem.getId() == newItem.getId();
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull GuidedAction oldItem,
+ @NonNull GuidedAction newItem) {
+ if (oldItem == null) {
+ return newItem == null;
+ } else if (newItem == null) {
+ return false;
+ }
+ return oldItem.getCheckSetId() == newItem.getCheckSetId()
+ && oldItem.mActionFlags == newItem.mActionFlags
+ && TextUtils.equals(oldItem.getTitle(), newItem.getTitle())
+ && TextUtils.equals(oldItem.getDescription(), newItem.getDescription())
+ && oldItem.getInputType() == newItem.getInputType()
+ && TextUtils.equals(oldItem.getEditTitle(), newItem.getEditTitle())
+ && TextUtils.equals(oldItem.getEditDescription(), newItem.getEditDescription())
+ && oldItem.getEditInputType() == newItem.getEditInputType()
+ && oldItem.getDescriptionEditInputType() == newItem.getDescriptionEditInputType();
+ }
+}
diff --git a/android/support/v17/leanback/widget/ObjectAdapter.java b/android/support/v17/leanback/widget/ObjectAdapter.java
index 535f81b4..d411f9e7 100644
--- a/android/support/v17/leanback/widget/ObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ObjectAdapter.java
@@ -13,7 +13,10 @@
*/
package android.support.v17.leanback.widget;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
import android.database.Observable;
+import android.support.annotation.RestrictTo;
/**
* Base class adapter to be used in leanback activities. Provides access to a data model and is
@@ -132,6 +135,10 @@ public abstract class ObjectAdapter {
mObservers.get(i).onItemMoved(positionStart, toPosition);
}
}
+
+ boolean hasObserver() {
+ return mObservers.size() > 0;
+ }
}
private final DataObservable mObservable = new DataObservable();
@@ -207,6 +214,14 @@ public abstract class ObjectAdapter {
}
/**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public final boolean hasObserver() {
+ return mObservable.hasObserver();
+ }
+
+ /**
* Unregisters all DataObservers for this ObjectAdapter.
*/
public final void unregisterAllObservers() {
diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java
index 614ff351..78161a87 100644
--- a/android/support/v4/app/FragmentActivity.java
+++ b/android/support/v4/app/FragmentActivity.java
@@ -536,7 +536,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ markFragmentsCreated();
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
@@ -591,7 +591,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
super.onStop();
mStopped = true;
- markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ markFragmentsCreated();
mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
mFragments.dispatchStop();
@@ -970,18 +970,30 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
}
}
- private static void markState(FragmentManager manager, Lifecycle.State state) {
+ private void markFragmentsCreated() {
+ boolean reiterate;
+ do {
+ reiterate = markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ } while (reiterate);
+ }
+
+ private static boolean markState(FragmentManager manager, Lifecycle.State state) {
+ boolean hadNotMarked = false;
Collection<Fragment> fragments = manager.getFragments();
for (Fragment fragment : fragments) {
if (fragment == null) {
continue;
}
- fragment.mLifecycleRegistry.markState(state);
+ if (fragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
+ fragment.mLifecycleRegistry.markState(state);
+ hadNotMarked = true;
+ }
FragmentManager childFragmentManager = fragment.peekChildFragmentManager();
if (childFragmentManager != null) {
- markState(childFragmentManager, state);
+ hadNotMarked |= markState(childFragmentManager, state);
}
}
+ return hadNotMarked;
}
}
diff --git a/android/support/v4/graphics/TypefaceCompat.java b/android/support/v4/graphics/TypefaceCompat.java
index 3c55df62..734f1837 100644
--- a/android/support/v4/graphics/TypefaceCompat.java
+++ b/android/support/v4/graphics/TypefaceCompat.java
@@ -35,7 +35,6 @@ import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.provider.FontsContractCompat;
import android.support.v4.provider.FontsContractCompat.FontInfo;
import android.support.v4.util.LruCache;
-
/**
* Helper for accessing features in {@link Typeface}.
* @hide
@@ -46,7 +45,9 @@ public class TypefaceCompat {
private static final TypefaceCompatImpl sTypefaceCompatImpl;
static {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ sTypefaceCompatImpl = new TypefaceCompatApi28Impl();
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
sTypefaceCompatImpl = new TypefaceCompatApi26Impl();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& TypefaceCompatApi24Impl.isUsable()) {
diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 1b55a2e0..00e31a1a 100644
--- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -60,76 +60,69 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
"createFromFamiliesWithDefault";
private static final String FREEZE_METHOD = "freeze";
private static final String ABORT_CREATION_METHOD = "abortCreation";
- private static final Class sFontFamily;
- private static final Constructor sFontFamilyCtor;
- private static final Method sAddFontFromAssetManager;
- private static final Method sAddFontFromBuffer;
- private static final Method sFreeze;
- private static final Method sAbortCreation;
- private static final Method sCreateFromFamiliesWithDefault;
private static final int RESOLVE_BY_FONT_TABLE = -1;
- static {
- Class fontFamilyClass;
+ protected final Class mFontFamily;
+ protected final Constructor mFontFamilyCtor;
+ protected final Method mAddFontFromAssetManager;
+ protected final Method mAddFontFromBuffer;
+ protected final Method mFreeze;
+ protected final Method mAbortCreation;
+ protected final Method mCreateFromFamiliesWithDefault;
+
+ public TypefaceCompatApi26Impl() {
+ Class fontFamily;
Constructor fontFamilyCtor;
- Method addFontMethod;
- Method addFromBufferMethod;
- Method freezeMethod;
- Method abortCreationMethod;
- Method createFromFamiliesWithDefaultMethod;
+ Method addFontFromAssetManager;
+ Method addFontFromBuffer;
+ Method freeze;
+ Method abortCreation;
+ Method createFromFamiliesWithDefault;
try {
- fontFamilyClass = Class.forName(FONT_FAMILY_CLASS);
- fontFamilyCtor = fontFamilyClass.getConstructor();
- addFontMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
- AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
- Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
- addFromBufferMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
- ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
- Integer.TYPE);
- freezeMethod = fontFamilyClass.getMethod(FREEZE_METHOD);
- abortCreationMethod = fontFamilyClass.getMethod(ABORT_CREATION_METHOD);
- Object familyArray = Array.newInstance(fontFamilyClass, 1);
- createFromFamiliesWithDefaultMethod =
- Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
- familyArray.getClass(), Integer.TYPE, Integer.TYPE);
- createFromFamiliesWithDefaultMethod.setAccessible(true);
+ fontFamily = obtainFontFamily();
+ fontFamilyCtor = obtainFontFamilyCtor(fontFamily);
+ addFontFromAssetManager = obtainAddFontFromAssetManagerMethod(fontFamily);
+ addFontFromBuffer = obtainAddFontFromBufferMethod(fontFamily);
+ freeze = obtainFreezeMethod(fontFamily);
+ abortCreation = obtainAbortCreationMethod(fontFamily);
+ createFromFamiliesWithDefault = obtainCreateFromFamiliesWithDefaultMethod(fontFamily);
} catch (ClassNotFoundException | NoSuchMethodException e) {
Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(),
e);
- fontFamilyClass = null;
+ fontFamily = null;
fontFamilyCtor = null;
- addFontMethod = null;
- addFromBufferMethod = null;
- freezeMethod = null;
- abortCreationMethod = null;
- createFromFamiliesWithDefaultMethod = null;
+ addFontFromAssetManager = null;
+ addFontFromBuffer = null;
+ freeze = null;
+ abortCreation = null;
+ createFromFamiliesWithDefault = null;
}
- sFontFamilyCtor = fontFamilyCtor;
- sFontFamily = fontFamilyClass;
- sAddFontFromAssetManager = addFontMethod;
- sAddFontFromBuffer = addFromBufferMethod;
- sFreeze = freezeMethod;
- sAbortCreation = abortCreationMethod;
- sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod;
+ mFontFamily = fontFamily;
+ mFontFamilyCtor = fontFamilyCtor;
+ mAddFontFromAssetManager = addFontFromAssetManager;
+ mAddFontFromBuffer = addFontFromBuffer;
+ mFreeze = freeze;
+ mAbortCreation = abortCreation;
+ mCreateFromFamiliesWithDefault = createFromFamiliesWithDefault;
}
/**
- * Returns true if API26 implementation is usable.
+ * Returns true if all the necessary methods were found.
*/
- private static boolean isFontFamilyPrivateAPIAvailable() {
- if (sAddFontFromAssetManager == null) {
+ private boolean isFontFamilyPrivateAPIAvailable() {
+ if (mAddFontFromAssetManager == null) {
Log.w(TAG, "Unable to collect necessary private methods. "
+ "Fallback to legacy implementation.");
}
- return sAddFontFromAssetManager != null;
+ return mAddFontFromAssetManager != null;
}
/**
* Create a new FontFamily instance
*/
- private static Object newFamily() {
+ private Object newFamily() {
try {
- return sFontFamilyCtor.newInstance();
+ return mFontFamilyCtor.newInstance();
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
@@ -139,10 +132,10 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
* Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie,
* boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes)
*/
- private static boolean addFontFromAssetManager(Context context, Object family, String fileName,
+ private boolean addFontFromAssetManager(Context context, Object family, String fileName,
int ttcIndex, int weight, int style) {
try {
- final Boolean result = (Boolean) sAddFontFromAssetManager.invoke(family,
+ final Boolean result = (Boolean) mAddFontFromAssetManager.invoke(family,
context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex,
weight, style, null /* axes */);
return result.booleanValue();
@@ -155,10 +148,10 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
* Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
* int weight, int italic)
*/
- private static boolean addFontFromBuffer(Object family, ByteBuffer buffer,
+ private boolean addFontFromBuffer(Object family, ByteBuffer buffer,
int ttcIndex, int weight, int style) {
try {
- final Boolean result = (Boolean) sAddFontFromBuffer.invoke(family,
+ final Boolean result = (Boolean) mAddFontFromBuffer.invoke(family,
buffer, ttcIndex, null /* axes */, weight, style);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
@@ -167,14 +160,14 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
}
/**
- * Call static method Typeface#createFromFamiliesWithDefault(
+ * Call method Typeface#createFromFamiliesWithDefault(
* FontFamily[] families, int weight, int italic)
*/
- private static Typeface createFromFamiliesWithDefault(Object family) {
+ protected Typeface createFromFamiliesWithDefault(Object family) {
try {
- Object familyArray = Array.newInstance(sFontFamily, 1);
+ Object familyArray = Array.newInstance(mFontFamily, 1);
Array.set(familyArray, 0, family);
- return (Typeface) sCreateFromFamiliesWithDefault.invoke(null /* static method */,
+ return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
@@ -184,9 +177,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
/**
* Call FontFamily#freeze()
*/
- private static boolean freeze(Object family) {
+ private boolean freeze(Object family) {
try {
- Boolean result = (Boolean) sFreeze.invoke(family);
+ Boolean result = (Boolean) mFreeze.invoke(family);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
@@ -196,9 +189,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
/**
* Call FontFamily#abortCreation()
*/
- private static boolean abortCreation(Object family) {
+ private boolean abortCreation(Object family) {
try {
- Boolean result = (Boolean) sAbortCreation.invoke(family);
+ Boolean result = (Boolean) mAbortCreation.invoke(family);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
@@ -299,4 +292,47 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
}
return createFromFamiliesWithDefault(fontFamily);
}
+
+ // The following getters retrieve by reflection the Typeface methods, belonging to the
+ // framework code, which will be invoked. Since the definitions of these methods can change
+ // across different API versions, inheriting classes should override these getters in order to
+ // reflect the method definitions in the API versions they represent.
+ //===========================================================================================
+ protected Class obtainFontFamily() throws ClassNotFoundException {
+ return Class.forName(FONT_FAMILY_CLASS);
+ }
+
+ protected Constructor obtainFontFamilyCtor(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getConstructor();
+ }
+
+ protected Method obtainAddFontFromAssetManagerMethod(Class fontFamily)
+ throws NoSuchMethodException {
+ return fontFamily.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
+ AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
+ Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
+ }
+
+ protected Method obtainAddFontFromBufferMethod(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
+ ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
+ Integer.TYPE);
+ }
+
+ protected Method obtainFreezeMethod(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getMethod(FREEZE_METHOD);
+ }
+
+ protected Method obtainAbortCreationMethod(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getMethod(ABORT_CREATION_METHOD);
+ }
+
+ protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+ throws NoSuchMethodException {
+ Object familyArray = Array.newInstance(fontFamily, 1);
+ Method m = Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+ familyArray.getClass(), Integer.TYPE, Integer.TYPE);
+ m.setAccessible(true);
+ return m;
+ }
}
diff --git a/android/support/v4/graphics/TypefaceCompatApi28Impl.java b/android/support/v4/graphics/TypefaceCompatApi28Impl.java
new file mode 100644
index 00000000..baa2ce67
--- /dev/null
+++ b/android/support/v4/graphics/TypefaceCompatApi28Impl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Typeface;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Implementation of the Typeface compat methods for API 28 and above.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@RequiresApi(28)
+public class TypefaceCompatApi28Impl extends TypefaceCompatApi26Impl {
+ private static final String TAG = "TypefaceCompatApi28Impl";
+
+ private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD =
+ "createFromFamiliesWithDefault";
+ private static final int RESOLVE_BY_FONT_TABLE = -1;
+ private static final String DEFAULT_FAMILY = "sans-serif";
+
+ /**
+ * Call method Typeface#createFromFamiliesWithDefault(
+ * FontFamily[] families, String fallbackName, int weight, int italic)
+ */
+ @Override
+ protected Typeface createFromFamiliesWithDefault(Object family) {
+ try {
+ Object familyArray = Array.newInstance(mFontFamily, 1);
+ Array.set(familyArray, 0, family);
+ return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
+ familyArray, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+ throws NoSuchMethodException {
+ Object familyArray = Array.newInstance(fontFamily, 1);
+ Method m = Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+ familyArray.getClass(), String.class, Integer.TYPE, Integer.TYPE);
+ m.setAccessible(true);
+ return m;
+ }
+}
diff --git a/android/support/v4/media/MediaBrowserCompat.java b/android/support/v4/media/MediaBrowserCompat.java
index 85f5a512..7adf7d78 100644
--- a/android/support/v4/media/MediaBrowserCompat.java
+++ b/android/support/v4/media/MediaBrowserCompat.java
@@ -676,17 +676,15 @@ public final class MediaBrowserCompat {
WeakReference<Subscription> mSubscriptionRef;
public SubscriptionCallback() {
+ mToken = new Binder();
if (Build.VERSION.SDK_INT >= 26) {
mSubscriptionCallbackObj =
MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26());
- mToken = null;
} else if (Build.VERSION.SDK_INT >= 21) {
mSubscriptionCallbackObj =
MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
- mToken = new Binder();
} else {
mSubscriptionCallbackObj = null;
- mToken = new Binder();
}
}
@@ -1958,22 +1956,30 @@ public final class MediaBrowserCompat {
@Override
public void subscribe(@NonNull String parentId, @Nullable Bundle options,
@NonNull SubscriptionCallback callback) {
- if (options == null) {
- MediaBrowserCompatApi21.subscribe(
- mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ if (mServiceBinderWrapper == null) {
+ if (options == null) {
+ MediaBrowserCompatApi21.subscribe(
+ mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ } else {
+ MediaBrowserCompatApi26.subscribe(
+ mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ }
} else {
- MediaBrowserCompatApi26.subscribe(
- mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ super.subscribe(parentId, options, callback);
}
}
@Override
public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
- if (callback == null) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ if (mServiceBinderWrapper == null) {
+ if (callback == null) {
+ MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ } else {
+ MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
+ callback.mSubscriptionCallbackObj);
+ }
} else {
- MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
- callback.mSubscriptionCallbackObj);
+ super.unsubscribe(parentId, callback);
}
}
}
diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java
index 53b111ab..debc66e8 100644
--- a/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -422,11 +422,15 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override
public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (options == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ if (mMessenger == null) {
+ if (options == null) {
+ MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ } else {
+ MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
+ options);
+ }
} else {
- MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
- options);
+ super.notifyChildrenChanged(parentId, options);
}
}
diff --git a/android/support/v4/media/MediaMetadataCompat.java b/android/support/v4/media/MediaMetadataCompat.java
index 3ddf255c..00f16cb3 100644
--- a/android/support/v4/media/MediaMetadataCompat.java
+++ b/android/support/v4/media/MediaMetadataCompat.java
@@ -365,10 +365,12 @@ public final class MediaMetadataCompat implements Parcelable {
MediaMetadataCompat(Bundle bundle) {
mBundle = new Bundle(bundle);
+ mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
}
MediaMetadataCompat(Parcel in) {
mBundle = in.readBundle();
+ mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
}
/**
diff --git a/android/support/v4/view/ViewCompat.java b/android/support/v4/view/ViewCompat.java
index 34a198a1..204a1218 100644
--- a/android/support/v4/view/ViewCompat.java
+++ b/android/support/v4/view/ViewCompat.java
@@ -1356,7 +1356,7 @@ public class ViewCompat {
// after applying the tint
Drawable background = view.getBackground();
boolean hasTint = (view.getBackgroundTintList() != null)
- && (view.getBackgroundTintMode() != null);
+ || (view.getBackgroundTintMode() != null);
if ((background != null) && hasTint) {
if (background.isStateful()) {
background.setState(view.getDrawableState());
@@ -1375,7 +1375,7 @@ public class ViewCompat {
// after applying the tint
Drawable background = view.getBackground();
boolean hasTint = (view.getBackgroundTintList() != null)
- && (view.getBackgroundTintMode() != null);
+ || (view.getBackgroundTintMode() != null);
if ((background != null) && hasTint) {
if (background.isStateful()) {
background.setState(view.getDrawableState());
diff --git a/android/support/v7/app/AppCompatDelegateImplV9.java b/android/support/v7/app/AppCompatDelegateImplV9.java
index 056e33e3..5b53401c 100644
--- a/android/support/v7/app/AppCompatDelegateImplV9.java
+++ b/android/support/v7/app/AppCompatDelegateImplV9.java
@@ -1001,7 +1001,26 @@ class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
- mAppCompatViewInflater = new AppCompatViewInflater();
+ TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+ String viewInflaterClassName =
+ a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
+ if ((viewInflaterClassName == null)
+ || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
+ // Either default class name or set explicitly to null. In both cases
+ // create the base inflater (no reflection)
+ mAppCompatViewInflater = new AppCompatViewInflater();
+ } else {
+ try {
+ Class viewInflaterClass = Class.forName(viewInflaterClassName);
+ mAppCompatViewInflater =
+ (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
+ .newInstance();
+ } catch (Throwable t) {
+ Log.i(TAG, "Failed to instantiate custom view inflater "
+ + viewInflaterClassName + ". Falling back to default.", t);
+ mAppCompatViewInflater = new AppCompatViewInflater();
+ }
+ }
}
boolean inheritContext = false;
diff --git a/android/support/v7/app/AppCompatViewInflater.java b/android/support/v7/app/AppCompatViewInflater.java
index 54d01bce..87a1a3c7 100644
--- a/android/support/v7/app/AppCompatViewInflater.java
+++ b/android/support/v7/app/AppCompatViewInflater.java
@@ -51,14 +51,12 @@ import java.lang.reflect.Method;
import java.util.Map;
/**
- * This class is responsible for manually inflating our tinted widgets which are used on devices
- * running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class
- * should only be used when running on those devices.
+ * This class is responsible for manually inflating our tinted widgets.
* <p>This class two main responsibilities: the first is to 'inject' our tinted views in place of
* the framework versions in layout inflation; the second is backport the {@code android:theme}
* functionality for any inflated widgets. This include theme inheritance from its parent.
*/
-class AppCompatViewInflater {
+public class AppCompatViewInflater {
private static final Class<?>[] sConstructorSignature = new Class[]{
Context.class, AttributeSet.class};
@@ -77,7 +75,7 @@ class AppCompatViewInflater {
private final Object[] mConstructorArgs = new Object[2];
- public final View createView(View parent, final String name, @NonNull Context context,
+ final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
@@ -100,44 +98,63 @@ class AppCompatViewInflater {
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
- view = new AppCompatTextView(context, attrs);
+ view = createTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "ImageView":
- view = new AppCompatImageView(context, attrs);
+ view = createImageView(context, attrs);
+ verifyNotNull(view, name);
break;
case "Button":
- view = new AppCompatButton(context, attrs);
+ view = createButton(context, attrs);
+ verifyNotNull(view, name);
break;
case "EditText":
- view = new AppCompatEditText(context, attrs);
+ view = createEditText(context, attrs);
+ verifyNotNull(view, name);
break;
case "Spinner":
- view = new AppCompatSpinner(context, attrs);
+ view = createSpinner(context, attrs);
+ verifyNotNull(view, name);
break;
case "ImageButton":
- view = new AppCompatImageButton(context, attrs);
+ view = createImageButton(context, attrs);
+ verifyNotNull(view, name);
break;
case "CheckBox":
- view = new AppCompatCheckBox(context, attrs);
+ view = createCheckBox(context, attrs);
+ verifyNotNull(view, name);
break;
case "RadioButton":
- view = new AppCompatRadioButton(context, attrs);
+ view = createRadioButton(context, attrs);
+ verifyNotNull(view, name);
break;
case "CheckedTextView":
- view = new AppCompatCheckedTextView(context, attrs);
+ view = createCheckedTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
- view = new AppCompatAutoCompleteTextView(context, attrs);
+ view = createAutoCompleteTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
- view = new AppCompatMultiAutoCompleteTextView(context, attrs);
+ view = createMultiAutoCompleteTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "RatingBar":
- view = new AppCompatRatingBar(context, attrs);
+ view = createRatingBar(context, attrs);
+ verifyNotNull(view, name);
break;
case "SeekBar":
- view = new AppCompatSeekBar(context, attrs);
+ view = createSeekBar(context, attrs);
+ verifyNotNull(view, name);
break;
+ default:
+ // The fallback that allows extending class to take over view inflation
+ // for other tags. Note that we don't check that the result is not-null.
+ // That allows the custom inflater path to fall back on the default one
+ // later in this method.
+ view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
@@ -154,6 +171,85 @@ class AppCompatViewInflater {
return view;
}
+ @NonNull
+ protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
+ return new AppCompatTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
+ return new AppCompatImageView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatButton createButton(Context context, AttributeSet attrs) {
+ return new AppCompatButton(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatEditText createEditText(Context context, AttributeSet attrs) {
+ return new AppCompatEditText(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatSpinner createSpinner(Context context, AttributeSet attrs) {
+ return new AppCompatSpinner(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) {
+ return new AppCompatImageButton(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatCheckBox createCheckBox(Context context, AttributeSet attrs) {
+ return new AppCompatCheckBox(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatRadioButton createRadioButton(Context context, AttributeSet attrs) {
+ return new AppCompatRadioButton(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatCheckedTextView createCheckedTextView(Context context, AttributeSet attrs) {
+ return new AppCompatCheckedTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatAutoCompleteTextView createAutoCompleteTextView(Context context,
+ AttributeSet attrs) {
+ return new AppCompatAutoCompleteTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(Context context,
+ AttributeSet attrs) {
+ return new AppCompatMultiAutoCompleteTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatRatingBar createRatingBar(Context context, AttributeSet attrs) {
+ return new AppCompatRatingBar(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatSeekBar createSeekBar(Context context, AttributeSet attrs) {
+ return new AppCompatSeekBar(context, attrs);
+ }
+
+ private void verifyNotNull(View view, String name) {
+ if (view == null) {
+ throw new IllegalStateException(this.getClass().getName()
+ + " asked to inflate view for <" + name + ">, but returned null");
+ }
+ }
+
+ @Nullable
+ protected View createView(Context context, String name, AttributeSet attrs) {
+ return null;
+ }
+
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
@@ -165,14 +261,14 @@ class AppCompatViewInflater {
if (-1 == name.indexOf('.')) {
for (int i = 0; i < sClassPrefixList.length; i++) {
- final View view = createView(context, name, sClassPrefixList[i]);
+ final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
if (view != null) {
return view;
}
}
return null;
} else {
- return createView(context, name, null);
+ return createViewByPrefix(context, name, null);
}
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
@@ -209,7 +305,7 @@ class AppCompatViewInflater {
a.recycle();
}
- private View createView(Context context, String name, String prefix)
+ private View createViewByPrefix(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
diff --git a/android/support/v7/util/SortedList.java b/android/support/v7/util/SortedList.java
index c62d0ce8..af000a1e 100644
--- a/android/support/v7/util/SortedList.java
+++ b/android/support/v7/util/SortedList.java
@@ -16,6 +16,8 @@
package android.support.v7.util;
+import android.support.annotation.Nullable;
+
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
@@ -315,7 +317,8 @@ public class SortedList<T> {
newDataStart++;
mOldDataStart++;
if (!mCallback.areContentsTheSame(oldItem, newItem)) {
- mCallback.onChanged(mMergedSize - 1, 1);
+ mCallback.onChanged(mMergedSize - 1, 1,
+ mCallback.getChangePayload(oldItem, newItem));
}
} else {
// Old item is lower than or equal to (but not the same as the new). Output it.
@@ -401,7 +404,7 @@ public class SortedList<T> {
return index;
} else {
mData[index] = item;
- mCallback.onChanged(index, 1);
+ mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
return index;
}
}
@@ -488,13 +491,13 @@ public class SortedList<T> {
if (cmp == 0) {
mData[index] = item;
if (contentsChanged) {
- mCallback.onChanged(index, 1);
+ mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
}
return;
}
}
if (contentsChanged) {
- mCallback.onChanged(index, 1);
+ mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
}
// TODO this done in 1 pass to avoid shifting twice.
removeItemAtIndex(index, false);
@@ -741,6 +744,28 @@ public class SortedList<T> {
* @return True if the two items represent the same object or false if they are different.
*/
abstract public boolean areItemsTheSame(T2 item1, T2 item2);
+
+ /**
+ * When {@link #areItemsTheSame(T2, T2)} returns {@code true} for two items and
+ * {@link #areContentsTheSame(T2, T2)} returns false for them, {@link Callback} calls this
+ * method to get a payload about the change.
+ * <p>
+ * For example, if you are using {@link Callback} with
+ * {@link android.support.v7.widget.RecyclerView}, you can return the particular field that
+ * changed in the item and your
+ * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+ * information to run the correct animation.
+ * <p>
+ * Default implementation returns {@code null}.
+ *
+ * @param item1 The first item to check.
+ * @param item2 The second item to check.
+ * @return A payload object that represents the changes between the two items.
+ */
+ @Nullable
+ public Object getChangePayload(T2 item1, T2 item2) {
+ return null;
+ }
}
/**
@@ -801,6 +826,11 @@ public class SortedList<T> {
}
@Override
+ public void onChanged(int position, int count, Object payload) {
+ mBatchingListUpdateCallback.onChanged(position, count, payload);
+ }
+
+ @Override
public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
return mWrappedCallback.areContentsTheSame(oldItem, newItem);
}
@@ -810,6 +840,12 @@ public class SortedList<T> {
return mWrappedCallback.areItemsTheSame(item1, item2);
}
+ @Nullable
+ @Override
+ public Object getChangePayload(T2 item1, T2 item2) {
+ return mWrappedCallback.getChangePayload(item1, item2);
+ }
+
/**
* This method dispatches any pending event notifications to the wrapped Callback.
* You <b>must</b> always call this method after you are done with editing the SortedList.
diff --git a/android/support/v7/util/SortedListBatchedCallbackTest.java b/android/support/v7/util/SortedListBatchedCallbackTest.java
index 3ace2178..bc50415d 100644
--- a/android/support/v7/util/SortedListBatchedCallbackTest.java
+++ b/android/support/v7/util/SortedListBatchedCallbackTest.java
@@ -50,6 +50,16 @@ public class SortedListBatchedCallbackTest {
}
@Test
+ public void onChangeWithPayload() {
+ final Object payload = 7;
+ mBatchedCallback.onChanged(1, 2, payload);
+ verifyZeroInteractions(mMockCallback);
+ mBatchedCallback.dispatchLastEvent();
+ verify(mMockCallback).onChanged(1, 2, payload);
+ verifyNoMoreInteractions(mMockCallback);
+ }
+
+ @Test
public void onRemoved() {
mBatchedCallback.onRemoved(2, 3);
verifyZeroInteractions(mMockCallback);
diff --git a/android/support/v7/util/SortedListTest.java b/android/support/v7/util/SortedListTest.java
index da3c9572..47d2ac0f 100644
--- a/android/support/v7/util/SortedListTest.java
+++ b/android/support/v7/util/SortedListTest.java
@@ -16,6 +16,7 @@
package android.support.v7.util;
+import android.support.annotation.Nullable;
import android.support.test.filters.SmallTest;
import junit.framework.TestCase;
@@ -41,6 +42,8 @@ public class SortedListTest extends TestCase {
List<Pair> mRemovals = new ArrayList<Pair>();
List<Pair> mMoves = new ArrayList<Pair>();
List<Pair> mUpdates = new ArrayList<Pair>();
+ private boolean mPayloadChanges = false;
+ List<PayloadChange> mPayloadUpdates = new ArrayList<>();
private SortedList.Callback<Item> mCallback;
InsertedCallback<Item> mInsertedCallback;
ChangedCallback<Item> mChangedCallback;
@@ -97,6 +100,15 @@ public class SortedListTest extends TestCase {
}
@Override
+ public void onChanged(int position, int count, Object payload) {
+ if (mPayloadChanges) {
+ mPayloadUpdates.add(new PayloadChange(position, count, payload));
+ } else {
+ onChanged(position, count);
+ }
+ }
+
+ @Override
public boolean areContentsTheSame(Item oldItem, Item newItem) {
return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data;
}
@@ -105,6 +117,15 @@ public class SortedListTest extends TestCase {
public boolean areItemsTheSame(Item item1, Item item2) {
return item1.id == item2.id;
}
+
+ @Nullable
+ @Override
+ public Object getChangePayload(Item item1, Item item2) {
+ if (mPayloadChanges) {
+ return item2.data;
+ }
+ return null;
+ }
};
mInsertedCallback = null;
mChangedCallback = null;
@@ -705,6 +726,76 @@ public class SortedListTest extends TestCase {
assertTrue(mAdditions.contains(new Pair(0, 6)));
}
+ @Test
+ public void testAddExistingItemCallsChangeWithPayload() {
+ mList.addAll(
+ new Item(1, 10),
+ new Item(2, 20),
+ new Item(3, 30)
+ );
+ mPayloadChanges = true;
+
+ // add an item with the same id but a new data field i.e. send an update
+ final Item twoUpdate = new Item(2, 20);
+ twoUpdate.data = 1337;
+ mList.add(twoUpdate);
+ assertEquals(1, mPayloadUpdates.size());
+ final PayloadChange update = mPayloadUpdates.get(0);
+ assertEquals(1, update.position);
+ assertEquals(1, update.count);
+ assertEquals(1337, update.payload);
+ assertEquals(3, size());
+ }
+
+ @Test
+ public void testUpdateItemCallsChangeWithPayload() {
+ mList.addAll(
+ new Item(1, 10),
+ new Item(2, 20),
+ new Item(3, 30)
+ );
+ mPayloadChanges = true;
+
+ // add an item with the same id but a new data field i.e. send an update
+ final Item twoUpdate = new Item(2, 20);
+ twoUpdate.data = 1337;
+ mList.updateItemAt(1, twoUpdate);
+ assertEquals(1, mPayloadUpdates.size());
+ final PayloadChange update = mPayloadUpdates.get(0);
+ assertEquals(1, update.position);
+ assertEquals(1, update.count);
+ assertEquals(1337, update.payload);
+ assertEquals(3, size());
+ assertEquals(1337, mList.get(1).data);
+ }
+
+ @Test
+ public void testAddMultipleExistingItemCallsChangeWithPayload() {
+ mList.addAll(
+ new Item(1, 10),
+ new Item(2, 20),
+ new Item(3, 30)
+ );
+ mPayloadChanges = true;
+
+ // add two items with the same ids but a new data fields i.e. send two updates
+ final Item twoUpdate = new Item(2, 20);
+ twoUpdate.data = 222;
+ final Item threeUpdate = new Item(3, 30);
+ threeUpdate.data = 333;
+ mList.addAll(twoUpdate, threeUpdate);
+ assertEquals(2, mPayloadUpdates.size());
+ final PayloadChange update1 = mPayloadUpdates.get(0);
+ assertEquals(1, update1.position);
+ assertEquals(1, update1.count);
+ assertEquals(222, update1.payload);
+ final PayloadChange update2 = mPayloadUpdates.get(1);
+ assertEquals(2, update2.position);
+ assertEquals(1, update2.count);
+ assertEquals(333, update2.payload);
+ assertEquals(3, size());
+ }
+
private int size() {
return mList.size();
}
@@ -821,4 +912,37 @@ public class SortedListTest extends TestCase {
return result;
}
}
+
+ private static final class PayloadChange {
+ public final int position;
+ public final int count;
+ public final Object payload;
+
+ PayloadChange(int position, int count, Object payload) {
+ this.position = position;
+ this.count = count;
+ this.payload = payload;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PayloadChange payloadChange = (PayloadChange) o;
+
+ if (position != payloadChange.position) return false;
+ if (count != payloadChange.count) return false;
+ return payload != null ? payload.equals(payloadChange.payload)
+ : payloadChange.payload == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = position;
+ result = 31 * result + count;
+ result = 31 * result + (payload != null ? payload.hashCode() : 0);
+ return result;
+ }
+ }
} \ No newline at end of file
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index fa6196f5..b8ce82a4 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -213,9 +213,9 @@ class AppCompatTextHelper {
if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
|| a.hasValue(R.styleable.TextAppearance_fontFamily)) {
mFontTypeface = null;
- int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
- ? R.styleable.TextAppearance_android_fontFamily
- : R.styleable.TextAppearance_fontFamily;
+ int fontFamilyId = a.hasValue(R.styleable.TextAppearance_fontFamily)
+ ? R.styleable.TextAppearance_fontFamily
+ : R.styleable.TextAppearance_android_fontFamily;
if (!context.isRestricted()) {
final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
diff --git a/android/support/v7/widget/TooltipCompatHandler.java b/android/support/v7/widget/TooltipCompatHandler.java
index 5ce1f8b3..63a61982 100644
--- a/android/support/v7/widget/TooltipCompatHandler.java
+++ b/android/support/v7/widget/TooltipCompatHandler.java
@@ -66,6 +66,10 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
private TooltipPopup mPopup;
private boolean mFromTouch;
+ // The handler currently scheduled to show a tooltip, triggered by a hover
+ // (there can be only one).
+ private static TooltipCompatHandler sPendingHandler;
+
// The handler currently showing a tooltip (there can be only one).
private static TooltipCompatHandler sActiveHandler;
@@ -76,6 +80,15 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
* @param tooltipText the tooltip text
*/
public static void setTooltipText(View view, CharSequence tooltipText) {
+ // The code below is not attempting to update the tooltip text
+ // for a pending or currently active tooltip, because it may lead
+ // to updating the wrong tooltip in in some rare cases (e.g. when
+ // action menu item views are recycled). Instead, the tooltip is
+ // canceled/hidden. This might still be the wrong tooltip,
+ // but hiding a wrong tooltip is less disruptive UX.
+ if (sPendingHandler != null && sPendingHandler.mAnchor == view) {
+ setPendingHandler(null);
+ }
if (TextUtils.isEmpty(tooltipText)) {
if (sActiveHandler != null && sActiveHandler.mAnchor == view) {
sActiveHandler.hide();
@@ -119,8 +132,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
if (mAnchor.isEnabled() && mPopup == null) {
mAnchorX = (int) event.getX();
mAnchorY = (int) event.getY();
- mAnchor.removeCallbacks(mShowRunnable);
- mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+ setPendingHandler(this);
}
break;
case MotionEvent.ACTION_HOVER_EXIT:
@@ -145,6 +157,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
if (!ViewCompat.isAttachedToWindow(mAnchor)) {
return;
}
+ setPendingHandler(null);
if (sActiveHandler != null) {
sActiveHandler.hide();
}
@@ -180,7 +193,27 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
Log.e(TAG, "sActiveHandler.mPopup == null");
}
}
- mAnchor.removeCallbacks(mShowRunnable);
+ if (sPendingHandler == this) {
+ setPendingHandler(null);
+ }
mAnchor.removeCallbacks(mHideRunnable);
}
+
+ private static void setPendingHandler(TooltipCompatHandler handler) {
+ if (sPendingHandler != null) {
+ sPendingHandler.cancelPendingShow();
+ }
+ sPendingHandler = handler;
+ if (sPendingHandler != null) {
+ sPendingHandler.scheduleShow();
+ }
+ }
+
+ private void scheduleShow() {
+ mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+ }
+
+ private void cancelPendingShow() {
+ mAnchor.removeCallbacks(mShowRunnable);
+ }
}
diff --git a/android/support/v7/widget/util/SortedListAdapterCallback.java b/android/support/v7/widget/util/SortedListAdapterCallback.java
index 4921541b..a1203a69 100644
--- a/android/support/v7/widget/util/SortedListAdapterCallback.java
+++ b/android/support/v7/widget/util/SortedListAdapterCallback.java
@@ -56,4 +56,9 @@ public abstract class SortedListAdapterCallback<T2> extends SortedList.Callback<
public void onChanged(int position, int count) {
mAdapter.notifyItemRangeChanged(position, count);
}
+
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ mAdapter.notifyItemRangeChanged(position, count, payload);
+ }
}
diff --git a/android/support/wear/ambient/AmbientDelegate.java b/android/support/wear/ambient/AmbientDelegate.java
index 49012908..8e96a020 100644
--- a/android/support/wear/ambient/AmbientDelegate.java
+++ b/android/support/wear/ambient/AmbientDelegate.java
@@ -19,14 +19,12 @@ import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.util.Log;
import com.google.android.wearable.compat.WearableActivityController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.lang.reflect.Method;
/**
* Provides compatibility for ambient mode.
@@ -146,19 +144,6 @@ final class AmbientDelegate {
}
/**
- * Sets whether this activity's task should be moved to the front when the system exits ambient
- * mode. If true, the activity's task may be moved to the front if it was the last activity to
- * be running when ambient started, depending on how much time the system spent in ambient mode.
- */
- void setAutoResumeEnabled(boolean enabled) {
- if (mWearableController != null) {
- if (hasSetAutoResumeEnabledMethod()) {
- mWearableController.setAutoResumeEnabled(enabled);
- }
- }
- }
-
- /**
* @return {@code true} if the activity is currently in ambient.
*/
boolean isAmbient() {
@@ -177,31 +162,4 @@ final class AmbientDelegate {
mWearableController.dump(prefix, fd, writer, args);
}
}
-
- private boolean hasSetAutoResumeEnabledMethod() {
- if (!sInitAutoResumeEnabledMethod) {
- sInitAutoResumeEnabledMethod = true;
- try {
- Method method =
- WearableActivityController.class
- .getDeclaredMethod("setAutoResumeEnabled", boolean.class);
- // Proguard is sneaky -- it will actually rewrite strings it finds in addition to
- // function names. Therefore add a "." prefix to the method name check to ensure the
- // function was not renamed by proguard.
- if (!(".setAutoResumeEnabled".equals("." + method.getName()))) {
- throw new NoSuchMethodException();
- }
- sHasAutoResumeEnabledMethod = true;
- } catch (NoSuchMethodException e) {
- Log.w(
- "WearableActivity",
- "Could not find a required method for auto-resume "
- + "support, likely due to proguard optimization. Please add "
- + "com.google.android.wearable:wearable jar to the list of library "
- + "jars for your project");
- sHasAutoResumeEnabledMethod = false;
- }
- }
- return sHasAutoResumeEnabledMethod;
- }
}
diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java
index db53dfc1..5db93830 100644
--- a/android/support/wear/ambient/AmbientMode.java
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -38,13 +38,13 @@ import java.io.PrintWriter;
* It should be called with an {@link Activity} as an argument and that {@link Activity} will then
* be able to receive ambient lifecycle events through an {@link AmbientCallback}. The
* {@link Activity} will also receive a {@link AmbientController} object from the attachment which
- * can be used to query the current status of the ambient mode, or toggle simple settings.
+ * can be used to query the current status of the ambient mode.
* An example of how to attach {@link AmbientMode} to your {@link Activity} and use
* the {@link AmbientController} can be found below:
* <p>
* <pre class="prettyprint">{@code
* AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
- * controller.setAutoResumeEnabled(true);
+ * boolean isAmbient = controller.isAmbient();
* }</pre>
*/
public final class AmbientMode extends Fragment {
@@ -117,7 +117,7 @@ public final class AmbientMode extends Fragment {
* Called when the system is updating the display for ambient mode. Activities may use this
* opportunity to update or invalidate views.
*/
- public void onUpdateAmbient() {};
+ public void onUpdateAmbient() {}
/**
* Called when an activity should exit ambient mode. This event is sent while an activity is
@@ -126,7 +126,7 @@ public final class AmbientMode extends Fragment {
* <p><em>Derived classes must call through to the super class's implementation of this
* method. If they do not, an exception will be thrown.</em>
*/
- public void onExitAmbient() {};
+ public void onExitAmbient() {}
}
private final AmbientDelegate.AmbientCallback mCallback =
@@ -220,7 +220,7 @@ public final class AmbientMode extends Fragment {
* @param activity the activity to attach ambient support to. This activity has to also
* implement {@link AmbientCallbackProvider}
* @return the associated {@link AmbientController} which can be used to query the state of
- * ambient mode and toggle simple settings related to it.
+ * ambient mode.
*/
public static <T extends Activity & AmbientCallbackProvider> AmbientController
attachAmbientSupport(T activity) {
@@ -251,9 +251,8 @@ public final class AmbientMode extends Fragment {
/**
* A class for interacting with the ambient mode on a wearable device. This class can be used to
- * query the current state of ambient mode and to enable or disable certain settings.
- * An instance of this class is returned to the user when they attach their {@link Activity}
- * to {@link AmbientMode}.
+ * query the current state of ambient mode. An instance of this class is returned to the user
+ * when they attach their {@link Activity} to {@link AmbientMode}.
*/
public final class AmbientController {
private static final String TAG = "AmbientController";
diff --git a/android/support/wear/ambient/SharedLibraryVersion.java b/android/support/wear/ambient/SharedLibraryVersion.java
index cd90a3b7..9421d9ee 100644
--- a/android/support/wear/ambient/SharedLibraryVersion.java
+++ b/android/support/wear/ambient/SharedLibraryVersion.java
@@ -16,7 +16,6 @@
package android.support.wear.ambient;
import android.os.Build;
-import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import com.google.android.wearable.WearableSharedLib;
@@ -24,10 +23,7 @@ import com.google.android.wearable.WearableSharedLib;
/**
* Internal class which can be used to determine the version of the wearable shared library that is
* available on the current device.
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
final class SharedLibraryVersion {
private SharedLibraryVersion() {
diff --git a/android/support/wear/ambient/WearableControllerProvider.java b/android/support/wear/ambient/WearableControllerProvider.java
index 1682dc0e..4b6ae8ee 100644
--- a/android/support/wear/ambient/WearableControllerProvider.java
+++ b/android/support/wear/ambient/WearableControllerProvider.java
@@ -28,7 +28,7 @@ import java.lang.reflect.Method;
*
* @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
public class WearableControllerProvider {
private static final String TAG = "WearableControllerProvider";
diff --git a/android/support/wear/internal/widget/ResourcesUtil.java b/android/support/wear/internal/widget/ResourcesUtil.java
index f23a6889..8ba3adf0 100644
--- a/android/support/wear/internal/widget/ResourcesUtil.java
+++ b/android/support/wear/internal/widget/ResourcesUtil.java
@@ -26,7 +26,7 @@ import android.support.annotation.RestrictTo.Scope;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public final class ResourcesUtil {
/**
diff --git a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
index ad56048b..4a7ce667 100644
--- a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
+++ b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
@@ -28,7 +28,7 @@ import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableN
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class MultiPagePresenter extends WearableNavigationDrawerPresenter {
private final Ui mUi;
diff --git a/android/support/wear/internal/widget/drawer/MultiPageUi.java b/android/support/wear/internal/widget/drawer/MultiPageUi.java
index 90568451..0ba2f5d1 100644
--- a/android/support/wear/internal/widget/drawer/MultiPageUi.java
+++ b/android/support/wear/internal/widget/drawer/MultiPageUi.java
@@ -16,6 +16,7 @@
package android.support.wear.internal.widget.drawer;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
@@ -37,7 +38,7 @@ import android.widget.TextView;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class MultiPageUi implements MultiPagePresenter.Ui {
private static final String TAG = "MultiPageUi";
@@ -62,13 +63,8 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
final View content = inflater.inflate(R.layout.ws_navigation_drawer_view, drawer,
false /* attachToRoot */);
- mNavigationPager =
- (ViewPager) content
- .findViewById(R.id.ws_navigation_drawer_view_pager);
- mPageIndicatorView =
- (PageIndicatorView)
- content.findViewById(
- R.id.ws_navigation_drawer_page_indicator);
+ mNavigationPager = content.findViewById(R.id.ws_navigation_drawer_view_pager);
+ mPageIndicatorView = content.findViewById(R.id.ws_navigation_drawer_page_indicator);
drawer.setDrawerContent(content);
}
@@ -132,8 +128,9 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
mAdapter = adapter;
}
+ @NonNull
@Override
- public Object instantiateItem(ViewGroup container, int position) {
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
// Do not attach to root in the inflate method. The view needs to returned at the end
// of this method. Attaching to root will cause view to point to container instead.
final View view =
@@ -141,17 +138,17 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
.inflate(R.layout.ws_navigation_drawer_item_view, container, false);
container.addView(view);
final ImageView iconView =
- (ImageView) view
- .findViewById(R.id.ws_navigation_drawer_item_icon);
+ view.findViewById(R.id.ws_navigation_drawer_item_icon);
final TextView textView =
- (TextView) view.findViewById(R.id.ws_navigation_drawer_item_text);
+ view.findViewById(R.id.ws_navigation_drawer_item_text);
iconView.setImageDrawable(mAdapter.getItemDrawable(position));
textView.setText(mAdapter.getItemText(position));
return view;
}
@Override
- public void destroyItem(ViewGroup container, int position, Object object) {
+ public void destroyItem(@NonNull ViewGroup container, int position,
+ @NonNull Object object) {
container.removeView((View) object);
}
@@ -161,12 +158,12 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
}
@Override
- public int getItemPosition(Object object) {
+ public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
@Override
- public boolean isViewFromObject(View view, Object object) {
+ public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
}
diff --git a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
index d90b5891..42cc7d06 100644
--- a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
+++ b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
@@ -29,7 +29,7 @@ import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableN
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class SinglePagePresenter extends WearableNavigationDrawerPresenter {
private static final long DRAWER_CLOSE_DELAY_MS = 500;
diff --git a/android/support/wear/internal/widget/drawer/SinglePageUi.java b/android/support/wear/internal/widget/drawer/SinglePageUi.java
index f3a42904..ffc966f0 100644
--- a/android/support/wear/internal/widget/drawer/SinglePageUi.java
+++ b/android/support/wear/internal/widget/drawer/SinglePageUi.java
@@ -38,7 +38,7 @@ import android.widget.Toast;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class SinglePageUi implements SinglePagePresenter.Ui {
@IdRes
@@ -111,11 +111,10 @@ public class SinglePageUi implements SinglePagePresenter.Ui {
R.layout.ws_single_page_nav_drawer_peek_view, mDrawer,
false /* attachToRoot */);
- mTextView = (TextView) content.findViewById(R.id.ws_nav_drawer_text);
+ mTextView = content.findViewById(R.id.ws_nav_drawer_text);
mSinglePageImageViews = new CircledImageView[count];
for (int i = 0; i < count; i++) {
- mSinglePageImageViews[i] = (CircledImageView) content
- .findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
+ mSinglePageImageViews[i] = content.findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
mSinglePageImageViews[i].setOnClickListener(new OnSelectedClickHandler(i, mPresenter));
mSinglePageImageViews[i].setCircleHidden(true);
}
diff --git a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
index 1c8c4fb7..df108aac 100644
--- a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
+++ b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
@@ -30,7 +30,7 @@ import java.util.Set;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public abstract class WearableNavigationDrawerPresenter {
private final Set<OnItemSelectedListener> mOnItemSelectedListeners = new HashSet<>();
diff --git a/android/support/wear/utils/MetadataConstants.java b/android/support/wear/utils/MetadataConstants.java
index 5be9c523..c7335c2d 100644
--- a/android/support/wear/utils/MetadataConstants.java
+++ b/android/support/wear/utils/MetadataConstants.java
@@ -15,16 +15,13 @@
*/
package android.support.wear.utils;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.os.Build;
/**
* Constants for android wear apps which are related to manifest meta-data.
*/
-@TargetApi(Build.VERSION_CODES.N)
public class MetadataConstants {
// Constants for standalone apps. //
diff --git a/android/support/wear/widget/BezierSCurveInterpolator.java b/android/support/wear/widget/BezierSCurveInterpolator.java
index 131bae84..9c56a83d 100644
--- a/android/support/wear/widget/BezierSCurveInterpolator.java
+++ b/android/support/wear/widget/BezierSCurveInterpolator.java
@@ -17,8 +17,6 @@
package android.support.wear.widget;
import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.os.Build;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
@@ -27,8 +25,7 @@ import android.support.annotation.RestrictTo.Scope;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
+@RestrictTo(Scope.LIBRARY)
class BezierSCurveInterpolator implements TimeInterpolator {
/**
diff --git a/android/support/wear/widget/BoxInsetLayout.java b/android/support/wear/widget/BoxInsetLayout.java
index ba35f2c9..a8b13814 100644
--- a/android/support/wear/widget/BoxInsetLayout.java
+++ b/android/support/wear/widget/BoxInsetLayout.java
@@ -20,7 +20,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -111,21 +110,6 @@ public class BoxInsetLayout extends ViewGroup {
}
@Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- insets = super.onApplyWindowInsets(insets);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- final boolean round = insets.isRound();
- if (round != mIsRound) {
- mIsRound = round;
- requestLayout();
- }
- mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
- insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
- }
- return insets;
- }
-
- @Override
public void setForeground(Drawable drawable) {
super.setForeground(drawable);
mForegroundDrawable = drawable;
@@ -145,14 +129,10 @@ public class BoxInsetLayout extends ViewGroup {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- requestApplyInsets();
- } else {
- mIsRound = getResources().getConfiguration().isScreenRound();
- WindowInsets insets = getRootWindowInsets();
- mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
- insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
- }
+ mIsRound = getResources().getConfiguration().isScreenRound();
+ WindowInsets insets = getRootWindowInsets();
+ mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
@Override
@@ -413,7 +393,7 @@ public class BoxInsetLayout extends ViewGroup {
public static class LayoutParams extends FrameLayout.LayoutParams {
/** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({BOX_NONE, BOX_LEFT, BOX_TOP, BOX_RIGHT, BOX_BOTTOM, BOX_ALL})
@Retention(RetentionPolicy.SOURCE)
public @interface BoxedEdges {}
diff --git a/android/support/wear/widget/CircledImageView.java b/android/support/wear/widget/CircledImageView.java
index 03ed8c98..c441dd57 100644
--- a/android/support/wear/widget/CircledImageView.java
+++ b/android/support/wear/widget/CircledImageView.java
@@ -19,7 +19,6 @@ package android.support.wear.widget;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -32,7 +31,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.Px;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
@@ -47,8 +45,7 @@ import java.util.Objects;
*
* @hide
*/
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class CircledImageView extends View {
private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
@@ -133,13 +130,9 @@ public class CircledImageView extends View {
if (mDrawable != null && mDrawable.getConstantState() != null) {
// The provided Drawable may be used elsewhere, so make a mutable clone before setTint()
// or setAlpha() is called on it.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- mDrawable =
- mDrawable.getConstantState()
- .newDrawable(context.getResources(), context.getTheme());
- } else {
- mDrawable = mDrawable.getConstantState().newDrawable(context.getResources());
- }
+ mDrawable =
+ mDrawable.getConstantState()
+ .newDrawable(context.getResources(), context.getTheme());
mDrawable = mDrawable.mutate();
}
diff --git a/android/support/wear/widget/CurvingLayoutCallback.java b/android/support/wear/widget/CurvingLayoutCallback.java
index 275f1f8b..5e88a8c4 100644
--- a/android/support/wear/widget/CurvingLayoutCallback.java
+++ b/android/support/wear/widget/CurvingLayoutCallback.java
@@ -113,7 +113,7 @@ public class CurvingLayoutCallback extends WearableLinearLayoutManager.LayoutCal
*/
public void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY) {
return;
- };
+ }
@VisibleForTesting
void setRound(boolean isScreenRound) {
diff --git a/android/support/wear/widget/ProgressDrawable.java b/android/support/wear/widget/ProgressDrawable.java
index 08e8ec2e..28e05708 100644
--- a/android/support/wear/widget/ProgressDrawable.java
+++ b/android/support/wear/widget/ProgressDrawable.java
@@ -19,14 +19,12 @@ package android.support.wear.widget;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
import android.util.Property;
@@ -37,8 +35,7 @@ import android.view.animation.LinearInterpolator;
*
* @hide
*/
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class ProgressDrawable extends Drawable {
private static final Property<ProgressDrawable, Integer> LEVEL =
diff --git a/android/support/wear/widget/RoundedDrawable.java b/android/support/wear/widget/RoundedDrawable.java
index fd09a878..300b6dd6 100644
--- a/android/support/wear/widget/RoundedDrawable.java
+++ b/android/support/wear/widget/RoundedDrawable.java
@@ -15,7 +15,6 @@
*/
package android.support.wear.widget;
-import android.annotation.TargetApi;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -29,7 +28,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -76,7 +74,6 @@ import java.util.Objects;
* app:radius="dimension"
* app:clipEnabled="boolean" /&gt;</pre>
*/
-@TargetApi(Build.VERSION_CODES.N)
public class RoundedDrawable extends Drawable {
@VisibleForTesting
diff --git a/android/support/wear/widget/ScrollManager.java b/android/support/wear/widget/ScrollManager.java
index 8155f62d..e01a2710 100644
--- a/android/support/wear/widget/ScrollManager.java
+++ b/android/support/wear/widget/ScrollManager.java
@@ -16,11 +16,8 @@
package android.support.wear.widget;
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.annotation.TargetApi;
-import android.os.Build;
import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -30,8 +27,7 @@ import android.view.VelocityTracker;
*
* @hide
*/
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class ScrollManager {
// One second in milliseconds.
private static final int ONE_SEC_IN_MS = 1000;
diff --git a/android/support/wear/widget/SimpleAnimatorListener.java b/android/support/wear/widget/SimpleAnimatorListener.java
index a60b0bd2..3a1e56b9 100644
--- a/android/support/wear/widget/SimpleAnimatorListener.java
+++ b/android/support/wear/widget/SimpleAnimatorListener.java
@@ -29,7 +29,7 @@ import android.support.annotation.RestrictTo.Scope;
* @hide Hidden until this goes through review
*/
@RequiresApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class SimpleAnimatorListener implements Animator.AnimatorListener {
private boolean mWasCanceled;
diff --git a/android/support/wear/widget/SwipeDismissLayout.java b/android/support/wear/widget/SwipeDismissLayout.java
index 6e7a6f36..33da79c2 100644
--- a/android/support/wear/widget/SwipeDismissLayout.java
+++ b/android/support/wear/widget/SwipeDismissLayout.java
@@ -16,12 +16,11 @@
package android.support.wear.widget;
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
import android.support.annotation.UiThread;
import android.util.AttributeSet;
import android.util.Log;
@@ -40,7 +39,7 @@ import android.widget.FrameLayout;
*
* @hide
*/
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
@UiThread
class SwipeDismissLayout extends FrameLayout {
private static final String TAG = "SwipeDismissLayout";
diff --git a/android/support/wear/widget/WearableRecyclerView.java b/android/support/wear/widget/WearableRecyclerView.java
index 5cacdfcb..1425e68b 100644
--- a/android/support/wear/widget/WearableRecyclerView.java
+++ b/android/support/wear/widget/WearableRecyclerView.java
@@ -16,11 +16,9 @@
package android.support.wear.widget;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Point;
-import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.wear.R;
@@ -35,7 +33,6 @@ import android.view.ViewTreeObserver;
*
* @see #setCircularScrollingGestureEnabled(boolean)
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableRecyclerView extends RecyclerView {
private static final String TAG = "WearableRecyclerView";
diff --git a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
index f1cb640d..e9b2a40a 100644
--- a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
@@ -32,7 +32,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class AbsListViewFlingWatcher implements FlingWatcher, OnScrollListener {
private final FlingListener mListener;
diff --git a/android/support/wear/widget/drawer/FlingWatcherFactory.java b/android/support/wear/widget/drawer/FlingWatcherFactory.java
index 3fe84c63..2fdfa13c 100644
--- a/android/support/wear/widget/drawer/FlingWatcherFactory.java
+++ b/android/support/wear/widget/drawer/FlingWatcherFactory.java
@@ -33,7 +33,7 @@ import java.util.WeakHashMap;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class FlingWatcherFactory {
/**
diff --git a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
index ca95ab22..4c0e5c8c 100644
--- a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class NestedScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
static final int MAX_WAIT_TIME_MS = 100;
diff --git a/android/support/wear/widget/drawer/PageIndicatorView.java b/android/support/wear/widget/drawer/PageIndicatorView.java
index 99c7c09f..1285f725 100644
--- a/android/support/wear/widget/drawer/PageIndicatorView.java
+++ b/android/support/wear/widget/drawer/PageIndicatorView.java
@@ -54,7 +54,7 @@ import java.util.concurrent.TimeUnit;
* @hide
*/
@RequiresApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class PageIndicatorView extends View implements OnPageChangeListener {
private static final String TAG = "Dots";
diff --git a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
index 7570fae5..7916875e 100644
--- a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
@@ -31,7 +31,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class RecyclerViewFlingWatcher extends OnScrollListener implements FlingWatcher {
private final FlingListener mListener;
diff --git a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
index f0b973be..5154e7bb 100644
--- a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class ScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
static final int MAX_WAIT_TIME_MS = 100;
diff --git a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
index 158467dc..092ac72d 100644
--- a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
+++ b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
@@ -16,12 +16,10 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.Nullable;
import android.view.ActionProvider;
import android.view.ContextMenu;
@@ -34,7 +32,6 @@ import android.view.View;
import java.util.ArrayList;
import java.util.List;
-@TargetApi(Build.VERSION_CODES.M)
/* package */ class WearableActionDrawerMenu implements Menu {
private final Context mContext;
diff --git a/android/support/wear/widget/drawer/WearableActionDrawerView.java b/android/support/wear/widget/drawer/WearableActionDrawerView.java
index 8154e6b9..99cd4ff6 100644
--- a/android/support/wear/widget/drawer/WearableActionDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableActionDrawerView.java
@@ -16,12 +16,10 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -38,6 +36,7 @@ import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -75,7 +74,6 @@ import java.util.Objects;
* <p>For {@link MenuItem}, setting and getting the title and icon, {@link MenuItem#getItemId}, and
* {@link MenuItem#setOnMenuItemClickListener} are implemented.
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableActionDrawerView extends WearableDrawerView {
private static final String TAG = "WearableActionDrawer";
@@ -140,12 +138,8 @@ public class WearableActionDrawerView extends WearableDrawerView {
View peekView = layoutInflater.inflate(R.layout.ws_action_drawer_peek_view,
getPeekContainer(), false /* attachToRoot */);
setPeekContent(peekView);
- mPeekActionIcon =
- (ImageView) peekView
- .findViewById(R.id.ws_action_drawer_peek_action_icon);
- mPeekExpandIcon =
- (ImageView) peekView
- .findViewById(R.id.ws_action_drawer_expand_icon);
+ mPeekActionIcon = peekView.findViewById(R.id.ws_action_drawer_peek_action_icon);
+ mPeekExpandIcon = peekView.findViewById(R.id.ws_action_drawer_expand_icon);
} else {
mPeekActionIcon = null;
mPeekExpandIcon = null;
@@ -193,6 +187,16 @@ public class WearableActionDrawerView extends WearableDrawerView {
}
@Override
+ public void onDrawerOpened() {
+ if (mActionListAdapter.getItemCount() > 0) {
+ RecyclerView.ViewHolder holder = mActionList.findViewHolderForAdapterPosition(0);
+ if (holder != null && holder.itemView != null) {
+ holder.itemView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ }
+ }
+
+ @Override
public boolean canScrollHorizontally(int direction) {
// Prevent the window from being swiped closed while it is open by saying that it can scroll
// horizontally.
@@ -412,7 +416,6 @@ public class WearableActionDrawerView extends WearableDrawerView {
CharSequence title = mActionMenu.getItem(titleAwarePosition).getTitle();
holder.textView.setText(title);
holder.textView.setContentDescription(title);
- holder.iconView.setContentDescription(title);
holder.iconView.setImageDrawable(icon);
} else if (viewHolder instanceof TitleViewHolder) {
TitleViewHolder holder = (TitleViewHolder) viewHolder;
diff --git a/android/support/wear/widget/drawer/WearableDrawerLayout.java b/android/support/wear/widget/drawer/WearableDrawerLayout.java
index 6d270647..e100a467 100644
--- a/android/support/wear/widget/drawer/WearableDrawerLayout.java
+++ b/android/support/wear/widget/drawer/WearableDrawerLayout.java
@@ -19,11 +19,10 @@ package android.support.wear.widget.drawer;
import static android.support.wear.widget.drawer.WearableDrawerView.STATE_IDLE;
import static android.support.wear.widget.drawer.WearableDrawerView.STATE_SETTLING;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.NestedScrollingParent;
@@ -98,7 +97,6 @@ import android.widget.FrameLayout;
* &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;
* &lt;/android.support.wear.widget.drawer.WearableDrawerLayout&gt;</pre>
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableDrawerLayout extends FrameLayout
implements View.OnLayoutChangeListener, NestedScrollingParent, FlingListener {
@@ -654,12 +652,13 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override // NestedScrollingParent
- public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
+ boolean consumed) {
return false;
}
@Override // NestedScrollingParent
- public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+ public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
maybeUpdateScrollingContentView(target);
mLastScrollWasFling = true;
@@ -674,13 +673,13 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override // NestedScrollingParent
- public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
maybeUpdateScrollingContentView(target);
}
@Override // NestedScrollingParent
- public void onNestedScroll(
- View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+ public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
boolean scrolledUp = dyConsumed < 0;
boolean scrolledDown = dyConsumed > 0;
@@ -873,18 +872,20 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override // NestedScrollingParent
- public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+ public void onNestedScrollAccepted(@NonNull View child, @NonNull View target,
+ int nestedScrollAxes) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
}
@Override // NestedScrollingParent
- public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ public boolean onStartNestedScroll(@NonNull View child, @NonNull View target,
+ int nestedScrollAxes) {
mCurrentNestedScrollSlopTracker = 0;
return true;
}
@Override // NestedScrollingParent
- public void onStopNestedScroll(View target) {
+ public void onStopNestedScroll(@NonNull View target) {
mNestedScrollingParentHelper.onStopNestedScroll(target);
}
@@ -961,7 +962,7 @@ public class WearableDrawerLayout extends FrameLayout
public abstract WearableDrawerView getDrawerView();
@Override
- public boolean tryCaptureView(View child, int pointerId) {
+ public boolean tryCaptureView(@NonNull View child, int pointerId) {
WearableDrawerView drawerView = getDrawerView();
// Returns true if the dragger is dragging the drawer.
return child == drawerView && !drawerView.isLocked()
@@ -969,13 +970,13 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public int getViewVerticalDragRange(View child) {
+ public int getViewVerticalDragRange(@NonNull View child) {
// Defines the vertical drag range of the drawer.
return child == getDrawerView() ? child.getHeight() : 0;
}
@Override
- public void onViewCaptured(View capturedChild, int activePointerId) {
+ public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
showDrawerContentMaybeAnimate((WearableDrawerView) capturedChild);
}
@@ -1036,7 +1037,7 @@ public class WearableDrawerLayout extends FrameLayout
private class TopDrawerDraggerCallback extends DrawerDraggerCallback {
@Override
- public int clampViewPositionVertical(View child, int top, int dy) {
+ public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
if (mTopDrawerView == child) {
int peekHeight = mTopDrawerView.getPeekContainer().getHeight();
// The top drawer can be dragged vertically from peekHeight - height to 0.
@@ -1063,7 +1064,7 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
if (releasedChild == mTopDrawerView) {
// Settle to final position. Either swipe open or close.
final float openedPercent = mTopDrawerView.getOpenedPercent();
@@ -1085,7 +1086,8 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
+ int dy) {
if (changedView == mTopDrawerView) {
// Compute the offset and invalidate will move the drawer during layout.
final int height = changedView.getHeight();
@@ -1106,7 +1108,7 @@ public class WearableDrawerLayout extends FrameLayout
private class BottomDrawerDraggerCallback extends DrawerDraggerCallback {
@Override
- public int clampViewPositionVertical(View child, int top, int dy) {
+ public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
if (mBottomDrawerView == child) {
// The bottom drawer can be dragged vertically from (parentHeight - height) to
// (parentHeight - peekHeight).
@@ -1131,7 +1133,7 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
if (releasedChild == mBottomDrawerView) {
// Settle to final position. Either swipe open or close.
final int parentHeight = getHeight();
@@ -1151,7 +1153,8 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
+ int dy) {
if (changedView == mBottomDrawerView) {
// Compute the offset and invalidate will move the drawer during layout.
final int height = changedView.getHeight();
diff --git a/android/support/wear/widget/drawer/WearableDrawerView.java b/android/support/wear/widget/drawer/WearableDrawerView.java
index dafac39b..2462cba8 100644
--- a/android/support/wear/widget/drawer/WearableDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableDrawerView.java
@@ -16,11 +16,9 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
@@ -87,7 +85,6 @@ import java.lang.annotation.RetentionPolicy;
* &lt;/LinearLayout&gt;
* &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;</pre>
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableDrawerView extends FrameLayout {
/**
* Indicates that the drawer is in an idle, settled state. No animation is in progress.
@@ -109,7 +106,7 @@ public class WearableDrawerView extends FrameLayout {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @RestrictTo(Scope.LIBRARY)
@IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
public @interface DrawerState {}
@@ -155,8 +152,8 @@ public class WearableDrawerView extends FrameLayout {
setElevation(context.getResources()
.getDimension(R.dimen.ws_wearable_drawer_view_elevation));
- mPeekContainer = (ViewGroup) findViewById(R.id.ws_drawer_view_peek_container);
- mPeekIcon = (ImageView) findViewById(R.id.ws_drawer_view_peek_icon);
+ mPeekContainer = findViewById(R.id.ws_drawer_view_peek_container);
+ mPeekIcon = findViewById(R.id.ws_drawer_view_peek_icon);
mPeekContainer.setOnClickListener(
new OnClickListener() {
diff --git a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
index 480812b8..c5c49fe3 100644
--- a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
@@ -16,11 +16,9 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
@@ -58,7 +56,6 @@ import java.util.concurrent.TimeUnit;
* <p>The developer may specify which style to use with the {@code app:navigationStyle} custom
* attribute. If not specified, {@link #SINGLE_PAGE singlePage} will be used as the default.
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableNavigationDrawerView extends WearableDrawerView {
private static final String TAG = "WearableNavDrawer";
@@ -79,7 +76,7 @@ public class WearableNavigationDrawerView extends WearableDrawerView {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @RestrictTo(Scope.LIBRARY)
@IntDef({SINGLE_PAGE, MULTI_PAGE})
public @interface NavigationStyle {}
@@ -282,7 +279,7 @@ public class WearableNavigationDrawerView extends WearableDrawerView {
/**
* @hide
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @RestrictTo(Scope.LIBRARY)
public void setPresenter(WearableNavigationDrawerPresenter presenter) {
mPresenter = presenter;
}
diff --git a/android/support/wearable/watchface/decomposition/package-info.java b/android/support/wearable/watchface/decomposition/package-info.java
new file mode 100644
index 00000000..dbd815e7
--- /dev/null
+++ b/android/support/wearable/watchface/decomposition/package-info.java
@@ -0,0 +1,2 @@
+/** @hide */
+package android.support.wearable.watchface.decomposition;
diff --git a/android/system/Int32Ref.java b/android/system/Int32Ref.java
new file mode 100644
index 00000000..25a818da
--- /dev/null
+++ b/android/system/Int32Ref.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+/**
+ * A signed 32bit integer reference suitable for passing to lower-level system calls.
+ */
+public class Int32Ref {
+ public int value;
+
+ public Int32Ref(int value) {
+ this.value = value;
+ }
+}
diff --git a/android/system/Int64Ref.java b/android/system/Int64Ref.java
new file mode 100644
index 00000000..f42450d8
--- /dev/null
+++ b/android/system/Int64Ref.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+/**
+ * A signed 64bit integer reference suitable for passing to lower-level system calls.
+ */
+public class Int64Ref {
+ public long value;
+
+ public Int64Ref(long value) {
+ this.value = value;
+ }
+}
diff --git a/android/system/Os.java b/android/system/Os.java
index 2dabae2f..fe8f7d45 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -188,6 +188,17 @@ public final class Os {
public static int getgid() { return Libcore.os.getgid(); }
/**
+ * See <a href="http://man7.org/linux/man-pages/man2/getgroups.2.html">getgroups(2)</a>.
+ *
+ * <p>Should the number of groups change during the execution of this call, the call may
+ * return an arbitrary subset. This may be worth reconsidering should this be exposed
+ * as public API.
+ *
+ * @hide
+ */
+ public static int[] getgroups() throws ErrnoException { return Libcore.os.getgroups(); }
+
+ /**
* See <a href="http://man7.org/linux/man-pages/man3/getenv.3.html">getenv(3)</a>.
*/
public static String getenv(String name) { return Libcore.os.getenv(name); }
@@ -268,7 +279,16 @@ public final class Os {
public static InetAddress inet_pton(int family, String address) { return Libcore.os.inet_pton(family, address); }
/** @hide */ public static InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return Libcore.os.ioctlInetAddress(fd, cmd, interfaceName); }
- /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return Libcore.os.ioctlInt(fd, cmd, arg); }
+
+
+ /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, Int32Ref arg) throws ErrnoException {
+ libcore.util.MutableInt internalArg = new libcore.util.MutableInt(arg.value);
+ try {
+ return Libcore.os.ioctlInt(fd, cmd, internalArg);
+ } finally {
+ arg.value = internalArg.value;
+ }
+ }
/**
* See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
@@ -453,8 +473,41 @@ public final class Os {
/**
* See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
+ *
+ * @deprecated This method will be removed in a future version of Android. Use
+ * {@link #sendfile(FileDescriptor, FileDescriptor, Int64Ref, long)} instead.
+ */
+ @Deprecated
+ public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException {
+ if (inOffset == null) {
+ return Libcore.os.sendfile(outFd, inFd, null, byteCount);
+ } else {
+ libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong(
+ inOffset.value);
+ try {
+ return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount);
+ } finally {
+ inOffset.value = internalInOffset.value;
+ }
+ }
+ }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
*/
- public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return Libcore.os.sendfile(outFd, inFd, inOffset, byteCount); }
+ public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, Int64Ref inOffset, long byteCount) throws ErrnoException {
+ if (inOffset == null) {
+ return Libcore.os.sendfile(outFd, inFd, null, byteCount);
+ } else {
+ libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong(
+ inOffset.value);
+ try {
+ return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount);
+ } finally {
+ inOffset.value = internalInOffset.value;
+ }
+ }
+ }
/**
* See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
@@ -492,6 +545,13 @@ public final class Os {
public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
/**
+ * See <a href="http://man7.org/linux/man-pages/man2/setgroups.2.html">setgroups(2)</a>.
+ *
+ * @hide
+ */
+ public static void setgroups(int[] gids) throws ErrnoException { Libcore.os.setgroups(gids); }
+
+ /**
* See <a href="http://man7.org/linux/man-pages/man2/setpgid.2.html">setpgid(2)</a>.
*/
/** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }
@@ -611,8 +671,41 @@ public final class Os {
/**
* See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
- */
- public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return Libcore.os.waitpid(pid, status, options); }
+ *
+ * @deprecated This method will be removed in a future version of Android. Use
+ * {@link #waitpid(int, Int32Ref, int)} instead.
+ */
+ @Deprecated
+ public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException {
+ if (status == null) {
+ return Libcore.os.waitpid(pid, null, options);
+ } else {
+ libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value);
+ try {
+ return Libcore.os.waitpid(pid, internalStatus, options);
+ } finally {
+ status.value = internalStatus.value;
+ }
+ }
+ }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
+ *
+ * @throws IllegalArgumentException if {@code status != null && status.length != 1}
+ */
+ public static int waitpid(int pid, Int32Ref status, int options) throws ErrnoException {
+ if (status == null) {
+ return Libcore.os.waitpid(pid, null, options);
+ } else {
+ libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value);
+ try {
+ return Libcore.os.waitpid(pid, internalStatus, options);
+ } finally {
+ status.value = internalStatus.value;
+ }
+ }
+ }
/**
* See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
diff --git a/android/system/UnixSocketAddressTest.java b/android/system/UnixSocketAddressTest.java
deleted file mode 100644
index f1b7fc23..00000000
--- a/android/system/UnixSocketAddressTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.system;
-
-import junit.framework.TestCase;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-public class UnixSocketAddressTest extends TestCase {
-
- public void testFilesystemSunPath() throws Exception {
- String path = "/foo/bar";
- UnixSocketAddress sa = UnixSocketAddress.createFileSystem(path);
-
- byte[] abstractNameBytes = path.getBytes(StandardCharsets.UTF_8);
- byte[] expected = new byte[abstractNameBytes.length + 1];
- // See unix(7)
- System.arraycopy(abstractNameBytes, 0, expected, 0, abstractNameBytes.length);
- assertTrue(Arrays.equals(expected, sa.getSunPath()));
- }
-
- public void testUnnamedSunPath() throws Exception {
- UnixSocketAddress sa = UnixSocketAddress.createUnnamed();
- assertEquals(0, sa.getSunPath().length);
- }
-
- public void testAbstractSunPath() throws Exception {
- String abstractName = "abstract";
- UnixSocketAddress sa = UnixSocketAddress.createAbstract(abstractName);
- byte[] abstractNameBytes = abstractName.getBytes(StandardCharsets.UTF_8);
- byte[] expected = new byte[abstractNameBytes.length + 1];
- // See unix(7)
- System.arraycopy(abstractNameBytes, 0, expected, 1, abstractNameBytes.length);
- assertTrue(Arrays.equals(expected, sa.getSunPath()));
- }
-}
diff --git a/android/telecom/Call.java b/android/telecom/Call.java
index e13bd619..a07f2bbf 100644
--- a/android/telecom/Call.java
+++ b/android/telecom/Call.java
@@ -855,6 +855,39 @@ public final class Call {
*/
public static abstract class Callback {
/**
+ * @hide
+ */
+ @IntDef({HANDOVER_FAILURE_DEST_APP_REJECTED, HANDOVER_FAILURE_DEST_NOT_SUPPORTED,
+ HANDOVER_FAILURE_DEST_INVALID_PERM, HANDOVER_FAILURE_DEST_USER_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HandoverFailureErrors {}
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when the app
+ * to handover the call rejects handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_APP_REJECTED = 1;
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when there is
+ * an error associated with unsupported handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_NOT_SUPPORTED = 2;
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when there
+ * are some permission errors associated with APIs doing handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_INVALID_PERM = 3;
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when user
+ * rejects handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_USER_REJECTED = 4;
+
+
+ /**
* Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
*
* @param call The {@code Call} invoking this method.
@@ -989,6 +1022,21 @@ public final class Call {
* {@link android.telecom.Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
*/
public void onRttInitiationFailure(Call call, int reason) {}
+
+ /**
+ * Invoked when Call handover from one {@link PhoneAccount} to other {@link PhoneAccount}
+ * has completed successfully.
+ * @param call The call which had initiated handover.
+ */
+ public void onHandoverComplete(Call call) {}
+
+ /**
+ * Invoked when Call handover from one {@link PhoneAccount} to other {@link PhoneAccount}
+ * has failed.
+ * @param call The call which had initiated handover.
+ * @param failureReason Error reason for failure
+ */
+ public void onHandoverFailed(Call call, @HandoverFailureErrors int failureReason) {}
}
/**
@@ -1367,6 +1415,24 @@ public final class Call {
}
/**
+ * Initiates a handover of this {@link Call} to the {@link ConnectionService} identified
+ * by {@code toHandle}. The videoState specified indicates the desired video state after the
+ * handover.
+ * <p>
+ * A handover request is initiated by the user from one app to indicate a desire
+ * to handover a call to another.
+ *
+ * @param toHandle {@link PhoneAccountHandle} of the {@link ConnectionService} to handover
+ * this call to.
+ * @param videoState Indicates the video state desired after the handover.
+ * @param extras Bundle containing extra information to be passed to the
+ * {@link ConnectionService}
+ */
+ public void handoverTo(PhoneAccountHandle toHandle, int videoState, Bundle extras) {
+ mInCallAdapter.handoverTo(mTelecomCallId, toHandle, videoState, extras);
+ }
+
+ /**
* Terminate the RTT session on this call. The resulting state change will be notified via
* the {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback.
*/
diff --git a/android/telecom/CallAudioState.java b/android/telecom/CallAudioState.java
index f601d8b5..4b827d2e 100644
--- a/android/telecom/CallAudioState.java
+++ b/android/telecom/CallAudioState.java
@@ -16,16 +16,35 @@
package android.telecom;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
/**
* Encapsulates the telecom audio state, including the current audio routing, supported audio
* routing and mute.
*/
public final class CallAudioState implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
+ flag=true)
+ public @interface CallAudioRoute {}
+
/** Direct the audio stream through the device's earpiece. */
public static final int ROUTE_EARPIECE = 0x00000001;
@@ -55,6 +74,8 @@ public final class CallAudioState implements Parcelable {
private final boolean isMuted;
private final int route;
private final int supportedRouteMask;
+ private final BluetoothDevice activeBluetoothDevice;
+ private final Collection<BluetoothDevice> supportedBluetoothDevices;
/**
* Constructor for a {@link CallAudioState} object.
@@ -73,10 +94,21 @@ public final class CallAudioState implements Parcelable {
* {@link #ROUTE_WIRED_HEADSET}
* {@link #ROUTE_SPEAKER}
*/
- public CallAudioState(boolean muted, int route, int supportedRouteMask) {
- this.isMuted = muted;
+ public CallAudioState(boolean muted, @CallAudioRoute int route,
+ @CallAudioRoute int supportedRouteMask) {
+ this(muted, route, supportedRouteMask, null, Collections.emptyList());
+ }
+
+ /** @hide */
+ public CallAudioState(boolean isMuted, @CallAudioRoute int route,
+ @CallAudioRoute int supportedRouteMask,
+ @Nullable BluetoothDevice activeBluetoothDevice,
+ @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) {
+ this.isMuted = isMuted;
this.route = route;
this.supportedRouteMask = supportedRouteMask;
+ this.activeBluetoothDevice = activeBluetoothDevice;
+ this.supportedBluetoothDevices = supportedBluetoothDevices;
}
/** @hide */
@@ -84,6 +116,8 @@ public final class CallAudioState implements Parcelable {
isMuted = state.isMuted();
route = state.getRoute();
supportedRouteMask = state.getSupportedRouteMask();
+ activeBluetoothDevice = state.activeBluetoothDevice;
+ supportedBluetoothDevices = state.getSupportedBluetoothDevices();
}
/** @hide */
@@ -92,6 +126,8 @@ public final class CallAudioState implements Parcelable {
isMuted = state.isMuted();
route = state.getRoute();
supportedRouteMask = state.getSupportedRouteMask();
+ activeBluetoothDevice = null;
+ supportedBluetoothDevices = Collections.emptyList();
}
@Override
@@ -103,17 +139,32 @@ public final class CallAudioState implements Parcelable {
return false;
}
CallAudioState state = (CallAudioState) obj;
- return isMuted() == state.isMuted() && getRoute() == state.getRoute() &&
- getSupportedRouteMask() == state.getSupportedRouteMask();
+ if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) {
+ return false;
+ }
+ for (BluetoothDevice device : supportedBluetoothDevices) {
+ if (!state.supportedBluetoothDevices.contains(device)) {
+ return false;
+ }
+ }
+ return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() ==
+ state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() ==
+ state.getSupportedRouteMask();
}
@Override
public String toString() {
+ String bluetoothDeviceList = supportedBluetoothDevices.stream()
+ .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+
return String.format(Locale.US,
- "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]",
+ "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
+ "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]",
isMuted,
audioRouteToString(route),
- audioRouteToString(supportedRouteMask));
+ audioRouteToString(supportedRouteMask),
+ activeBluetoothDevice,
+ bluetoothDeviceList);
}
/**
@@ -126,6 +177,7 @@ public final class CallAudioState implements Parcelable {
/**
* @return The current audio route being used.
*/
+ @CallAudioRoute
public int getRoute() {
return route;
}
@@ -133,11 +185,27 @@ public final class CallAudioState implements Parcelable {
/**
* @return Bit mask of all routes supported by this call.
*/
+ @CallAudioRoute
public int getSupportedRouteMask() {
return supportedRouteMask;
}
/**
+ * @return The {@link BluetoothDevice} through which audio is being routed.
+ * Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}.
+ */
+ public BluetoothDevice getActiveBluetoothDevice() {
+ return activeBluetoothDevice;
+ }
+
+ /**
+ * @return {@link List} of {@link BluetoothDevice}s that can be used for this call.
+ */
+ public Collection<BluetoothDevice> getSupportedBluetoothDevices() {
+ return supportedBluetoothDevices;
+ }
+
+ /**
* Converts the provided audio route into a human readable string representation.
*
* @param route to convert into a string.
@@ -177,7 +245,13 @@ public final class CallAudioState implements Parcelable {
boolean isMuted = source.readByte() == 0 ? false : true;
int route = source.readInt();
int supportedRouteMask = source.readInt();
- return new CallAudioState(isMuted, route, supportedRouteMask);
+ BluetoothDevice activeBluetoothDevice = source.readParcelable(
+ ClassLoader.getSystemClassLoader());
+ List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
+ source.readParcelableList(supportedBluetoothDevices,
+ ClassLoader.getSystemClassLoader());
+ return new CallAudioState(isMuted, route,
+ supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
}
@Override
@@ -202,6 +276,8 @@ public final class CallAudioState implements Parcelable {
destination.writeByte((byte) (isMuted ? 1 : 0));
destination.writeInt(route);
destination.writeInt(supportedRouteMask);
+ destination.writeParcelable(activeBluetoothDevice, 0);
+ destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0);
}
private static void listAppend(StringBuffer buffer, String str) {
diff --git a/android/telecom/Connection.java b/android/telecom/Connection.java
index 8ba934cc..2bb1c4ed 100644
--- a/android/telecom/Connection.java
+++ b/android/telecom/Connection.java
@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Notification;
+import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -819,7 +820,7 @@ public abstract class Connection extends Conferenceable {
public void onConnectionEvent(Connection c, String event, Bundle extras) {}
/** @hide */
public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
- public void onAudioRouteChanged(Connection c, int audioRoute) {}
+ public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {}
public void onRttInitiationSuccess(Connection c) {}
public void onRttInitiationFailure(Connection c, int reason) {}
public void onRttSessionRemotelyTerminated(Connection c) {}
@@ -1683,6 +1684,8 @@ public abstract class Connection extends Conferenceable {
// The internal telecom call ID associated with this connection.
private String mTelecomCallId;
+ // The PhoneAccountHandle associated with this connection.
+ private PhoneAccountHandle mPhoneAccountHandle;
private int mState = STATE_NEW;
private CallAudioState mCallAudioState;
private Uri mAddress;
@@ -2576,7 +2579,29 @@ public abstract class Connection extends Conferenceable {
*/
public final void setAudioRoute(int route) {
for (Listener l : mListeners) {
- l.onAudioRouteChanged(this, route);
+ l.onAudioRouteChanged(this, route, null);
+ }
+ }
+
+ /**
+ *
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified if the
+ * bluetooth stack is unable to route audio to the requested device.
+ * A list of available devices can be obtained via
+ * {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * <p>
+ * Used by self-managed {@link ConnectionService}s which wish to use bluetooth audio for a
+ * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+ * <p>
+ * See also {@link InCallService#requestBluetoothAudio(String)}
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}.
+ */
+ public void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+ for (Listener l : mListeners) {
+ l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
}
}
@@ -3076,6 +3101,27 @@ public abstract class Connection extends Conferenceable {
}
/**
+ * Sets the {@link PhoneAccountHandle} associated with this connection.
+ *
+ * @hide
+ */
+ public void setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
+ if (mPhoneAccountHandle != phoneAccountHandle) {
+ mPhoneAccountHandle = phoneAccountHandle;
+ notifyPhoneAccountChanged(phoneAccountHandle);
+ }
+ }
+
+ /**
+ * Returns the {@link PhoneAccountHandle} associated with this connection.
+ *
+ * @hide
+ */
+ public PhoneAccountHandle getPhoneAccountHandle() {
+ return mPhoneAccountHandle;
+ }
+
+ /**
* Sends an event associated with this {@code Connection} with associated event extras to the
* {@link InCallService}.
* <p>
diff --git a/android/telecom/ConnectionService.java b/android/telecom/ConnectionService.java
index a81fba95..7e833066 100644
--- a/android/telecom/ConnectionService.java
+++ b/android/telecom/ConnectionService.java
@@ -1294,10 +1294,10 @@ public abstract class ConnectionService extends Service {
}
@Override
- public void onAudioRouteChanged(Connection c, int audioRoute) {
+ public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
String id = mIdByConnection.get(c);
if (id != null) {
- mAdapter.setAudioRoute(id, audioRoute);
+ mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
}
}
@@ -1382,7 +1382,7 @@ public abstract class ConnectionService extends Service {
connection.setTelecomCallId(callId);
if (connection.getState() != Connection.STATE_DISCONNECTED) {
- addConnection(callId, connection);
+ addConnection(request.getAccountHandle(), callId, connection);
}
Uri address = connection.getAddress();
@@ -1846,6 +1846,7 @@ public abstract class ConnectionService extends Service {
mAdapter.setIsConferenced(connectionId, id);
}
}
+ onConferenceAdded(conference);
}
}
@@ -2033,6 +2034,43 @@ public abstract class ConnectionService extends Service {
}
/**
+ * Called by Telecom on the initiating side of the handover to create an instance of a
+ * handover connection.
+ * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
+ * ConnectionService which needs to handover the call.
+ * @param request Details about the call which needs to be handover.
+ * @return Connection object corresponding to the handover call.
+ */
+ public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+ ConnectionRequest request) {
+ return null;
+ }
+
+ /**
+ * Called by Telecom on the receiving side of the handover to request the
+ * {@link ConnectionService} to create an instance of a handover connection.
+ * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
+ * ConnectionService which needs to handover the call.
+ * @param request Details about the call which needs to be handover.
+ * @return {@link Connection} object corresponding to the handover call.
+ */
+ public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+ ConnectionRequest request) {
+ return null;
+ }
+
+ /**
+ * Called by Telecom in response to a {@code TelecomManager#acceptHandover()}
+ * invocation which failed.
+ * @param request Details about the call which needs to be handover.
+ * @param error Reason for handover failure as defined in
+ * {@link android.telecom.Call.Callback#HANDOVER_FAILURE_DEST_INVALID_PERM}
+ */
+ public void onHandoverFailed(ConnectionRequest request, int error) {
+ return;
+ }
+
+ /**
* Create a {@code Connection} for a new unknown call. An unknown call is a call originating
* from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
* call created using
@@ -2056,6 +2094,30 @@ public abstract class ConnectionService extends Service {
public void onConference(Connection connection1, Connection connection2) {}
/**
+ * Called when a connection is added.
+ * @hide
+ */
+ public void onConnectionAdded(Connection connection) {}
+
+ /**
+ * Called when a connection is removed.
+ * @hide
+ */
+ public void onConnectionRemoved(Connection connection) {}
+
+ /**
+ * Called when a conference is added.
+ * @hide
+ */
+ public void onConferenceAdded(Conference conference) {}
+
+ /**
+ * Called when a conference is removed.
+ * @hide
+ */
+ public void onConferenceRemoved(Conference conference) {}
+
+ /**
* Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
* When this method is invoked, this {@link ConnectionService} should create its own
* representation of the conference call and send it to telecom using {@link #addConference}.
@@ -2122,16 +2184,18 @@ public abstract class ConnectionService extends Service {
// prefix for a unique incremental call ID.
id = handle.getComponentName().getClassName() + "@" + getNextCallId();
}
- addConnection(id, connection);
+ addConnection(handle, id, connection);
return id;
}
- private void addConnection(String callId, Connection connection) {
+ private void addConnection(PhoneAccountHandle handle, String callId, Connection connection) {
connection.setTelecomCallId(callId);
mConnectionById.put(callId, connection);
mIdByConnection.put(connection, callId);
connection.addConnectionListener(mConnectionListener);
connection.setConnectionService(this);
+ connection.setPhoneAccountHandle(handle);
+ onConnectionAdded(connection);
}
/** {@hide} */
@@ -2143,6 +2207,7 @@ public abstract class ConnectionService extends Service {
mConnectionById.remove(id);
mIdByConnection.remove(connection);
mAdapter.removeCall(id);
+ onConnectionRemoved(connection);
}
}
@@ -2179,6 +2244,8 @@ public abstract class ConnectionService extends Service {
mConferenceById.remove(id);
mIdByConference.remove(conference);
mAdapter.removeCall(id);
+
+ onConferenceRemoved(conference);
}
}
diff --git a/android/telecom/ConnectionServiceAdapter.java b/android/telecom/ConnectionServiceAdapter.java
index 111fcc78..92a9dc23 100644
--- a/android/telecom/ConnectionServiceAdapter.java
+++ b/android/telecom/ConnectionServiceAdapter.java
@@ -520,11 +520,14 @@ final class ConnectionServiceAdapter implements DeathRecipient {
* @param callId The unique ID of the call.
* @param audioRoute The new audio route (see {@code CallAudioState#ROUTE_*}).
*/
- void setAudioRoute(String callId, int audioRoute) {
- Log.v(this, "setAudioRoute: %s %s", callId, CallAudioState.audioRouteToString(audioRoute));
+ void setAudioRoute(String callId, int audioRoute, String bluetoothAddress) {
+ Log.v(this, "setAudioRoute: %s %s %s", callId,
+ CallAudioState.audioRouteToString(audioRoute),
+ bluetoothAddress);
for (IConnectionServiceAdapter adapter : mAdapters) {
try {
- adapter.setAudioRoute(callId, audioRoute, Log.getExternalSession());
+ adapter.setAudioRoute(callId, audioRoute,
+ bluetoothAddress, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
diff --git a/android/telecom/ConnectionServiceAdapterServant.java b/android/telecom/ConnectionServiceAdapterServant.java
index b1617f4d..3fbdeb1e 100644
--- a/android/telecom/ConnectionServiceAdapterServant.java
+++ b/android/telecom/ConnectionServiceAdapterServant.java
@@ -298,8 +298,8 @@ final class ConnectionServiceAdapterServant {
case MSG_SET_AUDIO_ROUTE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
- mDelegate.setAudioRoute((String) args.arg1, args.argi1,
- (Session.Info) args.arg2);
+ mDelegate.setAudioRoute((String) args.arg1, args.argi1, (String) args.arg2,
+ (Session.Info) args.arg3);
} finally {
args.recycle();
}
@@ -548,12 +548,12 @@ final class ConnectionServiceAdapterServant {
@Override
public final void setAudioRoute(String connectionId, int audioRoute,
- Session.Info sessionInfo) {
-
+ String bluetoothAddress, Session.Info sessionInfo) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = connectionId;
args.argi1 = audioRoute;
- args.arg2 = sessionInfo;
+ args.arg2 = bluetoothAddress;
+ args.arg3 = sessionInfo;
mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, args).sendToTarget();
}
diff --git a/android/telecom/InCallAdapter.java b/android/telecom/InCallAdapter.java
index 9559a28c..4bc2a9b1 100644
--- a/android/telecom/InCallAdapter.java
+++ b/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.os.RemoteException;
@@ -128,7 +129,22 @@ public final class InCallAdapter {
*/
public void setAudioRoute(int route) {
try {
- mAdapter.setAudioRoute(route);
+ mAdapter.setAudioRoute(route, null);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified. A list of
+ * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+ */
+ public void requestBluetoothAudio(String bluetoothAddress) {
+ try {
+ mAdapter.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
} catch (RemoteException e) {
}
}
@@ -419,4 +435,21 @@ public final class InCallAdapter {
} catch (RemoteException ignored) {
}
}
+
+
+ /**
+ * Initiates a handover of this {@link Call} to the {@link ConnectionService} identified
+ * by destAcct.
+ * @param callId The callId of the Call which calls this function.
+ * @param destAcct ConnectionService to which the call should be handed over.
+ * @param videoState The video state desired after the handover.
+ * @param extras Extra information to be passed to ConnectionService
+ */
+ public void handoverTo(String callId, PhoneAccountHandle destAcct, int videoState,
+ Bundle extras) {
+ try {
+ mAdapter.handoverTo(callId, destAcct, videoState, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
}
diff --git a/android/telecom/InCallService.java b/android/telecom/InCallService.java
index e384d469..d558bbae 100644
--- a/android/telecom/InCallService.java
+++ b/android/telecom/InCallService.java
@@ -16,9 +16,11 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
+import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -377,6 +379,22 @@ public abstract class InCallService extends Service {
}
/**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified if the
+ * bluetooth stack is unable to route audio to the requested device.
+ * A list of available devices can be obtained via
+ * {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}.
+ */
+ public final void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+ if (mPhone != null) {
+ mPhone.requestBluetoothAudio(bluetoothAddress);
+ }
+ }
+
+ /**
* Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
* to start displaying in-call information to the user. Each instance of {@code InCallService}
* will have only one {@code Phone}, and this method will be called exactly once in the lifetime
diff --git a/android/telecom/Phone.java b/android/telecom/Phone.java
index 066f6c26..421b1a4b 100644
--- a/android/telecom/Phone.java
+++ b/android/telecom/Phone.java
@@ -17,7 +17,9 @@
package android.telecom;
import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
+import android.os.RemoteException;
import android.util.ArrayMap;
import java.util.Collections;
@@ -295,6 +297,18 @@ public final class Phone {
}
/**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified. A list of
+ * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+ */
+ public void requestBluetoothAudio(String bluetoothAddress) {
+ mInCallAdapter.requestBluetoothAudio(bluetoothAddress);
+ }
+
+ /**
* Turns the proximity sensor on. When this request is made, the proximity sensor will
* become active, and the touch screen and display will be turned off when the user's face
* is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/android/telecom/PhoneAccount.java b/android/telecom/PhoneAccount.java
index 691e7cf1..74b94650 100644
--- a/android/telecom/PhoneAccount.java
+++ b/android/telecom/PhoneAccount.java
@@ -86,13 +86,11 @@ public final class PhoneAccount implements Parcelable {
/**
* Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
* indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
- * connection (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) to this
- * {@link PhoneAccount} from a {@link PhoneAccount} specifying
- * {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
+ * connection (see {@code android.telecom.Call#handoverTo()}) to this {@link PhoneAccount} from
+ * a {@link PhoneAccount} specifying {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
* <p>
* A handover request is initiated by the user from the default dialer app to indicate a desire
* to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
- * @hide
*/
public static final String EXTRA_SUPPORTS_HANDOVER_TO =
"android.telecom.extra.SUPPORTS_HANDOVER_TO";
@@ -113,12 +111,11 @@ public final class PhoneAccount implements Parcelable {
* Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
* indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
* connection from this {@link PhoneAccount} to another {@link PhoneAccount}.
- * (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) which specifies
+ * (see {@code android.telecom.Call#handoverTo()}) which specifies
* {@link #EXTRA_SUPPORTS_HANDOVER_TO}.
* <p>
* A handover request is initiated by the user from the default dialer app to indicate a desire
* to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
- * @hide
*/
public static final String EXTRA_SUPPORTS_HANDOVER_FROM =
"android.telecom.extra.SUPPORTS_HANDOVER_FROM";
@@ -132,7 +129,6 @@ public final class PhoneAccount implements Parcelable {
* <p>
* By default, Self-Managed {@link PhoneAccount}s do not log their calls to the call log.
* Setting this extra to {@code true} provides a means for them to log their calls.
- * @hide
*/
public static final String EXTRA_LOG_SELF_MANAGED_CALLS =
"android.telecom.extra.LOG_SELF_MANAGED_CALLS";
diff --git a/android/telecom/RemoteConnectionService.java b/android/telecom/RemoteConnectionService.java
index 2cc43143..85906ad1 100644
--- a/android/telecom/RemoteConnectionService.java
+++ b/android/telecom/RemoteConnectionService.java
@@ -398,7 +398,8 @@ final class RemoteConnectionService {
}
@Override
- public void setAudioRoute(String callId, int audioRoute, Session.Info sessionInfo) {
+ public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+ Session.Info sessionInfo) {
if (hasConnection(callId)) {
// TODO(3pcalls): handle this for remote connections.
// Likely we don't want to do anything since it doesn't make sense for self-managed
diff --git a/android/telecom/TelecomManager.java b/android/telecom/TelecomManager.java
index 9e52c71b..da32e0be 100644
--- a/android/telecom/TelecomManager.java
+++ b/android/telecom/TelecomManager.java
@@ -1750,6 +1750,41 @@ public class TelecomManager {
return false;
}
+ /**
+ * Called from the recipient side of a handover to indicate a desire to accept the handover
+ * of an ongoing call to another {@link ConnectionService} identified by
+ * {@link PhoneAccountHandle} destAcct. For managed {@link ConnectionService}s, the specified
+ * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and
+ * the user must have enabled the corresponding {@link PhoneAccount}. This can be checked using
+ * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to handover a call to it.
+ * <p>
+ * Once invoked, this method will cause the system to bind to the {@link ConnectionService}
+ * associated with the {@link PhoneAccountHandle} destAcct and call
+ * (See {@link ConnectionService#onCreateIncomingHandoverConnection}).
+ * <p>
+ * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either
+ * the {@link PhoneAccountHandle} destAcct does not correspond to a registered
+ * {@link PhoneAccount} or the associated {@link PhoneAccount} is not currently enabled by the
+ * user.
+ * <p>
+ * For a self-managed {@link ConnectionService}, a {@link SecurityException} will be thrown if
+ * the calling app does not have {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+ *
+ * @param srcAddr The {@link android.net.Uri} of the ongoing call to handover to the caller’s
+ * {@link ConnectionService}.
+ * @param videoState Video state after the handover.
+ * @param destAcct The {@link PhoneAccountHandle} registered to the calling package.
+ */
+ public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct) {
+ try {
+ if (isServiceConnected()) {
+ getTelecomService().acceptHandover(srcAddr, videoState, destAcct);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException acceptHandover: " + e);
+ }
+ }
private ITelecomService getTelecomService() {
if (mTelecomServiceOverride != null) {
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 6fc7d23a..1db6ef7b 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -352,6 +352,17 @@ public class CarrierConfigManager {
public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
/**
+ * Flag that specifies to use the user's own phone number as the voicemail number when there is
+ * no pre-loaded voicemail number on the SIM card.
+ * <p>
+ * {@link #KEY_DEFAULT_VM_NUMBER_STRING} takes precedence over this flag.
+ * <p>
+ * If false, the system default (*86) will be used instead.
+ */
+ public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL =
+ "config_telephony_use_own_number_for_voicemail_bool";
+
+ /**
* When {@code true}, changes to the mobile data enabled switch will not cause the VT
* registration state to change. That is, turning on or off mobile data will not cause VT to be
* enabled or disabled.
@@ -844,6 +855,14 @@ public class CarrierConfigManager {
public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
/**
+ * Default Enhanced 4G LTE mode enabled. When this is {@code true}, Enhanced 4G LTE mode by
+ * default is on, otherwise if {@code false}, Enhanced 4G LTE mode by default is off.
+ * @hide
+ */
+ public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL =
+ "enhanced_4g_lte_on_by_default_bool";
+
+ /**
* Determine whether IMS apn can be shown.
*/
public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
@@ -1187,24 +1206,21 @@ public class CarrierConfigManager {
*/
public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
-
/**
- * @hide
- * The default value for preferred CDMA roaming mode (aka CDMA system select.)
- * CDMA_ROAMING_MODE_RADIO_DEFAULT = the default roaming mode from the radio
- * CDMA_ROAMING_MODE_HOME = Home Networks
- * CDMA_ROAMING_MODE_AFFILIATED = Roaming on Affiliated networks
- * CDMA_ROAMING_MODE_ANY = Roaming on any networks
+ * The CDMA roaming mode (aka CDMA system select).
+ *
+ * <p>The value should be one of the CDMA_ROAMING_MODE_ constants in {@link TelephonyManager}.
+ * Values other than {@link TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT} (which is the
+ * default) will take precedence over user selection.
+ *
+ * @see TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT
+ * @see TelephonyManager#CDMA_ROAMING_MODE_HOME
+ * @see TelephonyManager#CDMA_ROAMING_MODE_AFFILIATED
+ * @see TelephonyManager#CDMA_ROAMING_MODE_ANY
*/
public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
- /** @hide */
- public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_HOME = 0;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_ANY = 2;
+
+
/**
* Boolean indicating if support is provided for directly dialing FDN number from FDN list.
* If false, this feature is not supported.
@@ -1535,6 +1551,13 @@ public class CarrierConfigManager {
"boosted_lte_earfcns_string_array";
/**
+ * Determine whether to use only RSRP for the number of LTE signal bars.
+ * @hide
+ */
+ public static final String KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL =
+ "use_only_rsrp_for_lte_signal_bar_bool";
+
+ /**
* Key identifying if voice call barring notification is required to be shown to the user.
* @hide
*/
@@ -1628,6 +1651,33 @@ public class CarrierConfigManager {
public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
"feature_access_codes_string_array";
+ /**
+ * Determines if the carrier wants to identify high definition calls in the call log.
+ * @hide
+ */
+ public static final String KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL =
+ "identify_high_definition_calls_in_call_log_bool";
+
+ /**
+ * Flag specifying whether to use the {@link ServiceState} roaming status, which can be
+ * affected by other carrier configs (e.g.
+ * {@link #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY}), when setting the SPN display.
+ * <p>
+ * If {@code true}, the SPN display uses {@link ServiceState#getRoaming}.
+ * If {@code false} the SPN display checks if the current MCC/MNC is different from the
+ * SIM card's MCC/MNC.
+ *
+ * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+ * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+ * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+ * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
+ * @see KEY_FORCE_HOME_NETWORK_BOOL
+ *
+ * @hide
+ */
+ public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL =
+ "spn_display_rule_use_roaming_from_service_state_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1645,6 +1695,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
+ sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
@@ -1769,6 +1820,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_ENHANCED_4G_LTE_BOOL, false);
+ sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL, false);
@@ -1821,7 +1873,8 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
- sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT);
+ sDefaults.putInt(
+ KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, "");
// Carrier Signalling Receivers
@@ -1892,6 +1945,7 @@ public class CarrierConfigManager {
null);
sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
sDefaults.putInt(IMSI_KEY_AVAILABILITY_INT, 0);
sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
@@ -1902,6 +1956,8 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
+ sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
}
/**
diff --git a/android/telephony/PhoneStateListener.java b/android/telephony/PhoneStateListener.java
index afff6d54..9ccfa942 100644
--- a/android/telephony/PhoneStateListener.java
+++ b/android/telephony/PhoneStateListener.java
@@ -204,16 +204,6 @@ public class PhoneStateListener {
public static final int LISTEN_VOLTE_STATE = 0x00004000;
/**
- * Listen for OEM hook raw event
- *
- * @see #onOemHookRawEvent
- * @hide
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public static final int LISTEN_OEM_HOOK_RAW_EVENT = 0x00008000;
-
- /**
* Listen for carrier network changes indicated by a carrier app.
*
* @see #onCarrierNetworkRequest
@@ -359,9 +349,6 @@ public class PhoneStateListener {
case LISTEN_DATA_ACTIVATION_STATE:
PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
break;
- case LISTEN_OEM_HOOK_RAW_EVENT:
- PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
- break;
case LISTEN_CARRIER_NETWORK_CHANGE:
PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
break;
@@ -556,16 +543,6 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when OEM hook raw event is received. Requires
- * the READ_PRIVILEGED_PHONE_STATE permission.
- * @param rawData is the byte array of the OEM hook raw data.
- * @hide
- */
- public void onOemHookRawEvent(byte[] rawData) {
- // default implementation empty
- }
-
- /**
* Callback invoked when telephony has received notice from a carrier
* app that a network action that could result in connectivity loss
* has been requested by an app using
@@ -677,10 +654,6 @@ public class PhoneStateListener {
send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
}
- public void onOemHookRawEvent(byte[] rawData) {
- send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
- }
-
public void onCarrierNetworkChange(boolean active) {
send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
}
diff --git a/android/telephony/SignalStrength.java b/android/telephony/SignalStrength.java
index c8b47765..de02de7b 100644
--- a/android/telephony/SignalStrength.java
+++ b/android/telephony/SignalStrength.java
@@ -68,6 +68,7 @@ public class SignalStrength implements Parcelable {
private int mTdScdmaRscp;
private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
+ private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
/**
* Create a new SignalStrength from a intent notifier Bundle
@@ -108,6 +109,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = true;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -134,6 +136,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = gsmFlag;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -145,10 +148,10 @@ public class SignalStrength implements Parcelable {
int cdmaDbm, int cdmaEcio,
int evdoDbm, int evdoEcio, int evdoSnr,
int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) {
+ int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag, boolean lteLevelBaseOnRsrp) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag, lteLevelBaseOnRsrp);
mTdScdmaRscp = tdScdmaRscp;
}
@@ -164,7 +167,7 @@ public class SignalStrength implements Parcelable {
int tdScdmaRscp, boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
mTdScdmaRscp = tdScdmaRscp;
}
@@ -180,7 +183,7 @@ public class SignalStrength implements Parcelable {
boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
}
/**
@@ -194,7 +197,7 @@ public class SignalStrength implements Parcelable {
boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsmFlag);
+ INVALID, INVALID, INVALID, 0, gsmFlag, false);
}
/**
@@ -228,7 +231,7 @@ public class SignalStrength implements Parcelable {
boolean gsm) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsm);
+ INVALID, INVALID, INVALID, 0, gsm, false);
}
/**
@@ -248,6 +251,7 @@ public class SignalStrength implements Parcelable {
* @param lteCqi
* @param lteRsrpBoost
* @param gsm
+ * @param useOnlyRsrpForLteLevel
*
* @hide
*/
@@ -255,7 +259,7 @@ public class SignalStrength implements Parcelable {
int cdmaDbm, int cdmaEcio,
int evdoDbm, int evdoEcio, int evdoSnr,
int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, boolean gsm) {
+ int lteRsrpBoost, boolean gsm, boolean useOnlyRsrpForLteLevel) {
mGsmSignalStrength = gsmSignalStrength;
mGsmBitErrorRate = gsmBitErrorRate;
mCdmaDbm = cdmaDbm;
@@ -271,6 +275,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = lteRsrpBoost;
mTdScdmaRscp = INVALID;
isGsm = gsm;
+ mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
if (DBG) log("initialize: " + toString());
}
@@ -293,6 +298,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = s.mLteRsrpBoost;
mTdScdmaRscp = s.mTdScdmaRscp;
isGsm = s.isGsm;
+ mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
}
/**
@@ -318,6 +324,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = in.readInt();
mTdScdmaRscp = in.readInt();
isGsm = (in.readInt() != 0);
+ mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
}
/**
@@ -366,6 +373,7 @@ public class SignalStrength implements Parcelable {
out.writeInt(mLteRsrpBoost);
out.writeInt(mTdScdmaRscp);
out.writeInt(isGsm ? 1 : 0);
+ out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
}
/**
@@ -449,6 +457,17 @@ public class SignalStrength implements Parcelable {
}
/**
+ * @param useOnlyRsrpForLteLevel true if it uses only RSRP for the number of LTE signal bar,
+ * otherwise false.
+ *
+ * Used by phone to use only RSRP or not for the number of LTE signal bar.
+ * @hide
+ */
+ public void setUseOnlyRsrpForLteLevel(boolean useOnlyRsrpForLteLevel) {
+ mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
+ }
+
+ /**
* @param lteRsrpBoost - signal strength offset
*
* Used by phone to set the lte signal strength offset which will be
@@ -835,6 +854,13 @@ public class SignalStrength implements Parcelable {
}
}
+ if (useOnlyRsrpForLteLevel()) {
+ log("getLTELevel - rsrp = " + rsrpIconLevel);
+ if (rsrpIconLevel != -1) {
+ return rsrpIconLevel;
+ }
+ }
+
/*
* Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5
* dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars
@@ -915,6 +941,15 @@ public class SignalStrength implements Parcelable {
}
/**
+ * @return true if it uses only RSRP for the number of LTE signal bar, otherwise false.
+ *
+ * @hide
+ */
+ public boolean useOnlyRsrpForLteLevel() {
+ return this.mUseOnlyRsrpForLteLevel;
+ }
+
+ /**
* @return get TD_SCDMA dbm
*
* @hide
@@ -974,7 +1009,8 @@ public class SignalStrength implements Parcelable {
+ (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)
+ (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
+ (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
- + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0));
+ + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
+ + (mUseOnlyRsrpForLteLevel ? 1 : 0));
}
/**
@@ -1008,7 +1044,8 @@ public class SignalStrength implements Parcelable {
&& mLteCqi == s.mLteCqi
&& mLteRsrpBoost == s.mLteRsrpBoost
&& mTdScdmaRscp == s.mTdScdmaRscp
- && isGsm == s.isGsm);
+ && isGsm == s.isGsm
+ && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
}
/**
@@ -1031,7 +1068,9 @@ public class SignalStrength implements Parcelable {
+ " " + mLteCqi
+ " " + mLteRsrpBoost
+ " " + mTdScdmaRscp
- + " " + (isGsm ? "gsm|lte" : "cdma"));
+ + " " + (isGsm ? "gsm|lte" : "cdma")
+ + " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
+ "use_rsrp_and_rssnr_for_lte_level"));
}
/** Returns the signal strength related to GSM. */
@@ -1086,6 +1125,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = m.getInt("lteRsrpBoost");
mTdScdmaRscp = m.getInt("TdScdma");
isGsm = m.getBoolean("isGsm");
+ mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
}
/**
@@ -1110,6 +1150,7 @@ public class SignalStrength implements Parcelable {
m.putInt("lteRsrpBoost", mLteRsrpBoost);
m.putInt("TdScdma", mTdScdmaRscp);
m.putBoolean("isGsm", isGsm);
+ m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
}
/**
diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java
index 98195ada..5d039268 100644
--- a/android/telephony/SmsManager.java
+++ b/android/telephony/SmsManager.java
@@ -338,16 +338,18 @@ public final class SmsManager {
/**
* Send a text based SMS without writing it into the SMS Provider.
*
+ * <p>
+ * The message will be sent directly over the network and will not be visible in SMS
+ * applications. Intended for internal carrier use only.
+ * </p>
+ *
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
* privileges.
* </p>
*
* @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
- * @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void sendTextMessageWithoutPersisting(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -387,6 +389,112 @@ public final class SmsManager {
}
/**
+ * Send a text based SMS with messaging options.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ *
+ * @throws IllegalArgumentException if destinationAddress or text are empty
+ * {@hide}
+ */
+ public void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ true /* persistMessage*/, priority, expectMore, validityPeriod);
+ }
+
+ private void sendTextMessageInternal(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+ int priority, boolean expectMore, int validityPeriod) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (priority < 0x00 || priority > 0x03) {
+ throw new IllegalArgumentException("Invalid priority");
+ }
+
+ if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ throw new IllegalArgumentException("Invalid validity period");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ if (iccISms != null) {
+ iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
+ sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+ validityPeriod);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendTextMessage(String, String, String, PendingIntent,
+ * PendingIntent, int, boolean, int)
+ * @hide
+ */
+ public void sendTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
+ boolean expectMore, int validityPeriod) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ false /* persistMessage */, priority, expectMore, validityPeriod);
+ }
+
+ /**
+ *
* Inject an SMS PDU into the android application framework.
*
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
@@ -544,6 +652,140 @@ public final class SmsManager {
}
/**
+ * Send a multi-part text based SMS with messaging options. The callee should have already
+ * divided the message into correctly sized parts by calling
+ * <code>divideMessage</code>.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+ * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ * {@hide}
+ */
+ public void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, true /* persistMessage*/);
+ }
+
+ private void sendMultipartTextMessageInternal(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+ if (parts == null || parts.size() < 1) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (priority < 0x00 || priority > 0x03) {
+ throw new IllegalArgumentException("Invalid priority");
+ }
+
+ if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ throw new IllegalArgumentException("Invalid validity period");
+ }
+
+ if (parts.size() > 1) {
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ if (iccISms != null) {
+ iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress,
+ parts, sentIntents, deliveryIntents, persistMessage, priority,
+ expectMore, validityPeriod);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ } else {
+ PendingIntent sentIntent = null;
+ PendingIntent deliveryIntent = null;
+ if (sentIntents != null && sentIntents.size() > 0) {
+ sentIntent = sentIntents.get(0);
+ }
+ if (deliveryIntents != null && deliveryIntents.size() > 0) {
+ deliveryIntent = deliveryIntents.get(0);
+ }
+ sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
+ sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+ validityPeriod);
+ }
+ }
+
+ /**
+ * Send a multi-part text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList,
+ * ArrayList, int, boolean, int)
+ * @hide
+ **/
+ public void sendMultipartTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, false /* persistMessage*/, priority, expectMore,
+ validityPeriod);
+ }
+
+ /**
* Send a data based SMS to a specific application port.
*
* <p class="note"><strong>Note:</strong> Using this method requires that your app has the
@@ -1006,7 +1248,7 @@ public final class SmsManager {
* <code>getAllMessagesFromIcc</code>
* @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
*/
- private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+ private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
if (records != null) {
int count = records.size();
@@ -1014,7 +1256,8 @@ public final class SmsManager {
SmsRawData data = records.get(i);
// List contains all records, including "free" records (null)
if (data != null) {
- SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+ SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
+ getSubscriptionId());
if (sms != null) {
messages.add(sms);
}
diff --git a/android/telephony/SmsMessage.java b/android/telephony/SmsMessage.java
index df412335..a5d67c60 100644
--- a/android/telephony/SmsMessage.java
+++ b/android/telephony/SmsMessage.java
@@ -271,6 +271,31 @@ public class SmsMessage {
}
/**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @param subId Subscription Id of the SMS
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data, int subId) {
+ SmsMessageBase wrappedMessage;
+
+ if (isCdmaVoice(subId)) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ index, data);
+ } else {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ index, data);
+ }
+
+ return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+ }
+
+ /**
* Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
* length in bytes (not hex chars) less the SMSC header
*
@@ -822,6 +847,7 @@ public class SmsMessage {
int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
return (PHONE_TYPE_CDMA == activePhone);
}
+
/**
* Decide if the carrier supports long SMS.
* {@hide}
diff --git a/android/telephony/SubscriptionManager.java b/android/telephony/SubscriptionManager.java
index 88f4880a..2f39ddb1 100644
--- a/android/telephony/SubscriptionManager.java
+++ b/android/telephony/SubscriptionManager.java
@@ -360,6 +360,42 @@ public class SubscriptionManager {
public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
/**
+ * TelephonyProvider column name for enable Volte.
+ *@hide
+ */
+ public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+
+ /**
+ * TelephonyProvider column name for enable VT (Video Telephony over IMS)
+ *@hide
+ */
+ public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+
+ /**
+ * TelephonyProvider column name for enable Wifi calling
+ *@hide
+ */
+ public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+ /**
+ * TelephonyProvider column name for Wifi calling mode
+ *@hide
+ */
+ public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+ /**
+ * TelephonyProvider column name for Wifi calling mode in roaming
+ *@hide
+ */
+ public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+
+ /**
+ * TelephonyProvider column name for enable Wifi calling in roaming
+ *@hide
+ */
+ public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+ /**
* Broadcast Action: The user has changed one of the default subs related to
* data, phone calls, or sms</p>
*
diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java
index c0564c55..4ffb3c32 100644
--- a/android/telephony/TelephonyManager.java
+++ b/android/telephony/TelephonyManager.java
@@ -953,6 +953,27 @@ public class TelephonyManager {
*/
public static final int USSD_ERROR_SERVICE_UNAVAIL = -2;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which leaves the roaming
+ * mode set to the radio default or to the user's preference if they've indicated one.
+ */
+ public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which only permits
+ * connections on home networks.
+ */
+ public static final int CDMA_ROAMING_MODE_HOME = 0;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+ * affiliated networks.
+ */
+ public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+ * any network.
+ */
+ public static final int CDMA_ROAMING_MODE_ANY = 2;
+
//
//
// Device Info
@@ -2145,13 +2166,16 @@ public class TelephonyManager {
* @hide
*/
public String getSimOperatorNumeric() {
- int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ int subId = mSubId;
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ subId = SubscriptionManager.getDefaultSmsSubscriptionId();
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultSubscriptionId();
+ subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ if (!SubscriptionManager.isUsableSubIdValue(subId)) {
+ subId = SubscriptionManager.getDefaultSubscriptionId();
+ }
}
}
}
@@ -5685,29 +5709,6 @@ public class TelephonyManager {
return retVal;
}
- /**
- * Returns the result and response from RIL for oem request
- *
- * @param oemReq the data is sent to ril.
- * @param oemResp the respose data from RIL.
- * @return negative value request was not handled or get error
- * 0 request was handled succesfully, but no response data
- * positive value success, data length of response
- * @hide
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.invokeOemRilRequestRaw(oemReq, oemResp);
- } catch (RemoteException ex) {
- } catch (NullPointerException ex) {
- }
- return -1;
- }
-
/** @hide */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
diff --git a/android/telephony/TelephonyScanManager.java b/android/telephony/TelephonyScanManager.java
index 92a21b6f..7bcdcdcc 100644
--- a/android/telephony/TelephonyScanManager.java
+++ b/android/telephony/TelephonyScanManager.java
@@ -73,8 +73,8 @@ public final class TelephonyScanManager {
/**
* Informs the user that there is some error about the scan.
*
- * This callback will be called whenever there is any error about the scan, but the scan
- * won't stop unless the onComplete() callback is called.
+ * This callback will be called whenever there is any error about the scan, and the scan
+ * will be terminated. onComplete() will NOT be called.
*/
public void onError(int error) {}
}
diff --git a/android/telephony/euicc/DownloadableSubscription.java b/android/telephony/euicc/DownloadableSubscription.java
index b5484e34..01041c8b 100644
--- a/android/telephony/euicc/DownloadableSubscription.java
+++ b/android/telephony/euicc/DownloadableSubscription.java
@@ -53,6 +53,8 @@ public final class DownloadableSubscription implements Parcelable {
@Nullable
public final String encodedActivationCode;
+ @Nullable private String confirmationCode;
+
// see getCarrierName and setCarrierName
@Nullable
private String carrierName;
@@ -66,6 +68,7 @@ public final class DownloadableSubscription implements Parcelable {
private DownloadableSubscription(Parcel in) {
encodedActivationCode = in.readString();
+ confirmationCode = in.readString();
carrierName = in.readString();
accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
}
@@ -83,6 +86,21 @@ public final class DownloadableSubscription implements Parcelable {
}
/**
+ * Sets the confirmation code.
+ */
+ public void setConfirmationCode(String confirmationCode) {
+ this.confirmationCode = confirmationCode;
+ }
+
+ /**
+ * Returns the confirmation code.
+ */
+ @Nullable
+ public String getConfirmationCode() {
+ return confirmationCode;
+ }
+
+ /**
* Set the user-visible carrier name.
* @hide
*
@@ -134,6 +152,7 @@ public final class DownloadableSubscription implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(encodedActivationCode);
+ dest.writeString(confirmationCode);
dest.writeString(carrierName);
dest.writeTypedArray(accessRules, flags);
}
diff --git a/android/telephony/ims/feature/IMMTelFeature.java b/android/telephony/ims/feature/IMMTelFeature.java
deleted file mode 100644
index d65e27eb..00000000
--- a/android/telephony/ims/feature/IMMTelFeature.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.ims.feature;
-
-import android.app.PendingIntent;
-import android.os.Message;
-import android.os.RemoteException;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsUt;
-
-/**
- * MMTel interface for an ImsService. When updating this interface, ensure that base implementations
- * of your changes are also present in MMTelFeature for compatibility with older versions of the
- * MMTel feature.
- * @hide
- */
-
-public interface IMMTelFeature {
-
- /**
- * Notifies the MMTel feature that you would like to start a session. This should always be
- * done before making/receiving IMS calls. The IMS service will register the device to the
- * operator's network with the credentials (from ISIM) periodically in order to receive calls
- * from the operator's network. When the IMS service receives a new call, it will send out an
- * intent with the provided action string. The intent contains a call ID extra
- * {@link IImsCallSession#getCallId} and it can be used to take a call.
- *
- * @param incomingCallIntent When an incoming call is received, the IMS service will call
- * {@link PendingIntent#send} to send back the intent to the caller with
- * {@link #INCOMING_CALL_RESULT_CODE} as the result code and the intent to fill in the call ID;
- * It cannot be null.
- * @param listener To listen to IMS registration events; It cannot be null
- * @return an integer (greater than 0) representing the session id associated with the session
- * that has been started.
- */
- int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
- throws RemoteException;
-
- /**
- * End a previously started session using the associated sessionId.
- * @param sessionId an integer (greater than 0) representing the ongoing session. See
- * {@link #startSession}.
- */
- void endSession(int sessionId) throws RemoteException;
-
- /**
- * Checks if the IMS service has successfully registered to the IMS network with the specified
- * service & call type.
- *
- * @param callServiceType a service type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
- * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
- * @param callType a call type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
- * {@link ImsCallProfile#CALL_TYPE_VOICE}
- * {@link ImsCallProfile#CALL_TYPE_VT}
- * {@link ImsCallProfile#CALL_TYPE_VS}
- * @return true if the specified service id is connected to the IMS network; false otherwise
- * @throws RemoteException
- */
- boolean isConnected(int callServiceType, int callType) throws RemoteException;
-
- /**
- * Checks if the specified IMS service is opened.
- *
- * @return true if the specified service id is opened; false otherwise
- */
- boolean isOpened() throws RemoteException;
-
- /**
- * Add a new registration listener for the client associated with the session Id.
- * @param listener An implementation of IImsRegistrationListener.
- */
- void addRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException;
-
- /**
- * Remove a previously registered listener using {@link #addRegistrationListener} for the client
- * associated with the session Id.
- * @param listener A previously registered IImsRegistrationListener
- */
- void removeRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException;
-
- /**
- * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
- *
- * @param sessionId a session id which is obtained from {@link #startSession}
- * @param callServiceType a service type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#SERVICE_TYPE_NONE}
- * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
- * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
- * @param callType a call type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#CALL_TYPE_VOICE}
- * {@link ImsCallProfile#CALL_TYPE_VT}
- * {@link ImsCallProfile#CALL_TYPE_VT_TX}
- * {@link ImsCallProfile#CALL_TYPE_VT_RX}
- * {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
- * {@link ImsCallProfile#CALL_TYPE_VS}
- * {@link ImsCallProfile#CALL_TYPE_VS_TX}
- * {@link ImsCallProfile#CALL_TYPE_VS_RX}
- * @return a {@link ImsCallProfile} object
- */
- ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
- throws RemoteException;
-
- /**
- * Creates a {@link ImsCallSession} with the specified call profile.
- * Use other methods, if applicable, instead of interacting with
- * {@link ImsCallSession} directly.
- *
- * @param sessionId a session id which is obtained from {@link #startSession}
- * @param profile a call profile to make the call
- * @param listener An implementation of IImsCallSessionListener.
- */
- IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
- IImsCallSessionListener listener) throws RemoteException;
-
- /**
- * Retrieves the call session associated with a pending call.
- *
- * @param sessionId a session id which is obtained from {@link #startSession}
- * @param callId a call id to make the call
- */
- IImsCallSession getPendingCallSession(int sessionId, String callId) throws RemoteException;
-
- /**
- * @return The Ut interface for the supplementary service configuration.
- */
- IImsUt getUtInterface() throws RemoteException;
-
- /**
- * @return The config interface for IMS Configuration
- */
- IImsConfig getConfigInterface() throws RemoteException;
-
- /**
- * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
- * @param sessionId a session id which is obtained from {@link #startSession}
- */
- void turnOnIms() throws RemoteException;
-
- /**
- * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
- * @param sessionId a session id which is obtained from {@link #startSession}
- */
- void turnOffIms() throws RemoteException;
-
- /**
- * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
- */
- IImsEcbm getEcbmInterface() throws RemoteException;
-
- /**
- * Sets the current UI TTY mode for the MMTelFeature.
- * @param uiTtyMode An integer containing the new UI TTY Mode.
- * @param onComplete A {@link Message} to be used when the mode has been set.
- * @throws RemoteException
- */
- void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException;
-
- /**
- * @return MultiEndpoint interface for DEP notifications
- */
- IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException;
-}
diff --git a/android/telephony/ims/feature/MMTelFeature.java b/android/telephony/ims/feature/MMTelFeature.java
index a71f0bf0..758c379f 100644
--- a/android/telephony/ims/feature/MMTelFeature.java
+++ b/android/telephony/ims/feature/MMTelFeature.java
@@ -32,90 +32,183 @@ import java.util.ArrayList;
import java.util.List;
/**
- * Base implementation, which implements all methods in IMMTelFeature. Any class wishing to use
- * MMTelFeature should extend this class and implement all methods that the service supports.
+ * Base implementation for MMTel.
+ * Any class wishing to use MMTelFeature should extend this class and implement all methods that the
+ * service supports.
*
* @hide
*/
-public class MMTelFeature extends ImsFeature implements IMMTelFeature {
-
- @Override
+public class MMTelFeature extends ImsFeature {
+
+ /**
+ * Notifies the MMTel feature that you would like to start a session. This should always be
+ * done before making/receiving IMS calls. The IMS service will register the device to the
+ * operator's network with the credentials (from ISIM) periodically in order to receive calls
+ * from the operator's network. When the IMS service receives a new call, it will send out an
+ * intent with the provided action string. The intent contains a call ID extra
+ * {@link IImsCallSession#getCallId} and it can be used to take a call.
+ *
+ * @param incomingCallIntent When an incoming call is received, the IMS service will call
+ * {@link PendingIntent#send} to send back the intent to the caller with
+ * ImsManager#INCOMING_CALL_RESULT_CODE as the result code and the intent to fill in the call
+ * ID; It cannot be null.
+ * @param listener To listen to IMS registration events; It cannot be null
+ * @return an integer (greater than 0) representing the session id associated with the session
+ * that has been started.
+ */
public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) {
return 0;
}
- @Override
+ /**
+ * End a previously started session using the associated sessionId.
+ * @param sessionId an integer (greater than 0) representing the ongoing session. See
+ * {@link #startSession}.
+ */
public void endSession(int sessionId) {
}
- @Override
+ /**
+ * Checks if the IMS service has successfully registered to the IMS network with the specified
+ * service & call type.
+ *
+ * @param callSessionType a service type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+ * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+ * @param callType a call type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
+ * {@link ImsCallProfile#CALL_TYPE_VOICE}
+ * {@link ImsCallProfile#CALL_TYPE_VT}
+ * {@link ImsCallProfile#CALL_TYPE_VS}
+ * @return true if the specified service id is connected to the IMS network; false otherwise
+ */
public boolean isConnected(int callSessionType, int callType) {
return false;
}
- @Override
+ /**
+ * Checks if the specified IMS service is opened.
+ *
+ * @return true if the specified service id is opened; false otherwise
+ */
public boolean isOpened() {
return false;
}
- @Override
+ /**
+ * Add a new registration listener for the client associated with the session Id.
+ * @param listener An implementation of IImsRegistrationListener.
+ */
public void addRegistrationListener(IImsRegistrationListener listener) {
}
- @Override
+ /**
+ * Remove a previously registered listener using {@link #addRegistrationListener} for the client
+ * associated with the session Id.
+ * @param listener A previously registered IImsRegistrationListener
+ */
public void removeRegistrationListener(IImsRegistrationListener listener) {
}
- @Override
+ /**
+ * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
+ *
+ * @param sessionId a session id which is obtained from {@link #startSession}
+ * @param callSessionType a service type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#SERVICE_TYPE_NONE}
+ * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+ * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+ * @param callType a call type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#CALL_TYPE_VOICE}
+ * {@link ImsCallProfile#CALL_TYPE_VT}
+ * {@link ImsCallProfile#CALL_TYPE_VT_TX}
+ * {@link ImsCallProfile#CALL_TYPE_VT_RX}
+ * {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
+ * {@link ImsCallProfile#CALL_TYPE_VS}
+ * {@link ImsCallProfile#CALL_TYPE_VS_TX}
+ * {@link ImsCallProfile#CALL_TYPE_VS_RX}
+ * @return a {@link ImsCallProfile} object
+ */
public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) {
return null;
}
- @Override
+ /**
+ * Creates a {@link ImsCallSession} with the specified call profile.
+ * Use other methods, if applicable, instead of interacting with
+ * {@link ImsCallSession} directly.
+ *
+ * @param sessionId a session id which is obtained from {@link #startSession}
+ * @param profile a call profile to make the call
+ * @param listener An implementation of IImsCallSessionListener.
+ */
public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
IImsCallSessionListener listener) {
return null;
}
- @Override
+ /**
+ * Retrieves the call session associated with a pending call.
+ *
+ * @param sessionId a session id which is obtained from {@link #startSession}
+ * @param callId a call id to make the call
+ */
public IImsCallSession getPendingCallSession(int sessionId, String callId) {
return null;
}
- @Override
+ /**
+ * @return The Ut interface for the supplementary service configuration.
+ */
public IImsUt getUtInterface() {
return null;
}
- @Override
+ /**
+ * @return The config interface for IMS Configuration
+ */
public IImsConfig getConfigInterface() {
return null;
}
- @Override
+ /**
+ * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
+ */
public void turnOnIms() {
}
- @Override
+ /**
+ * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
+ */
public void turnOffIms() {
}
- @Override
+ /**
+ * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
+ */
public IImsEcbm getEcbmInterface() {
return null;
}
- @Override
+ /**
+ * Sets the current UI TTY mode for the MMTelFeature.
+ * @param uiTtyMode An integer containing the new UI TTY Mode.
+ * @param onComplete A {@link Message} to be used when the mode has been set.
+ */
public void setUiTTYMode(int uiTtyMode, Message onComplete) {
}
- @Override
+ /**
+ * @return MultiEndpoint interface for DEP notifications
+ */
public IImsMultiEndpoint getMultiEndpointInterface() {
return null;
}
- @Override
+ /**
+ * {@inheritDoc}
+ */
public void onFeatureRemoved() {
}
diff --git a/android/telephony/ims/feature/RcsFeature.java b/android/telephony/ims/feature/RcsFeature.java
index 9cddc1b9..332cca3e 100644
--- a/android/telephony/ims/feature/RcsFeature.java
+++ b/android/telephony/ims/feature/RcsFeature.java
@@ -18,11 +18,11 @@ package android.telephony.ims.feature;
/**
* Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
- * this class and provide implementations of the IRcsFeature methods that they support.
+ * this class and provide implementations of the RcsFeature methods that they support.
* @hide
*/
-public class RcsFeature extends ImsFeature implements IRcsFeature {
+public class RcsFeature extends ImsFeature {
public RcsFeature() {
super();
diff --git a/android/telephony/mbms/DownloadStateCallback.java b/android/telephony/mbms/DownloadStateCallback.java
index 892fbf07..9f60cc36 100644
--- a/android/telephony/mbms/DownloadStateCallback.java
+++ b/android/telephony/mbms/DownloadStateCallback.java
@@ -38,7 +38,7 @@ public class DownloadStateCallback {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+ @IntDef(flag = true, value = {ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
public @interface FilterFlag {}
/**
diff --git a/android/telephony/mbms/FileInfo.java b/android/telephony/mbms/FileInfo.java
index 0d737b58..e064adb5 100644
--- a/android/telephony/mbms/FileInfo.java
+++ b/android/telephony/mbms/FileInfo.java
@@ -17,10 +17,13 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* Describes a single file that is available over MBMS.
*/
@@ -47,6 +50,7 @@ public final class FileInfo implements Parcelable {
* @hide
*/
@SystemApi
+ @TestApi
public FileInfo(Uri uri, String mimeType) {
this.uri = uri;
this.mimeType = mimeType;
@@ -82,4 +86,23 @@ public final class FileInfo implements Parcelable {
public String getMimeType() {
return mimeType;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ FileInfo fileInfo = (FileInfo) o;
+ return Objects.equals(uri, fileInfo.uri) &&
+ Objects.equals(mimeType, fileInfo.mimeType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uri, mimeType);
+ }
}
diff --git a/android/telephony/mbms/FileServiceInfo.java b/android/telephony/mbms/FileServiceInfo.java
index d8d7f48a..b30a3af7 100644
--- a/android/telephony/mbms/FileServiceInfo.java
+++ b/android/telephony/mbms/FileServiceInfo.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,6 +36,7 @@ public final class FileServiceInfo extends ServiceInfo implements Parcelable {
/** @hide */
@SystemApi
+ @TestApi
public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
List<Locale> newLocales, String newServiceId, Date start, Date end,
List<FileInfo> newFiles) {
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index 9af1eb9e..9ef188cf 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,16 +165,16 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
return false;
}
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
+ Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
+ return false;
+ }
// We do not need to verify below extras if the result is not success.
if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
MbmsDownloadSession.RESULT_CANCELLED)) {
return true;
}
- if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
- Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
- return false;
- }
if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
@@ -242,10 +242,12 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
MbmsDownloadSession.RESULT_CANCELLED);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
context.sendBroadcast(intentForApp);
+ setResultCode(RESULT_OK);
return;
}
@@ -273,7 +275,6 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
stagedFileLocation);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
- intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
context.sendBroadcast(intentForApp);
setResultCode(RESULT_OK);
diff --git a/android/telephony/mbms/UriPathPair.java b/android/telephony/mbms/UriPathPair.java
index 187e9eed..dd20a692 100644
--- a/android/telephony/mbms/UriPathPair.java
+++ b/android/telephony/mbms/UriPathPair.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Parcel;
@@ -29,6 +30,7 @@ import android.telephony.mbms.vendor.VendorUtils;
* @hide
*/
@SystemApi
+@TestApi
public final class UriPathPair implements Parcelable {
private final Uri mFilePathUri;
private final Uri mContentUri;
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 9ccdd56f..4fee3df8 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -18,6 +18,7 @@ package android.telephony.mbms.vendor;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
@@ -42,6 +43,7 @@ import java.util.Map;
* @hide
*/
@SystemApi
+@TestApi
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
diff --git a/android/telephony/mbms/vendor/VendorUtils.java b/android/telephony/mbms/vendor/VendorUtils.java
index a43f1224..f1cac8cf 100644
--- a/android/telephony/mbms/vendor/VendorUtils.java
+++ b/android/telephony/mbms/vendor/VendorUtils.java
@@ -17,6 +17,7 @@
package android.telephony.mbms.vendor;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -34,6 +35,7 @@ import java.util.List;
* @hide
*/
@SystemApi
+@TestApi
public class VendorUtils {
/**
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java
index fc1d4873..2a272208 100644
--- a/android/util/FeatureFlagUtils.java
+++ b/android/util/FeatureFlagUtils.java
@@ -16,6 +16,7 @@
package android.util;
+import android.content.Context;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -37,7 +38,7 @@ public class FeatureFlagUtils {
* @param feature the flag name
* @return true if the flag is enabled (either by default in system, or override by user)
*/
- public static boolean isEnabled(String feature) {
+ public static boolean isEnabled(Context context, String feature) {
// Tries to get feature flag from system property.
// Step 1: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
String value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
diff --git a/android/util/KeyValueListParser.java b/android/util/KeyValueListParser.java
index be531ff3..d50395e2 100644
--- a/android/util/KeyValueListParser.java
+++ b/android/util/KeyValueListParser.java
@@ -147,4 +147,18 @@ public class KeyValueListParser {
}
return def;
}
+
+ /**
+ * @return the number of keys.
+ */
+ public int size() {
+ return mValues.size();
+ }
+
+ /**
+ * @return the key at {@code index}. Use with {@link #size()} to enumerate all key-value pairs.
+ */
+ public String keyAt(int index) {
+ return mValues.keyAt(index);
+ }
}
diff --git a/android/util/Log.java b/android/util/Log.java
index 02998653..b94e48b3 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,45 +16,12 @@
package android.util;
-import android.os.DeadSystemException;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.LineBreakBufferedWriter;
-
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.io.Writer;
import java.net.UnknownHostException;
/**
- * API for sending log output.
- *
- * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
- * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
- * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
- *
- * <p>The order in terms of verbosity, from least to most is
- * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled
- * into an application except during development. Debug logs are compiled
- * in but stripped at runtime. Error, warning and info logs are always kept.
- *
- * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
- * in your class:
- *
- * <pre>private static final String TAG = "MyActivity";</pre>
- *
- * and use that in subsequent calls to the log methods.
- * </p>
- *
- * <p><b>Tip:</b> Don't forget that when you make a call like
- * <pre>Log.v(TAG, "index=" + i);</pre>
- * that when you're building the string to pass into Log.d, the compiler uses a
- * StringBuilder and at least three allocations occur: the StringBuilder
- * itself, the buffer, and the String object. Realistically, there is also
- * another buffer allocation and copy, and even more pressure on the gc.
- * That means that if your log message is filtered out, you might be doing
- * significant work and incurring significant overhead.
+ * Mock Log implementation for testing on non android host.
*/
public final class Log {
@@ -88,29 +55,6 @@ public final class Log {
*/
public static final int ASSERT = 7;
- /**
- * Exception class used to capture a stack trace in {@link #wtf}.
- * @hide
- */
- public static class TerribleFailure extends Exception {
- TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
- }
-
- /**
- * Interface to handle terrible failures from {@link #wtf}.
- *
- * @hide
- */
- public interface TerribleFailureHandler {
- void onTerribleFailure(String tag, TerribleFailure what, boolean system);
- }
-
- private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
- public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
- RuntimeInit.wtf(tag, what, system);
- }
- };
-
private Log() {
}
@@ -121,7 +65,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -132,7 +76,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -142,7 +86,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -153,7 +97,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -163,7 +107,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println_native(LOG_ID_MAIN, INFO, tag, msg);
+ return println(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -174,7 +118,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
+ return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -184,7 +128,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println_native(LOG_ID_MAIN, WARN, tag, msg);
+ return println(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -195,31 +139,9 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
+ return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
- /**
- * Checks to see whether or not a log for the specified tag is loggable at the specified level.
- *
- * The default level of any tag is set to INFO. This means that any level above and including
- * INFO will be logged. Before you make any calls to a logging method you should check to see
- * if your tag should be logged. You can change the default level by setting a system property:
- * 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
- * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
- * turn off all logging for your tag. You can also create a local.prop file that with the
- * following in it:
- * 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
- * and place that in /data/local.prop.
- *
- * @param tag The tag to check.
- * @param level The level to check.
- * @return Whether or not that this is allowed to be logged.
- * @throws IllegalArgumentException is thrown if the tag.length() > 23
- * for Nougat (7.0) releases (API <= 23) and prior, there is no
- * tag limit of concern after this API level.
- */
- public static native boolean isLoggable(String tag, int level);
-
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -227,7 +149,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
+ return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
/**
@@ -237,7 +159,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println_native(LOG_ID_MAIN, ERROR, tag, msg);
+ return println(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -248,82 +170,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- */
- public static int wtf(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
- }
-
- /**
- * Like {@link #wtf(String, String)}, but also writes to the log the full
- * call stack.
- * @hide
- */
- public static int wtfStack(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, String)}, with an exception to log.
- * @param tag Used to identify the source of a log message.
- * @param tr An exception to log.
- */
- public static int wtf(String tag, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, Throwable)}, with a message as well.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- * @param tr An exception to log. May be null.
- */
- public static int wtf(String tag, String msg, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
- }
-
- static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
- boolean system) {
- TerribleFailure what = new TerribleFailure(msg, tr);
- // Only mark this as ERROR, do not use ASSERT since that should be
- // reserved for cases where the system is guaranteed to abort.
- // The onTerribleFailure call does not always cause a crash.
- int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
- sWtfHandler.onTerribleFailure(tag, what, system);
- return bytes;
- }
-
- static void wtfQuiet(int logId, String tag, String msg, boolean system) {
- TerribleFailure what = new TerribleFailure(msg, null);
- sWtfHandler.onTerribleFailure(tag, what, system);
- }
-
- /**
- * Sets the terrible failure handler, for testing.
- *
- * @return the old handler
- *
- * @hide
- */
- public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
- if (handler == null) {
- throw new NullPointerException("handler == null");
- }
- TerribleFailureHandler oldHandler = sWtfHandler;
- sWtfHandler = handler;
- return oldHandler;
+ return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -346,7 +193,7 @@ public final class Log {
}
StringWriter sw = new StringWriter();
- PrintWriter pw = new FastPrintWriter(sw, false, 256);
+ PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
@@ -361,7 +208,7 @@ public final class Log {
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
- return println_native(LOG_ID_MAIN, priority, tag, msg);
+ return println(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -370,115 +217,9 @@ public final class Log {
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
- /** @hide */ public static native int println_native(int bufID,
- int priority, String tag, String msg);
-
- /**
- * Return the maximum payload the log daemon accepts without truncation.
- * @return LOGGER_ENTRY_MAX_PAYLOAD.
- */
- private static native int logger_entry_max_payload_native();
-
- /**
- * Helper function for long messages. Uses the LineBreakBufferedWriter to break
- * up long messages and stacktraces along newlines, but tries to write in large
- * chunks. This is to avoid truncation.
- * @hide
- */
- public static int printlns(int bufID, int priority, String tag, String msg,
- Throwable tr) {
- ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
- // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
- // and the length of the tag.
- // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
- // is too expensive to compute that ahead of time.
- int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
- - 2 // Two terminators.
- - (tag != null ? tag.length() : 0) // Tag length.
- - 32; // Some slack.
- // At least assume you can print *some* characters (tag is not too large).
- bufferSize = Math.max(bufferSize, 100);
-
- LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
-
- lbbw.println(msg);
-
- if (tr != null) {
- // This is to reduce the amount of log spew that apps do in the non-error
- // condition of the network being unavailable.
- Throwable t = tr;
- while (t != null) {
- if (t instanceof UnknownHostException) {
- break;
- }
- if (t instanceof DeadSystemException) {
- lbbw.println("DeadSystemException: The system died; "
- + "earlier logs will point to the root cause");
- break;
- }
- t = t.getCause();
- }
- if (t == null) {
- tr.printStackTrace(lbbw);
- }
- }
-
- lbbw.flush();
-
- return logWriter.getWritten();
- }
-
- /**
- * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
- * a JNI call during logging.
- */
- static class PreloadHolder {
- public final static int LOGGER_ENTRY_MAX_PAYLOAD =
- logger_entry_max_payload_native();
- }
-
- /**
- * Helper class to write to the logcat. Different from LogWriter, this writes
- * the whole given buffer and does not break along newlines.
- */
- private static class ImmediateLogWriter extends Writer {
-
- private int bufID;
- private int priority;
- private String tag;
-
- private int written = 0;
-
- /**
- * Create a writer that immediately writes to the log, using the given
- * parameters.
- */
- public ImmediateLogWriter(int bufID, int priority, String tag) {
- this.bufID = bufID;
- this.priority = priority;
- this.tag = tag;
- }
-
- public int getWritten() {
- return written;
- }
-
- @Override
- public void write(char[] cbuf, int off, int len) {
- // Note: using String here has a bit of overhead as a Java object is created,
- // but using the char[] directly is not easier, as it needs to be translated
- // to a C char[] for logging.
- written += println_native(bufID, priority, tag, new String(cbuf, off, len));
- }
-
- @Override
- public void flush() {
- // Ignored.
- }
-
- @Override
- public void close() {
- // Ignored.
- }
+ /** @hide */ @SuppressWarnings("unused")
+ public static int println(int bufID,
+ int priority, String tag, String msg) {
+ return 0;
}
}
diff --git a/android/util/StatsManager.java b/android/util/StatsManager.java
new file mode 100644
index 00000000..55b33a61
--- /dev/null
+++ b/android/util/StatsManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * API for StatsD clients to send configurations and retrieve data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsManager {
+ IStatsManager mService;
+ private static final String TAG = "StatsManager";
+
+ /**
+ * Constructor for StatsManagerClient.
+ *
+ * @hide
+ */
+ public StatsManager() {
+ }
+
+ /**
+ * Clients can send a configuration and simultaneously registers the name of a broadcast
+ * receiver that listens for when it should request data.
+ *
+ * @param configKey An arbitrary string that allows clients to track the configuration.
+ * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
+ * dependencies eg, conditions and matchers).
+ * @param pkg The package name to receive the broadcast.
+ * @param cls The name of the class that receives the broadcast.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ throw new RuntimeException("StatsD service connection lost");
+ }
+ return service.addConfiguration(configKey, config, pkg, cls);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when getting data");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Remove a configuration from logging.
+ *
+ * @param configKey Configuration key to remove.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean removeConfiguration(String configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ throw new RuntimeException("StatsD service connection lost");
+ }
+ return service.removeConfiguration(configKey);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when getting data");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Clients can request data with a binder call.
+ *
+ * @param configKey Configuration key to retrieve data from.
+ * @return Serialized ConfigMetricsReport proto. Returns null on failure.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getData(String configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ throw new RuntimeException("StatsD service connection lost");
+ }
+ return service.getData(configKey);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connecto statsd when getting data");
+ return null;
+ }
+ }
+ }
+
+ private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ synchronized (this) {
+ mService = null;
+ }
+ }
+ }
+
+ private IStatsManager getIStatsManagerLocked() throws RemoteException {
+ if (mService != null) {
+ return mService;
+ }
+ mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ if (mService != null) {
+ mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+ }
+ return mService;
+ }
+}
diff --git a/android/util/TimeUtils.java b/android/util/TimeUtils.java
index 2b03ed6c..cc4a0b60 100644
--- a/android/util/TimeUtils.java
+++ b/android/util/TimeUtils.java
@@ -340,6 +340,14 @@ public class TimeUtils {
}
/** @hide Just for debugging; not internationalized. */
+ public static String formatDuration(long duration) {
+ synchronized (sFormatSync) {
+ int len = formatDurationLocked(duration, 0);
+ return new String(sFormatStr, 0, len);
+ }
+ }
+
+ /** @hide Just for debugging; not internationalized. */
public static void formatDuration(long duration, PrintWriter pw) {
formatDuration(duration, pw, 0);
}
diff --git a/android/util/apk/ApkSignatureSchemeV2Verifier.java b/android/util/apk/ApkSignatureSchemeV2Verifier.java
index a9ccae11..18081234 100644
--- a/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,9 +16,6 @@
package android.util.apk;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.Pair;
@@ -30,7 +27,6 @@ import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.nio.DirectByteBuffer;
import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -122,40 +118,6 @@ public class ApkSignatureSchemeV2Verifier {
}
/**
- * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
- * contained in the block against the file.
- */
- private static class SignatureInfo {
- /** Contents of APK Signature Scheme v2 block. */
- private final ByteBuffer signatureBlock;
-
- /** Position of the APK Signing Block in the file. */
- private final long apkSigningBlockOffset;
-
- /** Position of the ZIP Central Directory in the file. */
- private final long centralDirOffset;
-
- /** Position of the ZIP End of Central Directory (EoCD) in the file. */
- private final long eocdOffset;
-
- /** Contents of ZIP End of Central Directory (EoCD) of the file. */
- private final ByteBuffer eocd;
-
- private SignatureInfo(
- ByteBuffer signatureBlock,
- long apkSigningBlockOffset,
- long centralDirOffset,
- long eocdOffset,
- ByteBuffer eocd) {
- this.signatureBlock = signatureBlock;
- this.apkSigningBlockOffset = apkSigningBlockOffset;
- this.centralDirOffset = centralDirOffset;
- this.eocdOffset = eocdOffset;
- this.eocd = eocd;
- }
- }
-
- /**
* Returns the APK Signature Scheme v2 block contained in the provided APK file and the
* additional information relevant for verifying the block against the file.
*
@@ -497,6 +459,7 @@ public class ApkSignatureSchemeV2Verifier {
// TODO: Compute digests of chunks in parallel when beneficial. This requires some research
// into how to parallelize (if at all) based on the capabilities of the hardware on which
// this code is running and based on the size of input.
+ DataDigester digester = new MultipleDigestDataDigester(mds);
int dataSourceIndex = 0;
for (DataSource input : contents) {
long inputOffset = 0;
@@ -508,7 +471,7 @@ public class ApkSignatureSchemeV2Verifier {
mds[i].update(chunkContentPrefix);
}
try {
- input.feedIntoMessageDigests(mds, inputOffset, chunkSize);
+ input.feedIntoDataDigester(digester, inputOffset, chunkSize);
} catch (IOException e) {
throw new DigestException(
"Failed to digest chunk #" + chunkIndex + " of section #"
@@ -967,155 +930,26 @@ public class ApkSignatureSchemeV2Verifier {
}
/**
- * Source of data to be digested.
+ * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
*/
- private static interface DataSource {
-
- /**
- * Returns the size (in bytes) of the data offered by this source.
- */
- long size();
-
- /**
- * Feeds the specified region of this source's data into the provided digests. Each digest
- * instance gets the same data.
- *
- * @param offset offset of the region inside this data source.
- * @param size size (in bytes) of the region.
- */
- void feedIntoMessageDigests(MessageDigest[] mds, long offset, int size) throws IOException;
- }
+ private static class MultipleDigestDataDigester implements DataDigester {
+ private final MessageDigest[] mMds;
- /**
- * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
- * of the file requested by
- * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
- */
- private static final class MemoryMappedFileDataSource implements DataSource {
- private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
-
- private final FileDescriptor mFd;
- private final long mFilePosition;
- private final long mSize;
-
- /**
- * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
- *
- * @param position start position of the region in the file.
- * @param size size (in bytes) of the region.
- */
- public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
- mFd = fd;
- mFilePosition = position;
- mSize = size;
+ MultipleDigestDataDigester(MessageDigest[] mds) {
+ mMds = mds;
}
@Override
- public long size() {
- return mSize;
- }
-
- @Override
- public void feedIntoMessageDigests(
- MessageDigest[] mds, long offset, int size) throws IOException {
- // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
- // method was settled on a straightforward mmap with prefaulting.
- //
- // This method is not using FileChannel.map API because that API does not offset a way
- // to "prefault" the resulting memory pages. Without prefaulting, performance is about
- // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
- // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
- // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
- // time, which is not compensated for by faster reads.
-
- // We mmap the smallest region of the file containing the requested data. mmap requires
- // that the start offset in the file must be a multiple of memory page size. We thus may
- // need to mmap from an offset less than the requested offset.
- long filePosition = mFilePosition + offset;
- long mmapFilePosition =
- (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
- int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
- long mmapRegionSize = size + dataStartOffsetInMmapRegion;
- long mmapPtr = 0;
- try {
- mmapPtr = Os.mmap(
- 0, // let the OS choose the start address of the region in memory
- mmapRegionSize,
- OsConstants.PROT_READ,
- OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
- mFd,
- mmapFilePosition);
- // Feeding a memory region into MessageDigest requires the region to be represented
- // as a direct ByteBuffer.
- ByteBuffer buf = new DirectByteBuffer(
- size,
- mmapPtr + dataStartOffsetInMmapRegion,
- mFd, // not really needed, but just in case
- null, // no need to clean up -- it's taken care of by the finally block
- true // read only buffer
- );
- for (MessageDigest md : mds) {
- buf.position(0);
- md.update(buf);
- }
- } catch (ErrnoException e) {
- throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
- } finally {
- if (mmapPtr != 0) {
- try {
- Os.munmap(mmapPtr, mmapRegionSize);
- } catch (ErrnoException ignored) {}
- }
+ public void consume(ByteBuffer buffer) {
+ buffer = buffer.slice();
+ for (MessageDigest md : mMds) {
+ buffer.position(0);
+ md.update(buffer);
}
}
- }
-
- /**
- * {@link DataSource} which provides data from a {@link ByteBuffer}.
- */
- private static final class ByteBufferDataSource implements DataSource {
- /**
- * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
- * The buffer's position is 0 and limit is equal to capacity.
- */
- private final ByteBuffer mBuf;
-
- public ByteBufferDataSource(ByteBuffer buf) {
- // Defensive copy, to avoid changes to mBuf being visible in buf.
- mBuf = buf.slice();
- }
@Override
- public long size() {
- return mBuf.capacity();
- }
-
- @Override
- public void feedIntoMessageDigests(
- MessageDigest[] mds, long offset, int size) throws IOException {
- // There's no way to tell MessageDigest to read data from ByteBuffer from a position
- // other than the buffer's current position. We thus need to change the buffer's
- // position to match the requested offset.
- //
- // In the future, it may be necessary to compute digests of multiple regions in
- // parallel. Given that digest computation is a slow operation, we enable multiple
- // such requests to be fulfilled by this instance. This is achieved by serially
- // creating a new ByteBuffer corresponding to the requested data range and then,
- // potentially concurrently, feeding these buffers into MessageDigest instances.
- ByteBuffer region;
- synchronized (mBuf) {
- mBuf.position((int) offset);
- mBuf.limit((int) offset + size);
- region = mBuf.slice();
- }
-
- for (MessageDigest md : mds) {
- // Need to reset position to 0 at the start of each iteration because
- // MessageDigest.update below sets it to the buffer's limit.
- region.position(0);
- md.update(region);
- }
- }
+ public void finish() {}
}
/**
diff --git a/android/util/apk/ApkVerityBuilder.java b/android/util/apk/ApkVerityBuilder.java
new file mode 100644
index 00000000..7412ef41
--- /dev/null
+++ b/android/util/apk/ApkVerityBuilder.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+
+/**
+ * ApkVerityBuilder builds the APK verity tree and the verity header, which will be used by the
+ * kernel to verity the APK content on access.
+ *
+ * <p>Unlike a regular Merkle tree, APK verity tree does not cover the content fully. Due to
+ * the existing APK format, it has to skip APK Signing Block and also has some special treatment for
+ * the "Central Directory offset" field of ZIP End of Central Directory.
+ *
+ * @hide
+ */
+abstract class ApkVerityBuilder {
+ private ApkVerityBuilder() {}
+
+ private static final int CHUNK_SIZE_BYTES = 4096; // Typical Linux block size
+ private static final int DIGEST_SIZE_BYTES = 32; // SHA-256 size
+ private static final int FSVERITY_HEADER_SIZE_BYTES = 64;
+ private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE = 4;
+ private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+ private static final String JCA_DIGEST_ALGORITHM = "SHA-256";
+ private static final byte[] DEFAULT_SALT = new byte[8];
+
+ static class ApkVerityResult {
+ public final ByteBuffer fsverityData;
+ public final byte[] rootHash;
+
+ ApkVerityResult(ByteBuffer fsverityData, byte[] rootHash) {
+ this.fsverityData = fsverityData;
+ this.rootHash = rootHash;
+ }
+ }
+
+ /**
+ * Generates fsverity metadata and the Merkle tree into the {@link ByteBuffer} created by the
+ * {@link ByteBufferFactory}. The bytes layout in the buffer will be used by the kernel and is
+ * ready to be appended to the target file to set up fsverity. For fsverity to work, this data
+ * must be placed at the next page boundary, and the caller must add additional padding in that
+ * case.
+ *
+ * @return ApkVerityResult containing the fsverity data and the root hash of the Merkle tree.
+ */
+ static ApkVerityResult generateApkVerity(RandomAccessFile apk,
+ SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
+ throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
+ assertSigningBlockAlignedAndHasFullPages(signatureInfo);
+
+ long signingBlockSize =
+ signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+ long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ int[] levelOffset = calculateVerityLevelOffset(dataSize);
+ ByteBuffer output = bufferFactory.create(
+ CHUNK_SIZE_BYTES + // fsverity header + extensions + padding
+ levelOffset[levelOffset.length - 1] + // Merkle tree size
+ FSVERITY_HEADER_SIZE_BYTES); // second fsverity header (verbatim copy)
+
+ // Start generating the tree from the block boundary as the kernel will expect.
+ ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES,
+ output.limit() - FSVERITY_HEADER_SIZE_BYTES);
+ byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset,
+ treeOutput);
+
+ ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT);
+ output.put(integrityHeader);
+ output.put(generateFsverityExtensions());
+
+ integrityHeader.rewind();
+ output.put(integrityHeader);
+ output.rewind();
+ return new ApkVerityResult(output, rootHash);
+ }
+
+ /**
+ * A helper class to consume and digest data by block continuously, and write into a buffer.
+ */
+ private static class BufferedDigester implements DataDigester {
+ /** Amount of the data to digest in each cycle before writting out the digest. */
+ private static final int BUFFER_SIZE = CHUNK_SIZE_BYTES;
+
+ /**
+ * Amount of data the {@link MessageDigest} has consumed since the last reset. This must be
+ * always less than BUFFER_SIZE since {@link MessageDigest} is reset whenever it has
+ * consumed BUFFER_SIZE of data.
+ */
+ private int mBytesDigestedSinceReset;
+
+ /** The final output {@link ByteBuffer} to write the digest to sequentially. */
+ private final ByteBuffer mOutput;
+
+ private final MessageDigest mMd;
+ private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES];
+ private final byte[] mSalt;
+
+ private BufferedDigester(byte[] salt, ByteBuffer output) throws NoSuchAlgorithmException {
+ mSalt = salt;
+ mOutput = output.slice();
+ mMd = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+ mMd.update(mSalt);
+ mBytesDigestedSinceReset = 0;
+ }
+
+ /**
+ * Consumes and digests data up to BUFFER_SIZE (may continue from the previous remaining),
+ * then writes the final digest to the output buffer. Repeat until all data are consumed.
+ * If the last consumption is not enough for BUFFER_SIZE, the state will stay and future
+ * consumption will continuous from there.
+ */
+ @Override
+ public void consume(ByteBuffer buffer) throws DigestException {
+ int offset = buffer.position();
+ int remaining = buffer.remaining();
+ while (remaining > 0) {
+ int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset);
+ // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object.
+ buffer.limit(buffer.position() + allowance);
+ mMd.update(buffer);
+ offset += allowance;
+ remaining -= allowance;
+ mBytesDigestedSinceReset += allowance;
+
+ if (mBytesDigestedSinceReset == BUFFER_SIZE) {
+ mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+ mOutput.put(mDigestBuffer);
+ // After digest, MessageDigest resets automatically, so no need to reset again.
+ mMd.update(mSalt);
+ mBytesDigestedSinceReset = 0;
+ }
+ }
+ }
+
+ /** Finish the current digestion if any. */
+ @Override
+ public void finish() throws DigestException {
+ if (mBytesDigestedSinceReset == 0) {
+ return;
+ }
+ mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+ mOutput.put(mDigestBuffer);
+ }
+
+ private void fillUpLastOutputChunk() {
+ int extra = (int) (BUFFER_SIZE - mOutput.position() % BUFFER_SIZE);
+ if (extra == 0) {
+ return;
+ }
+ mOutput.put(ByteBuffer.allocate(extra));
+ }
+ }
+
+ /**
+ * Digest the source by chunk in the given range. If the last chunk is not a full chunk,
+ * digest the remaining.
+ */
+ private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize)
+ throws IOException, DigestException {
+ long inputRemaining = source.size();
+ long inputOffset = 0;
+ while (inputRemaining > 0) {
+ int size = (int) Math.min(inputRemaining, chunkSize);
+ source.feedIntoDataDigester(digester, inputOffset, size);
+ inputOffset += size;
+ inputRemaining -= size;
+ }
+ }
+
+ // Rationale: 1) 1 MB should fit in memory space on all devices. 2) It is not too granular
+ // thus the syscall overhead is not too big.
+ private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024;
+
+ private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk,
+ SignatureInfo signatureInfo, byte[] salt, ByteBuffer output)
+ throws IOException, NoSuchAlgorithmException, DigestException {
+ BufferedDigester digester = new BufferedDigester(salt, output);
+
+ // 1. Digest from the beginning of the file, until APK Signing Block is reached.
+ consumeByChunk(digester,
+ new MemoryMappedFileDataSource(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset),
+ MMAP_REGION_SIZE_BYTES);
+
+ // 2. Skip APK Signing Block and continue digesting, until the Central Directory offset
+ // field in EoCD is reached.
+ long eocdCdOffsetFieldPosition =
+ signatureInfo.eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET;
+ consumeByChunk(digester,
+ new MemoryMappedFileDataSource(apk.getFD(), signatureInfo.centralDirOffset,
+ eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset),
+ MMAP_REGION_SIZE_BYTES);
+
+ // 3. Fill up the rest of buffer with 0s.
+ ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate(
+ ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset));
+ alternativeCentralDirOffset.flip();
+ digester.consume(alternativeCentralDirOffset);
+
+ // 4. Read from end of the Central Directory offset field in EoCD to the end of the file.
+ long offsetAfterEocdCdOffsetField =
+ eocdCdOffsetFieldPosition + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ consumeByChunk(digester,
+ new MemoryMappedFileDataSource(apk.getFD(), offsetAfterEocdCdOffsetField,
+ apk.length() - offsetAfterEocdCdOffsetField),
+ MMAP_REGION_SIZE_BYTES);
+ digester.finish();
+
+ // 5. Fill up the rest of buffer with 0s.
+ digester.fillUpLastOutputChunk();
+ }
+
+ private static byte[] generateApkVerityTree(RandomAccessFile apk, SignatureInfo signatureInfo,
+ byte[] salt, int[] levelOffset, ByteBuffer output)
+ throws IOException, NoSuchAlgorithmException, DigestException {
+ // 1. Digest the apk to generate the leaf level hashes.
+ generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
+ levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
+
+ // 2. Digest the lower level hashes bottom up.
+ for (int level = levelOffset.length - 3; level >= 0; level--) {
+ ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]);
+ ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]);
+
+ DataSource source = new ByteBufferDataSource(inputBuffer);
+ BufferedDigester digester = new BufferedDigester(salt, outputBuffer);
+ consumeByChunk(digester, source, CHUNK_SIZE_BYTES);
+ digester.finish();
+
+ digester.fillUpLastOutputChunk();
+ }
+
+ // 3. Digest the first block (i.e. first level) to generate the root hash.
+ byte[] rootHash = new byte[DIGEST_SIZE_BYTES];
+ BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash));
+ digester.consume(slice(output, 0, CHUNK_SIZE_BYTES));
+ digester.finish();
+ return rootHash;
+ }
+
+ private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) {
+ if (salt.length != 8) {
+ throw new IllegalArgumentException("salt is not 8 bytes long");
+ }
+
+ ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ // TODO(b/30972906): insert a reference when there is a public one.
+ buffer.put("TrueBrew".getBytes()); // magic
+ buffer.put((byte) 1); // major version
+ buffer.put((byte) 0); // minor version
+ buffer.put((byte) 12); // log2(block-size) == log2(4096)
+ buffer.put((byte) 7); // log2(leaves-per-node) == log2(block-size / digest-size)
+ // == log2(4096 / 32)
+ buffer.putShort((short) 1); // meta algorithm, 1: SHA-256 FIXME finalize constant
+ buffer.putShort((short) 1); // data algorithm, 1: SHA-256 FIXME finalize constant
+ buffer.putInt(0x1); // flags, 0x1: has extension, FIXME also hide it
+ buffer.putInt(0); // reserved
+ buffer.putLong(fileSize); // original i_size
+ buffer.put(salt); // salt (8 bytes)
+
+ // TODO(b/30972906): Add extension.
+
+ buffer.rewind();
+ return buffer;
+ }
+
+ private static ByteBuffer generateFsverityExtensions() {
+ return ByteBuffer.allocate(64); // TODO(b/30972906): implement this.
+ }
+
+ /**
+ * Returns an array of summed area table of level size in the verity tree. In other words, the
+ * returned array is offset of each level in the verity tree file format, plus an additional
+ * offset of the next non-existing level (i.e. end of the last level + 1). Thus the array size
+ * is level + 1. Thus, the returned array is guarantee to have at least 2 elements.
+ */
+ private static int[] calculateVerityLevelOffset(long fileSize) {
+ ArrayList<Long> levelSize = new ArrayList<>();
+ while (true) {
+ long levelDigestSize = divideRoundup(fileSize, CHUNK_SIZE_BYTES) * DIGEST_SIZE_BYTES;
+ long chunksSize = CHUNK_SIZE_BYTES * divideRoundup(levelDigestSize, CHUNK_SIZE_BYTES);
+ levelSize.add(chunksSize);
+ if (levelDigestSize <= CHUNK_SIZE_BYTES) {
+ break;
+ }
+ fileSize = levelDigestSize;
+ }
+
+ // Reverse and convert to summed area table.
+ int[] levelOffset = new int[levelSize.size() + 1];
+ levelOffset[0] = 0;
+ for (int i = 0; i < levelSize.size(); i++) {
+ // We don't support verity tree if it is larger then Integer.MAX_VALUE.
+ levelOffset[i + 1] = levelOffset[i]
+ + Math.toIntExact(levelSize.get(levelSize.size() - i - 1));
+ }
+ return levelOffset;
+ }
+
+ private static void assertSigningBlockAlignedAndHasFullPages(SignatureInfo signatureInfo) {
+ if (signatureInfo.apkSigningBlockOffset % CHUNK_SIZE_BYTES != 0) {
+ throw new IllegalArgumentException(
+ "APK Signing Block does not start at the page boundary: "
+ + signatureInfo.apkSigningBlockOffset);
+ }
+
+ if ((signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset)
+ % CHUNK_SIZE_BYTES != 0) {
+ throw new IllegalArgumentException(
+ "Size of APK Signing Block is not a multiple of 4096: "
+ + (signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset));
+ }
+ }
+
+ /** Returns a slice of the buffer which shares content with the provided buffer. */
+ private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) {
+ ByteBuffer b = buffer.duplicate();
+ b.position(0); // to ensure position <= limit invariant.
+ b.limit(end);
+ b.position(begin);
+ return b.slice();
+ }
+
+ /** Divides a number and round up to the closest integer. */
+ private static long divideRoundup(long dividend, long divisor) {
+ return (dividend + divisor - 1) / divisor;
+ }
+}
diff --git a/android/util/apk/ByteBufferDataSource.java b/android/util/apk/ByteBufferDataSource.java
new file mode 100644
index 00000000..3976568a
--- /dev/null
+++ b/android/util/apk/ByteBufferDataSource.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a {@link ByteBuffer}.
+ */
+class ByteBufferDataSource implements DataSource {
+ /**
+ * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
+ * The buffer's position is 0 and limit is equal to capacity.
+ */
+ private final ByteBuffer mBuf;
+
+ ByteBufferDataSource(ByteBuffer buf) {
+ // Defensive copy, to avoid changes to mBuf being visible in buf, and to ensure position is
+ // 0 and limit == capacity.
+ mBuf = buf.slice();
+ }
+
+ @Override
+ public long size() {
+ return mBuf.capacity();
+ }
+
+ @Override
+ public void feedIntoDataDigester(DataDigester md, long offset, int size)
+ throws IOException, DigestException {
+ // There's no way to tell MessageDigest to read data from ByteBuffer from a position
+ // other than the buffer's current position. We thus need to change the buffer's
+ // position to match the requested offset.
+ //
+ // In the future, it may be necessary to compute digests of multiple regions in
+ // parallel. Given that digest computation is a slow operation, we enable multiple
+ // such requests to be fulfilled by this instance. This is achieved by serially
+ // creating a new ByteBuffer corresponding to the requested data range and then,
+ // potentially concurrently, feeding these buffers into MessageDigest instances.
+ ByteBuffer region;
+ synchronized (mBuf) {
+ mBuf.position(0);
+ mBuf.limit((int) offset + size);
+ mBuf.position((int) offset);
+ region = mBuf.slice();
+ }
+
+ md.consume(region);
+ }
+}
diff --git a/android/util/apk/ByteBufferFactory.java b/android/util/apk/ByteBufferFactory.java
new file mode 100644
index 00000000..7a998822
--- /dev/null
+++ b/android/util/apk/ByteBufferFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Provider of {@link ByteBuffer} instances.
+ * @hide
+ */
+public interface ByteBufferFactory {
+ /** Initiates a {@link ByteBuffer} with the given size. */
+ ByteBuffer create(int capacity);
+}
diff --git a/android/util/apk/DataDigester.java b/android/util/apk/DataDigester.java
new file mode 100644
index 00000000..278be803
--- /dev/null
+++ b/android/util/apk/DataDigester.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+interface DataDigester {
+ /** Consumes the {@link ByteBuffer}. */
+ void consume(ByteBuffer buffer) throws DigestException;
+
+ /** Finishes the digestion. Must be called after the last {@link #consume(ByteBuffer)}. */
+ void finish() throws DigestException;
+}
diff --git a/android/util/apk/DataSource.java b/android/util/apk/DataSource.java
new file mode 100644
index 00000000..82f3800a
--- /dev/null
+++ b/android/util/apk/DataSource.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.io.IOException;
+import java.security.DigestException;
+
+/** Source of data to be digested. */
+interface DataSource {
+
+ /**
+ * Returns the size (in bytes) of the data offered by this source.
+ */
+ long size();
+
+ /**
+ * Feeds the specified region of this source's data into the provided digester.
+ *
+ * @param offset offset of the region inside this data source.
+ * @param size size (in bytes) of the region.
+ */
+ void feedIntoDataDigester(DataDigester md, long offset, int size)
+ throws IOException, DigestException;
+}
diff --git a/android/util/apk/MemoryMappedFileDataSource.java b/android/util/apk/MemoryMappedFileDataSource.java
new file mode 100644
index 00000000..8d2b1e32
--- /dev/null
+++ b/android/util/apk/MemoryMappedFileDataSource.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.DirectByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
+ * of the file.
+ */
+class MemoryMappedFileDataSource implements DataSource {
+ private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
+
+ private final FileDescriptor mFd;
+ private final long mFilePosition;
+ private final long mSize;
+
+ /**
+ * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
+ *
+ * @param position start position of the region in the file.
+ * @param size size (in bytes) of the region.
+ */
+ MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
+ mFd = fd;
+ mFilePosition = position;
+ mSize = size;
+ }
+
+ @Override
+ public long size() {
+ return mSize;
+ }
+
+ @Override
+ public void feedIntoDataDigester(DataDigester md, long offset, int size)
+ throws IOException, DigestException {
+ // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
+ // method was settled on a straightforward mmap with prefaulting.
+ //
+ // This method is not using FileChannel.map API because that API does not offset a way
+ // to "prefault" the resulting memory pages. Without prefaulting, performance is about
+ // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
+ // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
+ // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
+ // time, which is not compensated for by faster reads.
+
+ // We mmap the smallest region of the file containing the requested data. mmap requires
+ // that the start offset in the file must be a multiple of memory page size. We thus may
+ // need to mmap from an offset less than the requested offset.
+ long filePosition = mFilePosition + offset;
+ long mmapFilePosition =
+ (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
+ int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
+ long mmapRegionSize = size + dataStartOffsetInMmapRegion;
+ long mmapPtr = 0;
+ try {
+ mmapPtr = Os.mmap(
+ 0, // let the OS choose the start address of the region in memory
+ mmapRegionSize,
+ OsConstants.PROT_READ,
+ OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
+ mFd,
+ mmapFilePosition);
+ ByteBuffer buf = new DirectByteBuffer(
+ size,
+ mmapPtr + dataStartOffsetInMmapRegion,
+ mFd, // not really needed, but just in case
+ null, // no need to clean up -- it's taken care of by the finally block
+ true // read only buffer
+ );
+ md.consume(buf);
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
+ } finally {
+ if (mmapPtr != 0) {
+ try {
+ Os.munmap(mmapPtr, mmapRegionSize);
+ } catch (ErrnoException ignored) { }
+ }
+ }
+ }
+}
diff --git a/android/util/apk/SignatureInfo.java b/android/util/apk/SignatureInfo.java
new file mode 100644
index 00000000..8e1233af
--- /dev/null
+++ b/android/util/apk/SignatureInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.nio.ByteBuffer;
+
+/**
+ * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
+ * contained in the block against the file.
+ */
+class SignatureInfo {
+ /** Contents of APK Signature Scheme v2 block. */
+ public final ByteBuffer signatureBlock;
+
+ /** Position of the APK Signing Block in the file. */
+ public final long apkSigningBlockOffset;
+
+ /** Position of the ZIP Central Directory in the file. */
+ public final long centralDirOffset;
+
+ /** Position of the ZIP End of Central Directory (EoCD) in the file. */
+ public final long eocdOffset;
+
+ /** Contents of ZIP End of Central Directory (EoCD) of the file. */
+ public final ByteBuffer eocd;
+
+ SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset,
+ long eocdOffset, ByteBuffer eocd) {
+ this.signatureBlock = signatureBlock;
+ this.apkSigningBlockOffset = apkSigningBlockOffset;
+ this.centralDirOffset = centralDirOffset;
+ this.eocdOffset = eocdOffset;
+ this.eocd = eocd;
+ }
+}
diff --git a/android/util/proto/ProtoOutputStream.java b/android/util/proto/ProtoOutputStream.java
index 43a97897..a94806a0 100644
--- a/android/util/proto/ProtoOutputStream.java
+++ b/android/util/proto/ProtoOutputStream.java
@@ -127,42 +127,48 @@ public final class ProtoOutputStream {
public static final long FIELD_TYPE_UNKNOWN = 0;
+ /**
+ * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+ * so no extra mapping needs to be maintained in this case.
+ */
public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+// public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+ public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
private static final String[] FIELD_TYPE_NAMES = new String[] {
"Double",
"Float",
- "Int32",
"Int64",
- "UInt32",
"UInt64",
- "SInt32",
- "SInt64",
- "Fixed32",
+ "Int32",
"Fixed64",
- "SFixed32",
- "SFixed64",
+ "Fixed32",
"Bool",
"String",
+ "Group", // This field is deprecated but reserved here for indexing.
+ "Message",
"Bytes",
+ "UInt32",
"Enum",
- "Object",
+ "SFixed32",
+ "SFixed64",
+ "SInt32",
+ "SInt64",
};
//
@@ -867,21 +873,21 @@ public final class ProtoOutputStream {
assertNotCompacted();
final int id = (int)fieldId;
- switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
// bytes
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeBytesImpl(id, val);
break;
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedBytesImpl(id, val);
break;
// Object
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeObjectImpl(id, val);
break;
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedObjectImpl(id, val);
break;
// nothing else allowed
@@ -899,7 +905,7 @@ public final class ProtoOutputStream {
assertNotCompacted();
final int id = (int)fieldId;
- if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) {
+ if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) {
final long count = fieldId & FIELD_COUNT_MASK;
if (count == FIELD_COUNT_SINGLE) {
return startObjectImpl(id, false);
@@ -2091,7 +2097,7 @@ public final class ProtoOutputStream {
@Deprecated
public long startObject(long fieldId) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
return startObjectImpl(id, false);
}
@@ -2119,7 +2125,7 @@ public final class ProtoOutputStream {
@Deprecated
public long startRepeatedObject(long fieldId) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
return startObjectImpl(id, true);
}
@@ -2217,7 +2223,7 @@ public final class ProtoOutputStream {
@Deprecated
public void writeObject(long fieldId, byte[] value) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
writeObjectImpl(id, value);
}
@@ -2237,7 +2243,7 @@ public final class ProtoOutputStream {
@Deprecated
public void writeRepeatedObject(long fieldId, byte[] value) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
writeRepeatedObjectImpl(id, value);
}
@@ -2296,7 +2302,7 @@ public final class ProtoOutputStream {
final String typeString = getFieldTypeString(fieldType);
if (typeString != null && countString != null) {
final StringBuilder sb = new StringBuilder();
- if (expectedType == FIELD_TYPE_OBJECT) {
+ if (expectedType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
@@ -2306,7 +2312,7 @@ public final class ProtoOutputStream {
sb.append(" called for field ");
sb.append((int)fieldId);
sb.append(" which should be used with ");
- if (fieldType == FIELD_TYPE_OBJECT) {
+ if (fieldType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
@@ -2321,7 +2327,7 @@ public final class ProtoOutputStream {
throw new IllegalArgumentException(sb.toString());
} else {
final StringBuilder sb = new StringBuilder();
- if (expectedType == FIELD_TYPE_OBJECT) {
+ if (expectedType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
diff --git a/android/view/Display.java b/android/view/Display.java
index e7c3f92d..6a44cdb9 100644
--- a/android/view/Display.java
+++ b/android/view/Display.java
@@ -294,11 +294,10 @@ public final class Display {
/**
* Display state: The display is dozing in a suspended low power state; it is still
- * on but is optimized for showing static system-provided content while the device
- * is non-interactive. This mode may be used to conserve even more power by allowing
- * the hardware to stop applying frame buffer updates from the graphics subsystem or
- * to take over the display and manage it autonomously to implement low power always-on
- * display functionality.
+ * on but the CPU is not updating it. This may be used in one of two ways: to show
+ * static system-provided content while the device is non-interactive, or to allow
+ * a "Sidekick" compute resource to update the display. For this reason, the
+ * CPU must not control the display in this mode.
*
* @see #getState
* @see android.os.PowerManager#isInteractive
@@ -313,6 +312,18 @@ public final class Display {
*/
public static final int STATE_VR = 5;
+ /**
+ * Display state: The display is in a suspended full power state; it is still
+ * on but the CPU is not updating it. This may be used in one of two ways: to show
+ * static system-provided content while the device is non-interactive, or to allow
+ * a "Sidekick" compute resource to update the display. For this reason, the
+ * CPU must not control the display in this mode.
+ *
+ * @see #getState
+ * @see android.os.PowerManager#isInteractive
+ */
+ public static final int STATE_ON_SUSPEND = 6;
+
/* The color mode constants defined below must be kept in sync with the ones in
* system/core/include/system/graphics-base.h */
@@ -994,7 +1005,7 @@ public final class Display {
* Gets the state of the display, such as whether it is on or off.
*
* @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
- * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, or
+ * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, {@link #STATE_ON_SUSPEND}, or
* {@link #STATE_UNKNOWN}.
*/
public int getState() {
@@ -1113,6 +1124,8 @@ public final class Display {
return "DOZE_SUSPEND";
case STATE_VR:
return "VR";
+ case STATE_ON_SUSPEND:
+ return "ON_SUSPEND";
default:
return Integer.toString(state);
}
@@ -1120,11 +1133,11 @@ public final class Display {
/**
* Returns true if display updates may be suspended while in the specified
- * display power state.
+ * display power state. In SUSPEND states, updates are absolutely forbidden.
* @hide
*/
public static boolean isSuspendedState(int state) {
- return state == STATE_OFF || state == STATE_DOZE_SUSPEND;
+ return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND;
}
/**
diff --git a/android/view/DisplayFrames.java b/android/view/DisplayFrames.java
new file mode 100644
index 00000000..e6861d83
--- /dev/null
+++ b/android/view/DisplayFrames.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+
+/**
+ * Container class for all the display frames that affect how we do window layout on a display.
+ * @hide
+ */
+public class DisplayFrames {
+ public final int mDisplayId;
+
+ /**
+ * The current size of the screen; really; extends into the overscan area of the screen and
+ * doesn't account for any system elements like the status bar.
+ */
+ public final Rect mOverscan = new Rect();
+
+ /**
+ * The current visible size of the screen; really; (ir)regardless of whether the status bar can
+ * be hidden but not extending into the overscan area.
+ */
+ public final Rect mUnrestricted = new Rect();
+
+ /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */
+ public final Rect mRestrictedOverscan = new Rect();
+
+ /**
+ * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar
+ * can't be hidden; in that case it effectively carves out that area of the display from all
+ * other windows.
+ */
+ public final Rect mRestricted = new Rect();
+
+ /**
+ * During layout, the current screen borders accounting for any currently visible system UI
+ * elements.
+ */
+ public final Rect mSystem = new Rect();
+
+ /** For applications requesting stable content insets, these are them. */
+ public final Rect mStable = new Rect();
+
+ /**
+ * For applications requesting stable content insets but have also set the fullscreen window
+ * flag, these are the stable dimensions without the status bar.
+ */
+ public final Rect mStableFullscreen = new Rect();
+
+ /**
+ * During layout, the current screen borders with all outer decoration (status bar, input method
+ * dock) accounted for.
+ */
+ public final Rect mCurrent = new Rect();
+
+ /**
+ * During layout, the frame in which content should be displayed to the user, accounting for all
+ * screen decoration except for any space they deem as available for other content. This is
+ * usually the same as mCurrent*, but may be larger if the screen decor has supplied content
+ * insets.
+ */
+ public final Rect mContent = new Rect();
+
+ /**
+ * During layout, the frame in which voice content should be displayed to the user, accounting
+ * for all screen decoration except for any space they deem as available for other content.
+ */
+ public final Rect mVoiceContent = new Rect();
+
+ /** During layout, the current screen borders along which input method windows are placed. */
+ public final Rect mDock = new Rect();
+
+ private final Rect mDisplayInfoOverscan = new Rect();
+ private final Rect mRotatedDisplayInfoOverscan = new Rect();
+ public int mDisplayWidth;
+ public int mDisplayHeight;
+
+ public int mRotation;
+
+ public DisplayFrames(int displayId, DisplayInfo info) {
+ mDisplayId = displayId;
+ onDisplayInfoUpdated(info);
+ }
+
+ public void onDisplayInfoUpdated(DisplayInfo info) {
+ mDisplayWidth = info.logicalWidth;
+ mDisplayHeight = info.logicalHeight;
+ mRotation = info.rotation;
+ mDisplayInfoOverscan.set(
+ info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
+ }
+
+ public void onBeginLayout() {
+ switch (mRotation) {
+ case ROTATION_90:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.right;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.left;
+ break;
+ case ROTATION_180:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.right;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.left;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.top;
+ break;
+ case ROTATION_270:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.left;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.top;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.right;
+ break;
+ default:
+ mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan);
+ break;
+ }
+
+ mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight);
+ mOverscan.set(mRestrictedOverscan);
+ mSystem.set(mRestrictedOverscan);
+ mUnrestricted.set(mRotatedDisplayInfoOverscan);
+ mUnrestricted.right = mDisplayWidth - mUnrestricted.right;
+ mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom;
+ mRestricted.set(mUnrestricted);
+ mDock.set(mUnrestricted);
+ mContent.set(mUnrestricted);
+ mVoiceContent.set(mUnrestricted);
+ mStable.set(mUnrestricted);
+ mStableFullscreen.set(mUnrestricted);
+ mCurrent.set(mUnrestricted);
+
+ }
+
+ public int getInputMethodWindowVisibleHeight() {
+ return mDock.bottom - mCurrent.bottom;
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ mStable.writeToProto(proto, STABLE_BOUNDS);
+ proto.end(token);
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
+ + " r=" + mRotation);
+ final String myPrefix = prefix + " ";
+ dumpFrame(mStable, "mStable", myPrefix, pw);
+ dumpFrame(mStableFullscreen, "mStableFullscreen", myPrefix, pw);
+ dumpFrame(mDock, "mDock", myPrefix, pw);
+ dumpFrame(mCurrent, "mCurrent", myPrefix, pw);
+ dumpFrame(mSystem, "mSystem", myPrefix, pw);
+ dumpFrame(mContent, "mContent", myPrefix, pw);
+ dumpFrame(mVoiceContent, "mVoiceContent", myPrefix, pw);
+ dumpFrame(mOverscan, "mOverscan", myPrefix, pw);
+ dumpFrame(mRestrictedOverscan, "mRestrictedOverscan", myPrefix, pw);
+ dumpFrame(mRestricted, "mRestricted", myPrefix, pw);
+ dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
+ dumpFrame(mDisplayInfoOverscan, "mDisplayInfoOverscan", myPrefix, pw);
+ dumpFrame(mRotatedDisplayInfoOverscan, "mRotatedDisplayInfoOverscan", myPrefix, pw);
+ }
+
+ private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
+ pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
+ }
+}
diff --git a/android/view/FocusFinder.java b/android/view/FocusFinder.java
index 74555de5..713cfb48 100644
--- a/android/view/FocusFinder.java
+++ b/android/view/FocusFinder.java
@@ -530,7 +530,7 @@ public class FocusFinder {
* axis distances. Warning: this fudge factor is finely tuned, be sure to
* run all focus tests if you dare tweak it.
*/
- int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+ long getWeightedDistanceFor(long majorAxisDistance, long minorAxisDistance) {
return 13 * majorAxisDistance * majorAxisDistance
+ minorAxisDistance * minorAxisDistance;
}
diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java
index b34dfbfe..6c006cae 100644
--- a/android/view/IWindowManagerImpl.java
+++ b/android/view/IWindowManagerImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import android.app.IAssistDataReceiver;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
@@ -29,7 +30,6 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.DisplayMetrics;
-import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
@@ -261,7 +261,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
+ public boolean requestAssistScreenshot(IAssistDataReceiver receiver)
throws RemoteException {
// TODO Auto-generated method stub
return false;
@@ -507,7 +507,7 @@ public class IWindowManagerImpl implements IWindowManager {
throws RemoteException {}
@Override
- public void createInputConsumer(String name, InputChannel inputChannel)
+ public void createInputConsumer(IBinder token, String name, InputChannel inputChannel)
throws RemoteException {}
@Override
diff --git a/android/view/NotificationHeaderView.java b/android/view/NotificationHeaderView.java
index 58045602..ab0b3eec 100644
--- a/android/view/NotificationHeaderView.java
+++ b/android/view/NotificationHeaderView.java
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -43,6 +44,7 @@ public class NotificationHeaderView extends ViewGroup {
public static final int NO_COLOR = Notification.COLOR_INVALID;
private final int mChildMinWidth;
private final int mContentEndMargin;
+ private final int mGravity;
private View mAppName;
private View mHeaderText;
private OnClickListener mExpandClickListener;
@@ -50,7 +52,6 @@ public class NotificationHeaderView extends ViewGroup {
private ImageView mExpandButton;
private CachingIconView mIcon;
private View mProfileBadge;
- private View mInfo;
private int mIconColor;
private int mOriginalNotificationColor;
private boolean mExpanded;
@@ -61,6 +62,7 @@ public class NotificationHeaderView extends ViewGroup {
private boolean mEntireHeaderClickable;
private boolean mExpandOnlyOnButton;
private boolean mAcceptAllTouches;
+ private int mTotalWidth;
ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
@@ -92,6 +94,11 @@ public class NotificationHeaderView extends ViewGroup {
mHeaderBackgroundHeight = res.getDimensionPixelSize(
R.dimen.notification_header_background_height);
mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
+
+ int[] attrIds = { android.R.attr.gravity };
+ TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
+ mGravity = ta.getInt(0, 0);
+ ta.recycle();
}
@Override
@@ -146,6 +153,7 @@ public class NotificationHeaderView extends ViewGroup {
mHeaderText.measure(childWidthSpec, wrapContentHeightSpec);
}
}
+ mTotalWidth = Math.min(totalWidth, givenWidth);
setMeasuredDimension(givenWidth, givenHeight);
}
@@ -153,6 +161,10 @@ public class NotificationHeaderView extends ViewGroup {
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = getPaddingStart();
int end = getMeasuredWidth();
+ final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
+ if (centerAligned) {
+ left += getMeasuredWidth() / 2 - mTotalWidth / 2;
+ }
int childCount = getChildCount();
int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
for (int i = 0; i < childCount; i++) {
diff --git a/android/view/RenderNode.java b/android/view/RenderNode.java
index ea6e63c3..50701518 100644
--- a/android/view/RenderNode.java
+++ b/android/view/RenderNode.java
@@ -353,6 +353,11 @@ public class RenderNode {
return nHasShadow(mNativeRenderNode);
}
+ /** setShadowColor */
+ public boolean setShadowColor(int color) {
+ return nSetShadowColor(mNativeRenderNode, color);
+ }
+
/**
* Enables or disables clipping to the outline.
*
@@ -910,6 +915,8 @@ public class RenderNode {
@CriticalNative
private static native boolean nHasShadow(long renderNode);
@CriticalNative
+ private static native boolean nSetShadowColor(long renderNode, int color);
+ @CriticalNative
private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
@CriticalNative
private static native boolean nSetRevealClip(long renderNode,
diff --git a/android/view/RenderNodeAnimator.java b/android/view/RenderNodeAnimator.java
index 95150409..c4a71601 100644
--- a/android/view/RenderNodeAnimator.java
+++ b/android/view/RenderNodeAnimator.java
@@ -19,7 +19,6 @@ package android.view;
import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.util.SparseIntArray;
@@ -281,12 +280,9 @@ public class RenderNodeAnimator extends Animator {
setTarget(mViewTarget.mRenderNode);
}
- public void setTarget(Canvas canvas) {
- if (!(canvas instanceof DisplayListCanvas)) {
- throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
- }
- final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
- setTarget(recordingCanvas.mNode);
+ /** Sets the animation target to the owning view of the DisplayListCanvas */
+ public void setTarget(DisplayListCanvas canvas) {
+ setTarget(canvas.mNode);
}
private void setTarget(RenderNode node) {
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 6f8315ae..5641009c 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -295,6 +295,12 @@ public class SurfaceControl {
public static final int POWER_MODE_DOZE_SUSPEND = 3;
/**
+ * Display power mode on: used while putting the screen into a suspended
+ * full power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ */
+ public static final int POWER_MODE_ON_SUSPEND = 4;
+
+ /**
* A value for windowType used to indicate that the window should be omitted from screenshots
* and display mirroring. A temporary workaround until we express such things with
* the hierarchy.
@@ -1206,56 +1212,65 @@ public class SurfaceControl {
}
public Transaction show(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
return this;
}
public Transaction hide(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
return this;
}
public Transaction setPosition(SurfaceControl sc, float x, float y) {
+ sc.checkNotReleased();
nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
return this;
}
public Transaction setSize(SurfaceControl sc, int w, int h) {
- nativeSetSize(mNativeObject, sc.mNativeObject,
- w, h);
+ sc.checkNotReleased();
+ nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
return this;
}
public Transaction setLayer(SurfaceControl sc, int z) {
+ sc.checkNotReleased();
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
}
public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
+ sc.checkNotReleased();
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
relativeTo.getHandle(), z);
return this;
}
public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+ sc.checkNotReleased();
nativeSetTransparentRegionHint(mNativeObject,
sc.mNativeObject, transparentRegion);
return this;
}
public Transaction setAlpha(SurfaceControl sc, float alpha) {
+ sc.checkNotReleased();
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
}
public Transaction setMatrix(SurfaceControl sc,
float dsdx, float dtdx, float dtdy, float dsdy) {
+ sc.checkNotReleased();
nativeSetMatrix(mNativeObject, sc.mNativeObject,
dsdx, dtdx, dtdy, dsdy);
return this;
}
public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+ sc.checkNotReleased();
if (crop != null) {
nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
crop.left, crop.top, crop.right, crop.bottom);
@@ -1267,6 +1282,7 @@ public class SurfaceControl {
}
public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
+ sc.checkNotReleased();
if (crop != null) {
nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
crop.left, crop.top, crop.right, crop.bottom);
@@ -1278,40 +1294,48 @@ public class SurfaceControl {
}
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+ sc.checkNotReleased();
nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
return this;
}
- public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
+ public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
+ long frameNumber) {
+ sc.checkNotReleased();
nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
return this;
}
public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
long frameNumber) {
+ sc.checkNotReleased();
nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
barrierSurface.mNativeObject, frameNumber);
return this;
}
public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
+ sc.checkNotReleased();
nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
return this;
}
/** Re-parents a specific child layer to a new parent */
public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+ sc.checkNotReleased();
nativeReparent(mNativeObject, sc.mNativeObject,
newParentHandle);
return this;
}
public Transaction detachChildren(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSeverChildren(mNativeObject, sc.mNativeObject);
return this;
}
public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
+ sc.checkNotReleased();
nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
overrideScalingMode);
return this;
@@ -1322,6 +1346,7 @@ public class SurfaceControl {
* @param color A float array with three values to represent r, g, b in range [0..1]
*/
public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+ sc.checkNotReleased();
nativeSetColor(mNativeObject, sc.mNativeObject, color);
return this;
}
@@ -1334,6 +1359,7 @@ public class SurfaceControl {
* (at which point the geometry influencing aspects of this transaction will then occur)
*/
public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
return this;
}
@@ -1343,6 +1369,7 @@ public class SurfaceControl {
* Surface with the {@link #SECURE} flag.
*/
Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+ sc.checkNotReleased();
if (isSecure) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
} else {
@@ -1356,6 +1383,7 @@ public class SurfaceControl {
* Surface with the {@link #OPAQUE} flag.
*/
public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+ sc.checkNotReleased();
if (isOpaque) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
} else {
diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java
index 2166f6e4..7c76bab2 100644
--- a/android/view/ThreadedRenderer.java
+++ b/android/view/ThreadedRenderer.java
@@ -70,6 +70,7 @@ public final class ThreadedRenderer {
* Name of the file that holds the shaders cache.
*/
private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
+ private static final String CACHE_PATH_SKIASHADERS = "com.android.skia.shaders_cache";
/**
* System property used to enable or disable threaded rendering profiling.
@@ -272,7 +273,9 @@ public final class ThreadedRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
- ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ ThreadedRenderer.setupShadersDiskCache(
+ new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath(),
+ new File(cacheDir, CACHE_PATH_SKIASHADERS).getAbsolutePath());
}
/**
@@ -1007,7 +1010,7 @@ public final class ThreadedRenderer {
/** Not actually public - internal use only. This doc to make lint happy */
public static native void disableVsync();
- static native void setupShadersDiskCache(String cacheFile);
+ static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile);
private static native void nRotateProcessStatsBuffer();
private static native void nSetProcessStatsBuffer(int fd);
diff --git a/android/view/View.java b/android/view/View.java
index c043dcac..be09fe86 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -14218,6 +14218,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setScaleX(float scaleX) {
if (scaleX != getScaleX()) {
+ requireIsFinite(scaleX, "scaleX");
invalidateViewProperty(true, false);
mRenderNode.setScaleX(scaleX);
invalidateViewProperty(false, true);
@@ -14254,6 +14255,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setScaleY(float scaleY) {
if (scaleY != getScaleY()) {
+ requireIsFinite(scaleY, "scaleY");
invalidateViewProperty(true, false);
mRenderNode.setScaleY(scaleY);
invalidateViewProperty(false, true);
@@ -14803,6 +14805,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ private static void requireIsFinite(float transform, String propertyName) {
+ if (Float.isNaN(transform)) {
+ throw new IllegalArgumentException("Cannot set '" + propertyName + "' to Float.NaN");
+ }
+ if (Float.isInfinite(transform)) {
+ throw new IllegalArgumentException("Cannot set '" + propertyName + "' to infinity");
+ }
+ }
+
/**
* The visual x position of this view, in pixels. This is equivalent to the
* {@link #setTranslationX(float) translationX} property plus the current
@@ -14889,6 +14900,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setElevation(float elevation) {
if (elevation != getElevation()) {
+ requireIsFinite(elevation, "elevation");
invalidateViewProperty(true, false);
mRenderNode.setElevation(elevation);
invalidateViewProperty(false, true);
@@ -14981,6 +14993,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setTranslationZ(float translationZ) {
if (translationZ != getTranslationZ()) {
+ requireIsFinite(translationZ, "translationZ");
invalidateViewProperty(true, false);
mRenderNode.setTranslationZ(translationZ);
invalidateViewProperty(false, true);
@@ -15169,6 +15182,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return mRenderNode.hasShadow();
}
+ /**
+ * @hide
+ */
+ public void setShadowColor(@ColorInt int color) {
+ if (mRenderNode.setShadowColor(color)) {
+ invalidateViewProperty(true, true);
+ }
+ }
+
/** @hide */
public void setRevealClip(boolean shouldClip, float x, float y, float radius) {
diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java
index 45008627..c44c8dda 100644
--- a/android/view/ViewConfiguration.java
+++ b/android/view/ViewConfiguration.java
@@ -92,7 +92,7 @@ public class ViewConfiguration {
* Defines the duration in milliseconds a user needs to hold down the
* appropriate button to enable the accessibility shortcut once it's configured.
*/
- private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+ private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 37829f0b..e30496fb 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -513,7 +513,7 @@ public final class ViewRootImpl implements ViewParent,
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
- sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
+ sAlwaysAssignFocus = true;
sCompatibilityDone = true;
}
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index c29a1daf..eb5fc92e 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -1422,7 +1422,7 @@ public interface WindowManager extends ViewManager {
* this window is visible.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
/**
@@ -1443,6 +1443,15 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN = 0x00200000;
/**
+ * Flag to indicate that this window should be considered a screen decoration similar to the
+ * nav bar and status bar. This will cause this window to affect the window insets reported
+ * to other windows when it is visible.
+ * @hide
+ */
+ @RequiresPermission(permission.STATUS_BAR_SERVICE)
+ public static final int PRIVATE_FLAG_IS_SCREEN_DECOR = 0x00400000;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
@@ -1526,7 +1535,11 @@ public interface WindowManager extends ViewManager {
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
- name = "ACQUIRES_SLEEP_TOKEN")
+ name = "ACQUIRES_SLEEP_TOKEN"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_IS_SCREEN_DECOR,
+ equals = PRIVATE_FLAG_IS_SCREEN_DECOR,
+ name = "IS_SCREEN_DECOR")
})
@TestApi
public int privateFlags;
diff --git a/android/view/WindowManagerGlobal.java b/android/view/WindowManagerGlobal.java
index c7e8dee3..cca66d6b 100644
--- a/android/view/WindowManagerGlobal.java
+++ b/android/view/WindowManagerGlobal.java
@@ -605,9 +605,10 @@ public final class WindowManagerGlobal {
public void setStoppedState(IBinder token, boolean stopped) {
synchronized (mLock) {
int count = mViews.size();
- for (int i = 0; i < count; i++) {
+ for (int i = count - 1; i >= 0; i--) {
if (token == null || mParams.get(i).token == token) {
ViewRootImpl root = mRoots.get(i);
+ // Client might remove the view by "stopped" event.
root.setWindowStopped(stopped);
}
}
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
index 69cc1002..cd1b1908 100644
--- a/android/view/WindowManagerInternal.java
+++ b/android/view/WindowManagerInternal.java
@@ -18,6 +18,7 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ClipData;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
@@ -140,6 +141,30 @@ public abstract class WindowManagerInternal {
}
/**
+ * An interface to customize drag and drop behaviors.
+ */
+ public interface IDragDropCallback {
+ /**
+ * Called when drag operation is started.
+ */
+ default boolean performDrag(IWindow window, IBinder dragToken,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ return true;
+ }
+
+ /**
+ * Called when drop result is reported.
+ */
+ default void reportDropResult(IWindow window, boolean consumed) {}
+
+ /**
+ * Called when drag operation is cancelled.
+ */
+ default void cancelDragAndDrop(IBinder dragToken) {}
+ }
+
+ /**
* Request that the window manager call
* {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
* within a surface transaction at a later time.
@@ -225,9 +250,6 @@ public abstract class WindowManagerInternal {
*/
public abstract boolean isKeyguardLocked();
- /** @return {@code true} if the keyguard is going away. */
- public abstract boolean isKeyguardGoingAway();
-
/**
* @return Whether the keyguard is showing and not occluded.
*/
@@ -354,4 +376,9 @@ public abstract class WindowManagerInternal {
* {@param vr2dDisplayId}.
*/
public abstract void setVr2dDisplayId(int vr2dDisplayId);
+
+ /**
+ * Sets callback to DragDropController.
+ */
+ public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
}
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index 137e551d..534335bf 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -66,7 +66,6 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.app.ActivityManager.StackId;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
@@ -722,12 +721,6 @@ public interface WindowManagerPolicy {
public void setInitialDisplaySize(Display display, int width, int height, int density);
/**
- * Called by window manager to set the overscan region that should be used for the
- * given display.
- */
- public void setDisplayOverscan(Display display, int left, int top, int right, int bottom);
-
- /**
* Check permissions when adding a window.
*
* @param attrs The window's LayoutParams.
@@ -758,7 +751,8 @@ public interface WindowManagerPolicy {
* @param attrs The window layout parameters to be modified. These values
* are modified in-place.
*/
- public void adjustWindowParamsLw(WindowManager.LayoutParams attrs);
+ public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+ boolean hasStatusBarServicePermission);
/**
* After the window manager has computed the current configuration based
@@ -1172,14 +1166,10 @@ public interface WindowManagerPolicy {
/**
* Called when layout of the windows is about to start.
*
- * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}.
- * @param displayWidth The current full width of the screen.
- * @param displayHeight The current full height of the screen.
- * @param displayRotation The current rotation being applied to the base window.
+ * @param displayFrames frames of the display we are doing layout on.
* @param uiMode The current uiMode in configuration.
*/
- public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
- int displayRotation, int uiMode);
+ default void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {}
/**
* Returns the bottom-most layer of the system decor, above which no policy decor should
@@ -1188,37 +1178,28 @@ public interface WindowManagerPolicy {
public int getSystemDecorLayerLw();
/**
- * Return the rectangle of the screen that is available for applications to run in.
- * This will be called immediately after {@link #beginLayoutLw}.
- *
- * @param r The rectangle to be filled with the boundaries available to applications.
- */
- public void getContentRectLw(Rect r);
-
- /**
- * Called for each window attached to the window manager as layout is
- * proceeding. The implementation of this function must take care of
- * setting the window's frame, either here or in finishLayout().
+ * Called for each window attached to the window manager as layout is proceeding. The
+ * implementation of this function must take care of setting the window's frame, either here or
+ * in finishLayout().
*
* @param win The window being positioned.
* @param attached For sub-windows, the window it is attached to; this
* window will already have had layoutWindow() called on it
* so you can use its Rect. Otherwise null.
+ * @param displayFrames The display frames.
*/
- public void layoutWindowLw(WindowState win, WindowState attached);
+ default void layoutWindowLw(
+ WindowState win, WindowState attached, DisplayFrames displayFrames) {}
/**
- * Return the insets for the areas covered by system windows. These values
- * are computed on the most recent layout, so they are not guaranteed to
- * be correct.
+ * Return the insets for the areas covered by system windows. These values are computed on the
+ * most recent layout, so they are not guaranteed to be correct.
*
* @param attrs The LayoutParams of the window.
* @param taskBounds The bounds of the task this window is on or {@code null} if no task is
* associated with the window.
- * @param displayRotation Rotation of the display.
- * @param displayWidth The width of the display.
- * @param displayHeight The height of the display.
+ * @param displayFrames display frames.
* @param outContentInsets The areas covered by system windows, expressed as positive insets.
* @param outStableInsets The areas covered by stable system windows irrespective of their
* current visibility. Expressed as positive insets.
@@ -1226,16 +1207,11 @@ public interface WindowManagerPolicy {
* @return Whether to always consume the navigation bar.
* See {@link #isNavBarForcedShownLw(WindowState)}.
*/
- public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
- int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
- Rect outStableInsets, Rect outOutsets);
-
- /**
- * Called when layout of the windows is finished. After this function has
- * returned, all windows given to layoutWindow() <em>must</em> have had a
- * frame assigned.
- */
- public void finishLayoutLw();
+ default boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
+ DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+ Rect outOutsets) {
+ return false;
+ }
/** Layout state may have changed (so another layout will be performed) */
static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
@@ -1652,11 +1628,6 @@ public interface WindowManagerPolicy {
public void showGlobalActions();
/**
- * @return The current height of the input method window.
- */
- public int getInputMethodWindowVisibleHeightLw();
-
- /**
* Called when the current user changes. Guaranteed to be called before the broadcast
* of the new user id is made to all listeners.
*
diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java
index 19213ca0..c3d6c695 100644
--- a/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/android/view/accessibility/AccessibilityInteractionClient.java
@@ -187,8 +187,11 @@ public final class AccessibilityInteractionClient
Log.i(LOG_TAG, "Window cache miss");
}
final long identityToken = Binder.clearCallingIdentity();
- window = connection.getWindow(accessibilityWindowId);
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ window = connection.getWindow(accessibilityWindowId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (window != null) {
sAccessibilityCache.addWindow(window);
return window;
@@ -225,8 +228,11 @@ public final class AccessibilityInteractionClient
Log.i(LOG_TAG, "Windows cache miss");
}
final long identityToken = Binder.clearCallingIdentity();
- windows = connection.getWindows();
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ windows = connection.getWindows();
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (windows != null) {
sAccessibilityCache.setWindows(windows);
return windows;
@@ -283,10 +289,14 @@ public final class AccessibilityInteractionClient
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
- accessibilityWindowId, accessibilityNodeId, interactionId, this,
- prefetchFlags, Thread.currentThread().getId(), arguments);
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfoByAccessibilityId(
+ accessibilityWindowId, accessibilityNodeId, interactionId, this,
+ prefetchFlags, Thread.currentThread().getId(), arguments);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -333,10 +343,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfosByViewId(
- accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfosByViewId(
+ accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -381,10 +396,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfosByText(
- accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfosByText(
+ accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -428,10 +448,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findFocus(accessibilityWindowId,
- accessibilityNodeId, focusType, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findFocus(accessibilityWindowId,
+ accessibilityNodeId, focusType, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
@@ -472,10 +497,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.focusSearch(accessibilityWindowId,
- accessibilityNodeId, direction, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.focusSearch(accessibilityWindowId,
+ accessibilityNodeId, direction, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
@@ -515,10 +545,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.performAccessibilityAction(
- accessibilityWindowId, accessibilityNodeId, action, arguments,
- interactionId, this, Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.performAccessibilityAction(
+ accessibilityWindowId, accessibilityNodeId, action, arguments,
+ interactionId, this, Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
return getPerformAccessibilityActionResultAndClear(interactionId);
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc576..35f6acba 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -436,8 +436,11 @@ public final class AccessibilityManager {
// client using it is called through Binder from another process. Example: MMS
// app adds a SMS notification and the NotificationManagerService calls this method
long identityToken = Binder.clearCallingIdentity();
- service.sendAccessibilityEvent(event, userId);
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ service.sendAccessibilityEvent(event, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
}
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index e79d201b..9241ec00 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -54,6 +54,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+// TODO: use java.lang.ref.Cleaner once Android supports Java 9
+import sun.misc.Cleaner;
+
/**
* The {@link AutofillManager} provides ways for apps and custom views to integrate with the
* Autofill Framework lifecycle.
@@ -303,6 +306,9 @@ public final class AutofillManager {
private IAutoFillManagerClient mServiceClient;
@GuardedBy("mLock")
+ private Cleaner mServiceClientCleaner;
+
+ @GuardedBy("mLock")
private AutofillCallback mCallback;
private final Context mContext;
@@ -1172,10 +1178,19 @@ public final class AutofillManager {
if (mServiceClient == null) {
mServiceClient = new AutofillManagerClient(this);
try {
- final int flags = mService.addClient(mServiceClient, mContext.getUserId());
+ final int userId = mContext.getUserId();
+ final int flags = mService.addClient(mServiceClient, userId);
mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
+ final IAutoFillManager service = mService;
+ final IAutoFillManagerClient serviceClient = mServiceClient;
+ mServiceClientCleaner = Cleaner.create(this, () -> {
+ try {
+ service.removeClient(serviceClient, userId);
+ } catch (RemoteException e) {
+ }
+ });
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1272,18 +1287,36 @@ public final class AutofillManager {
}
}
- private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ /** @hide */
+ public static final int SET_STATE_FLAG_ENABLED = 0x01;
+ /** @hide */
+ public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
+ /** @hide */
+ public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
+ /** @hide */
+ public static final int SET_STATE_FLAG_DEBUG = 0x08;
+ /** @hide */
+ public static final int SET_STATE_FLAG_VERBOSE = 0x10;
+
+ private void setState(int flags) {
+ if (sVerbose) Log.v(TAG, "setState(" + flags + ")");
synchronized (mLock) {
- mEnabled = enabled;
- if (!mEnabled || resetSession) {
+ mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
+ if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
// Reset the session state
resetSessionLocked();
}
- if (resetClient) {
+ if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) {
// Reset connection to system
mServiceClient = null;
+ if (mServiceClientCleaner != null) {
+ mServiceClientCleaner.clean();
+ mServiceClientCleaner = null;
+ }
}
}
+ sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
+ sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0;
}
/**
@@ -1609,6 +1642,7 @@ public final class AutofillManager {
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
pw.print(pfx); pw.print("context: "); pw.println(mContext);
+ pw.print(pfx); pw.print("client: "); pw.println(getClientLocked());
pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1625,6 +1659,8 @@ public final class AutofillManager {
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
+ pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
+ pw.print(" verbose: "); pw.println(sVerbose);
}
private String getStateAsStringLocked() {
@@ -1880,7 +1916,7 @@ public final class AutofillManager {
public abstract static class AutofillCallback {
/** @hide */
- @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
+ @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN, EVENT_INPUT_UNAVAILABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface AutofillEventType {}
@@ -1940,10 +1976,10 @@ public final class AutofillManager {
}
@Override
- public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ public void setState(int flags) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.setState(enabled, resetSession, resetClient));
+ afm.post(() -> afm.setState(flags));
}
}
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 26d2141e..2779aa2d 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.LocaleList;
import android.view.View.OnClickListener;
import android.view.textclassifier.TextClassifier.EntityType;
@@ -438,4 +439,31 @@ public final class TextClassification {
mLogType, mVersionInfo);
}
}
+
+ /**
+ * TextClassification optional input parameters.
+ */
+ public static final class Options {
+
+ private LocaleList mDefaultLocales;
+
+ /**
+ * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty
+ * locale list.
+ */
+ public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+ mDefaultLocales = defaultLocales;
+ return this;
+ }
+
+ /**
+ * @return ordered list of locale preferences that can be used to disambiguate
+ * the provided text.
+ */
+ @Nullable
+ public LocaleList getDefaultLocales() {
+ return mDefaultLocales;
+ }
+ }
}
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index 46dbd0e3..aeb84897 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -56,23 +56,7 @@ public interface TextClassifier {
* No-op TextClassifier.
* This may be used to turn off TextClassifier features.
*/
- TextClassifier NO_OP = new TextClassifier() {
-
- @Override
- public TextSelection suggestSelection(
- CharSequence text,
- int selectionStartIndex,
- int selectionEndIndex,
- LocaleList defaultLocales) {
- return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
- }
-
- @Override
- public TextClassification classifyText(
- CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales) {
- return TextClassification.EMPTY;
- }
- };
+ TextClassifier NO_OP = new TextClassifier() {};
/**
* Returns suggested text selection start and end indices, recognized entity types, and their
@@ -82,21 +66,34 @@ public interface TextClassifier {
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
* @param selectionStartIndex start index of the selected part of text
* @param selectionEndIndex end index of the selected part of text
- * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty locale
- * list in which case the classifier will decide whether to use no locale information, use
- * a default locale, or use the system default.
+ * @param options optional input parameters
*
* @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
* selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
*/
@WorkerThread
@NonNull
- TextSelection suggestSelection(
+ default TextSelection suggestSelection(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int selectionStartIndex,
+ @IntRange(from = 0) int selectionEndIndex,
+ @Nullable TextSelection.Options options) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
+
+ /**
+ * @see #suggestSelection(CharSequence, int, int, TextSelection.Options)
+ */
+ // TODO: Consider deprecating (b/68846316)
+ @WorkerThread
+ @NonNull
+ default TextSelection suggestSelection(
@NonNull CharSequence text,
@IntRange(from = 0) int selectionStartIndex,
@IntRange(from = 0) int selectionEndIndex,
- @Nullable LocaleList defaultLocales);
+ @Nullable LocaleList defaultLocales) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
/**
* Classifies the specified text and returns a {@link TextClassification} object that can be
@@ -106,41 +103,48 @@ public interface TextClassifier {
* by the sub sequence starting at startIndex and ending at endIndex)
* @param startIndex start index of the text to classify
* @param endIndex end index of the text to classify
- * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty locale
- * list in which case the classifier will decide whether to use no locale information, use
- * a default locale, or use the system default.
+ * @param options optional input parameters
*
* @throws IllegalArgumentException if text is null; startIndex is negative;
* endIndex is greater than text.length() or not greater than startIndex
*/
@WorkerThread
@NonNull
- TextClassification classifyText(
+ default TextClassification classifyText(
@NonNull CharSequence text,
@IntRange(from = 0) int startIndex,
@IntRange(from = 0) int endIndex,
- @Nullable LocaleList defaultLocales);
+ @Nullable TextClassification.Options options) {
+ return TextClassification.EMPTY;
+ }
/**
- * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+ * @see #classifyText(CharSequence, int, int, TextClassification.Options)
+ */
+ // TODO: Consider deprecating (b/68846316)
+ @WorkerThread
+ @NonNull
+ default TextClassification classifyText(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int startIndex,
+ @IntRange(from = 0) int endIndex,
+ @Nullable LocaleList defaultLocales) {
+ return TextClassification.EMPTY;
+ }
+
+ /**
+ * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
* information.
*
* @param text the text to generate annotations for
- * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
- * specified. Subclasses of this interface may specify additional linkMasks
- * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty locale
- * list in which case the classifier will decide whether to use no locale information, use
- * a default locale, or use the system default.
+ * @param options configuration for link generation. If null, defaults will be used.
*
* @throws IllegalArgumentException if text is null
- * @hide
*/
@WorkerThread
- default LinksInfo getLinks(
- @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
- return LinksInfo.NO_OP;
+ default TextLinks generateLinks(
+ @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+ return new TextLinks.Builder(text.toString()).build();
}
/**
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 1c07be4b..2ad6e02c 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -30,13 +30,8 @@ import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.ContactsContract;
import android.provider.Settings;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.WordIterator;
-import android.text.style.ClickableSpan;
import android.text.util.Linkify;
import android.util.Patterns;
-import android.view.View;
import android.widget.TextViewMetrics;
import com.android.internal.annotations.GuardedBy;
@@ -46,13 +41,8 @@ import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.text.BreakIterator;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -100,16 +90,24 @@ final class TextClassifierImpl implements TextClassifier {
@Override
public TextSelection suggestSelection(
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
- @Nullable LocaleList defaultLocales) {
+ @Nullable TextSelection.Options options) {
validateInput(text, selectionStartIndex, selectionEndIndex);
try {
if (text.length() > 0) {
- final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+ final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+ final SmartSelection smartSelection = getSmartSelection(locales);
final String string = text.toString();
- final int[] startEnd = smartSelection.suggest(
- string, selectionStartIndex, selectionEndIndex);
- final int start = startEnd[0];
- final int end = startEnd[1];
+ final int start;
+ final int end;
+ if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) {
+ start = selectionStartIndex;
+ end = selectionEndIndex;
+ } else {
+ final int[] startEnd = smartSelection.suggest(
+ string, selectionStartIndex, selectionEndIndex);
+ start = startEnd[0];
+ end = startEnd[1];
+ }
if (start <= end
&& start >= 0 && end <= string.length()
&& start <= selectionStartIndex && end >= selectionEndIndex) {
@@ -139,18 +137,27 @@ final class TextClassifierImpl implements TextClassifier {
}
// Getting here means something went wrong, return a NO_OP result.
return TextClassifier.NO_OP.suggestSelection(
- text, selectionStartIndex, selectionEndIndex, defaultLocales);
+ text, selectionStartIndex, selectionEndIndex, options);
+ }
+
+ @Override
+ public TextSelection suggestSelection(
+ @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
+ @Nullable LocaleList defaultLocales) {
+ return suggestSelection(text, selectionStartIndex, selectionEndIndex,
+ new TextSelection.Options().setDefaultLocales(defaultLocales));
}
@Override
public TextClassification classifyText(
@NonNull CharSequence text, int startIndex, int endIndex,
- @Nullable LocaleList defaultLocales) {
+ @Nullable TextClassification.Options options) {
validateInput(text, startIndex, endIndex);
try {
if (text.length() > 0) {
final String string = text.toString();
- SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales)
+ final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+ final SmartSelection.ClassificationResult[] results = getSmartSelection(locales)
.classifyText(string, startIndex, endIndex,
getHintFlags(string, startIndex, endIndex));
if (results.length > 0) {
@@ -165,23 +172,41 @@ final class TextClassifierImpl implements TextClassifier {
Log.e(LOG_TAG, "Error getting text classification info.", t);
}
// Getting here means something went wrong, return a NO_OP result.
- return TextClassifier.NO_OP.classifyText(
- text, startIndex, endIndex, defaultLocales);
+ return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options);
}
@Override
- public LinksInfo getLinks(
- @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
- Preconditions.checkArgument(text != null);
+ public TextClassification classifyText(
+ @NonNull CharSequence text, int startIndex, int endIndex,
+ @Nullable LocaleList defaultLocales) {
+ return classifyText(text, startIndex, endIndex,
+ new TextClassification.Options().setDefaultLocales(defaultLocales));
+ }
+
+ @Override
+ public TextLinks generateLinks(
+ @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+ Preconditions.checkNotNull(text);
+ final String textString = text.toString();
+ final TextLinks.Builder builder = new TextLinks.Builder(textString);
try {
- return LinksInfoFactory.create(
- mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
+ LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+ final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+ final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
+ for (SmartSelection.AnnotatedSpan span : annotations) {
+ final Map<String, Float> entityScores = new HashMap<>();
+ final SmartSelection.ClassificationResult[] results = span.getClassification();
+ for (int i = 0; i < results.length; i++) {
+ entityScores.put(results[i].mCollection, results[i].mScore);
+ }
+ builder.addLink(new TextLinks.TextLink(
+ textString, span.getStartIndex(), span.getEndIndex(), entityScores));
+ }
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
Log.e(LOG_TAG, "Error getting links info.", t);
}
- // Getting here means something went wrong, return a NO_OP result.
- return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
+ return builder.build();
}
@Override
@@ -210,7 +235,9 @@ final class TextClassifierImpl implements TextClassifier {
if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
destroySmartSelectionIfExistsLocked();
final ParcelFileDescriptor fd = getFdLocked(locale);
- mSmartSelection = new SmartSelection(fd.getFd());
+ final int modelFd = fd.getFd();
+ mVersion = SmartSelection.getVersion(modelFd);
+ mSmartSelection = new SmartSelection(modelFd);
closeAndLogError(fd);
mLocale = locale;
}
@@ -231,18 +258,26 @@ final class TextClassifierImpl implements TextClassifier {
@GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
ParcelFileDescriptor updateFd;
+ int updateVersion = -1;
try {
updateFd = ParcelFileDescriptor.open(
new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (updateFd != null) {
+ updateVersion = SmartSelection.getVersion(updateFd.getFd());
+ }
} catch (FileNotFoundException e) {
updateFd = null;
}
ParcelFileDescriptor factoryFd;
+ int factoryVersion = -1;
try {
final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
if (factoryModelFilePath != null) {
factoryFd = ParcelFileDescriptor.open(
new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (factoryFd != null) {
+ factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
+ }
} else {
factoryFd = null;
}
@@ -278,15 +313,11 @@ final class TextClassifierImpl implements TextClassifier {
return factoryFd;
}
- final int updateVersion = SmartSelection.getVersion(updateFdInt);
- final int factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
if (updateVersion > factoryVersion) {
closeAndLogError(factoryFd);
- mVersion = updateVersion;
return updateFd;
} else {
closeAndLogError(updateFd);
- mVersion = factoryVersion;
return factoryFd;
}
}
@@ -466,180 +497,6 @@ final class TextClassifierImpl implements TextClassifier {
}
/**
- * Detects and creates links for specified text.
- */
- private static final class LinksInfoFactory {
-
- private LinksInfoFactory() {}
-
- public static LinksInfo create(
- Context context, SmartSelection smartSelection, String text, int linkMask) {
- final WordIterator wordIterator = new WordIterator();
- wordIterator.setCharSequence(text, 0, text.length());
- final List<SpanSpec> spans = new ArrayList<>();
- int start = 0;
- int end;
- while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
- final String token = text.substring(start, end);
- if (TextUtils.isEmpty(token)) {
- continue;
- }
-
- final int[] selection = smartSelection.suggest(text, start, end);
- final int selectionStart = selection[0];
- final int selectionEnd = selection[1];
- if (selectionStart >= 0 && selectionEnd <= text.length()
- && selectionStart <= selectionEnd) {
- final SmartSelection.ClassificationResult[] results =
- smartSelection.classifyText(
- text, selectionStart, selectionEnd,
- getHintFlags(text, selectionStart, selectionEnd));
- if (results.length > 0) {
- final String type = getHighestScoringType(results);
- if (matches(type, linkMask)) {
- // For links without disambiguation, we simply use the default intent.
- final List<Intent> intents = IntentFactory.create(
- context, type, text.substring(selectionStart, selectionEnd));
- if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
- final ClickableSpan span = createSpan(context, intents.get(0));
- spans.add(new SpanSpec(selectionStart, selectionEnd, span));
- }
- }
- }
- }
- start = end;
- }
- return new LinksInfoImpl(text, avoidOverlaps(spans, text));
- }
-
- /**
- * Returns true if the classification type matches the specified linkMask.
- */
- private static boolean matches(String type, int linkMask) {
- type = type.trim().toLowerCase(Locale.ENGLISH);
- if ((linkMask & Linkify.PHONE_NUMBERS) != 0
- && TextClassifier.TYPE_PHONE.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
- && TextClassifier.TYPE_EMAIL.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.MAP_ADDRESSES) != 0
- && TextClassifier.TYPE_ADDRESS.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.WEB_URLS) != 0
- && TextClassifier.TYPE_URL.equals(type)) {
- return true;
- }
- return false;
- }
-
- /**
- * Trim the number of spans so that no two spans overlap.
- *
- * This algorithm first ensures that there is only one span per start index, then it
- * makes sure that no two spans overlap.
- */
- private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
- Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
- // Group spans by start index. Take the longest span.
- final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters.
- final int size = spans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = spans.get(i);
- final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
- if (rep == null || rep.mEnd < span.mEnd) {
- reps.put(span.mStart, span);
- }
- }
- // Avoid span intersections. Take the longer span.
- final LinkedList<SpanSpec> result = new LinkedList<>();
- for (SpanSpec rep : reps.values()) {
- if (result.isEmpty()) {
- result.add(rep);
- continue;
- }
-
- final SpanSpec last = result.getLast();
- if (rep.mStart < last.mEnd) {
- // Spans intersect. Use the one with characters.
- if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
- result.set(result.size() - 1, rep);
- }
- } else {
- result.add(rep);
- }
- }
- return result;
- }
-
- private static ClickableSpan createSpan(final Context context, final Intent intent) {
- return new ClickableSpan() {
- // TODO: Style this span.
- @Override
- public void onClick(View widget) {
- context.startActivity(intent);
- }
- };
- }
-
- private static boolean hasActivityHandler(Context context, Intent intent) {
- if (intent == null) {
- return false;
- }
- final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
- return resolveInfo != null && resolveInfo.activityInfo != null;
- }
-
- /**
- * Implementation of LinksInfo that adds ClickableSpans to the specified text.
- */
- private static final class LinksInfoImpl implements LinksInfo {
-
- private final CharSequence mOriginalText;
- private final List<SpanSpec> mSpans;
-
- LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
- mOriginalText = originalText;
- mSpans = spans;
- }
-
- @Override
- public boolean apply(@NonNull CharSequence text) {
- Preconditions.checkArgument(text != null);
- if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
- Spannable spannable = (Spannable) text;
- final int size = mSpans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = mSpans.get(i);
- spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
- }
- return true;
- }
- return false;
- }
- }
-
- /**
- * Span plus its start and end index.
- */
- private static final class SpanSpec {
-
- private final int mStart;
- private final int mEnd;
- private final ClickableSpan mSpan;
-
- SpanSpec(int start, int end, ClickableSpan span) {
- mStart = start;
- mEnd = end;
- mSpan = span;
- }
- }
- }
-
- /**
* Creates intents based on the classification type.
*/
private static final class IntentFactory {
@@ -656,8 +513,8 @@ final class TextClassifierImpl implements TextClassifier {
intents.add(new Intent(Intent.ACTION_SENDTO)
.setData(Uri.parse(String.format("mailto:%s", text))));
intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
- .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
break;
case TextClassifier.TYPE_PHONE:
intents.add(new Intent(Intent.ACTION_DIAL)
diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java
new file mode 100644
index 00000000..f3cc827f
--- /dev/null
+++ b/android/view/textclassifier/TextLinks.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
+import android.text.SpannableString;
+import android.text.style.ClickableSpan;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A collection of links, representing subsequences of text and the entity types (phone number,
+ * address, url, etc) they may be.
+ */
+public final class TextLinks {
+ private final String mFullText;
+ private final List<TextLink> mLinks;
+
+ private TextLinks(String fullText, Collection<TextLink> links) {
+ mFullText = fullText;
+ mLinks = Collections.unmodifiableList(new ArrayList<>(links));
+ }
+
+ /**
+ * Returns an unmodifiable Collection of the links.
+ */
+ public Collection<TextLink> getLinks() {
+ return mLinks;
+ }
+
+ /**
+ * Annotates the given text with the generated links. It will fail if the provided text doesn't
+ * match the original text used to crete the TextLinks.
+ *
+ * @param text the text to apply the links to. Must match the original text.
+ * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null.
+ *
+ * @return Success or failure.
+ */
+ public boolean apply(
+ @NonNull SpannableString text,
+ @Nullable Function<TextLink, ClickableSpan> spanFactory) {
+ Preconditions.checkNotNull(text);
+ if (!mFullText.equals(text.toString())) {
+ return false;
+ }
+
+ if (spanFactory == null) {
+ spanFactory = DEFAULT_SPAN_FACTORY;
+ }
+ for (TextLink link : mLinks) {
+ final ClickableSpan span = spanFactory.apply(link);
+ if (span != null) {
+ text.setSpan(span, link.getStart(), link.getEnd(), 0);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * A link, identifying a substring of text and possible entity types for it.
+ */
+ public static final class TextLink {
+ private final EntityConfidence<String> mEntityScores;
+ private final String mOriginalText;
+ private final int mStart;
+ private final int mEnd;
+
+ /**
+ * Create a new TextLink.
+ *
+ * @throws IllegalArgumentException if entityScores is null or empty.
+ */
+ public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) {
+ Preconditions.checkNotNull(originalText);
+ Preconditions.checkNotNull(entityScores);
+ Preconditions.checkArgument(!entityScores.isEmpty());
+ Preconditions.checkArgument(start <= end);
+ mOriginalText = originalText;
+ mStart = start;
+ mEnd = end;
+ mEntityScores = new EntityConfidence<>();
+
+ for (Map.Entry<String, Float> entry : entityScores.entrySet()) {
+ mEntityScores.setEntityType(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Returns the start index of this link in the original text.
+ *
+ * @return the start index.
+ */
+ public int getStart() {
+ return mStart;
+ }
+
+ /**
+ * Returns the end index of this link in the original text.
+ *
+ * @return the end index.
+ */
+ public int getEnd() {
+ return mEnd;
+ }
+
+ /**
+ * Returns the number of entity types that have confidence scores.
+ *
+ * @return the entity count.
+ */
+ public int getEntityCount() {
+ return mEntityScores.getEntities().size();
+ }
+
+ /**
+ * Returns the entity type at a given index. Entity types are sorted by confidence.
+ *
+ * @return the entity type at the provided index.
+ */
+ @NonNull public @TextClassifier.EntityType String getEntity(int index) {
+ return mEntityScores.getEntities().get(index);
+ }
+
+ /**
+ * Returns the confidence score for a particular entity type.
+ *
+ * @param entityType the entity type.
+ */
+ public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
+ @TextClassifier.EntityType String entityType) {
+ return mEntityScores.getConfidenceScore(entityType);
+ }
+ }
+
+ /**
+ * Optional input parameters for generating TextLinks.
+ */
+ public static final class Options {
+ private final LocaleList mLocaleList;
+
+ private Options(LocaleList localeList) {
+ this.mLocaleList = localeList;
+ }
+
+ /**
+ * Builder to construct Options.
+ */
+ public static final class Builder {
+ private LocaleList mLocaleList;
+
+ /**
+ * Sets the LocaleList to use.
+ *
+ * @return this Builder.
+ */
+ public Builder setLocaleList(@Nullable LocaleList localeList) {
+ this.mLocaleList = localeList;
+ return this;
+ }
+
+ /**
+ * Builds the Options object.
+ */
+ public Options build() {
+ return new Options(mLocaleList);
+ }
+ }
+ public @Nullable LocaleList getDefaultLocales() {
+ return mLocaleList;
+ }
+ };
+
+ /**
+ * A function to create spans from TextLinks.
+ *
+ * Applies only to TextViews.
+ * We can hide this until we are convinced we want it to be part of the public API.
+ *
+ * @hide
+ */
+ public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
+ new Function<TextLink, ClickableSpan>() {
+ @Override
+ public ClickableSpan apply(TextLink textLink) {
+ // TODO: Implement.
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+ };
+
+ /**
+ * A builder to construct a TextLinks instance.
+ */
+ public static final class Builder {
+ private final String mFullText;
+ private final Collection<TextLink> mLinks;
+
+ /**
+ * Create a new TextLinks.Builder.
+ *
+ * @param fullText The full text that links will be added to.
+ */
+ public Builder(@NonNull String fullText) {
+ mFullText = Preconditions.checkNotNull(fullText);
+ mLinks = new ArrayList<>();
+ }
+
+ /**
+ * Adds a TextLink.
+ *
+ * @return this instance.
+ */
+ public Builder addLink(TextLink link) {
+ Preconditions.checkNotNull(link);
+ mLinks.add(link);
+ return this;
+ }
+
+ /**
+ * Constructs a TextLinks instance.
+ *
+ * @return the constructed TextLinks.
+ */
+ public TextLinks build() {
+ return new TextLinks(mFullText, mLinks);
+ }
+ }
+}
diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java
index 11ebe835..0a67954a 100644
--- a/android/view/textclassifier/TextSelection.java
+++ b/android/view/textclassifier/TextSelection.java
@@ -19,6 +19,8 @@ package android.view.textclassifier;
import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
import android.view.textclassifier.TextClassifier.EntityType;
import com.android.internal.util.Preconditions;
@@ -181,4 +183,55 @@ public final class TextSelection {
mStartIndex, mEndIndex, mEntityConfidence, mLogSource, mVersionInfo);
}
}
+
+ /**
+ * TextSelection optional input parameters.
+ */
+ public static final class Options {
+
+ private LocaleList mDefaultLocales;
+ private boolean mDarkLaunchAllowed;
+
+ /**
+ * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty
+ * locale list.
+ */
+ public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+ mDefaultLocales = defaultLocales;
+ return this;
+ }
+
+ /**
+ * @return ordered list of locale preferences that can be used to disambiguate
+ * the provided text.
+ */
+ @Nullable
+ public LocaleList getDefaultLocales() {
+ return mDefaultLocales;
+ }
+
+ /**
+ * @param allowed whether or not the TextClassifier should return selection suggestions
+ * when "dark launched". When a TextClassifier is dark launched, it can suggest
+ * selection changes that should not be used to actually change the user's selection.
+ * Instead, the suggested selection is logged, compared with the user's selection
+ * interaction, and used to generate quality metrics for the TextClassifier.
+ *
+ * @hide
+ */
+ public void setDarkLaunchAllowed(boolean allowed) {
+ mDarkLaunchAllowed = allowed;
+ }
+
+ /**
+ * Returns true if the TextClassifier should return selection suggestions when
+ * "dark launched". Otherwise, returns false.
+ *
+ * @hide
+ */
+ public boolean isDarkLaunchAllowed() {
+ return mDarkLaunchAllowed;
+ }
+ }
}
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 83af19bb..2833564f 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -48,31 +48,45 @@ public final class SmartSelectionEventTracker {
private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
- private static final int VERSION_TAG = MetricsEvent.FIELD_SELECTION_VERSION_TAG;
- private static final int SMART_INDICES = MetricsEvent.FIELD_SELECTION_SMART_RANGE;
- private static final int EVENT_INDICES = MetricsEvent.FIELD_SELECTION_RANGE;
+ private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
+ private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
+ private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
+ private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
+ private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
+ private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
+ private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
+ private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
private static final String ZERO = "0";
private static final String TEXTVIEW = "textview";
private static final String EDITTEXT = "edittext";
+ private static final String UNSELECTABLE_TEXTVIEW = "nosel-textview";
private static final String WEBVIEW = "webview";
private static final String EDIT_WEBVIEW = "edit-webview";
+ private static final String CUSTOM_TEXTVIEW = "customview";
+ private static final String CUSTOM_EDITTEXT = "customedit";
+ private static final String CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
private static final String UNKNOWN = "unknown";
@Retention(RetentionPolicy.SOURCE)
@IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW,
WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW})
public @interface WidgetType {
- int UNSPECIFIED = 0;
- int TEXTVIEW = 1;
- int WEBVIEW = 2;
- int EDITTEXT = 3;
- int EDIT_WEBVIEW = 4;
+ int UNSPECIFIED = 0;
+ int TEXTVIEW = 1;
+ int WEBVIEW = 2;
+ int EDITTEXT = 3;
+ int EDIT_WEBVIEW = 4;
+ int UNSELECTABLE_TEXTVIEW = 5;
+ int CUSTOM_TEXTVIEW = 6;
+ int CUSTOM_EDITTEXT = 7;
+ int CUSTOM_UNSELECTABLE_TEXTVIEW = 8;
}
private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final int mWidgetType;
+ @Nullable private final String mWidgetVersion;
private final Context mContext;
@Nullable private String mSessionId;
@@ -83,10 +97,18 @@ public final class SmartSelectionEventTracker {
private long mSessionStartTime;
private long mLastEventTime;
private boolean mSmartSelectionTriggered;
- private String mVersionTag;
+ private String mModelName;
public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
mWidgetType = widgetType;
+ mWidgetVersion = null;
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ public SmartSelectionEventTracker(
+ @NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) {
+ mWidgetType = widgetType;
+ mWidgetVersion = widgetVersion;
mContext = Preconditions.checkNotNull(context);
}
@@ -115,7 +137,7 @@ public final class SmartSelectionEventTracker {
case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
case SelectionEvent.EventType.SMART_SELECTION_MULTI:
mSmartSelectionTriggered = true;
- mVersionTag = getVersionTag(event);
+ mModelName = getModelName(event);
mSmartIndices[0] = event.mStart;
mSmartIndices[1] = event.mEnd;
break;
@@ -137,14 +159,19 @@ public final class SmartSelectionEventTracker {
final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime;
final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
.setType(getLogType(event))
- .setSubtype(getLogSubType(event))
+ .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL)
.setPackageName(mContext.getPackageName())
.addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
.addTaggedData(PREV_EVENT_DELTA, prevEventDelta)
.addTaggedData(INDEX, mIndex)
- .addTaggedData(VERSION_TAG, mVersionTag)
- .addTaggedData(SMART_INDICES, getSmartDelta())
- .addTaggedData(EVENT_INDICES, getEventDelta(event))
+ .addTaggedData(WIDGET_TYPE, getWidgetTypeName())
+ .addTaggedData(WIDGET_VERSION, mWidgetVersion)
+ .addTaggedData(MODEL_NAME, mModelName)
+ .addTaggedData(ENTITY_TYPE, event.mEntityType)
+ .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0]))
+ .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1]))
+ .addTaggedData(EVENT_START, getRangeDelta(event.mStart))
+ .addTaggedData(EVENT_END, getRangeDelta(event.mEnd))
.addTaggedData(SESSION_ID, mSessionId);
mMetricsLogger.write(log);
debugLog(log);
@@ -169,7 +196,7 @@ public final class SmartSelectionEventTracker {
mSessionStartTime = 0;
mLastEventTime = 0;
mSmartSelectionTriggered = false;
- mVersionTag = getVersionTag(null);
+ mModelName = getModelName(null);
mSessionId = null;
}
@@ -251,113 +278,75 @@ public final class SmartSelectionEventTracker {
}
}
- private static int getLogSubType(SelectionEvent event) {
- switch (event.mEntityType) {
- case TextClassifier.TYPE_OTHER:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER;
- case TextClassifier.TYPE_EMAIL:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL;
- case TextClassifier.TYPE_PHONE:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE;
- case TextClassifier.TYPE_ADDRESS:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS;
- case TextClassifier.TYPE_URL:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_URL;
- default:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_UNKNOWN;
- }
- }
-
- private static String getLogSubTypeString(int logSubType) {
- switch (logSubType) {
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER:
- return TextClassifier.TYPE_OTHER;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL:
- return TextClassifier.TYPE_EMAIL;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE:
- return TextClassifier.TYPE_PHONE;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS:
- return TextClassifier.TYPE_ADDRESS;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_URL:
- return TextClassifier.TYPE_URL;
- default:
- return TextClassifier.TYPE_UNKNOWN;
- }
- }
-
- private int getSmartDelta() {
- if (mSmartSelectionTriggered) {
- return (clamp(mSmartIndices[0] - mOrigStart) << 16)
- | (clamp(mSmartIndices[1] - mOrigStart) & 0xffff);
- }
- // If the smart selection model was not run, return invalid selection indices [0,0]. This
- // allows us to tell from the terminal event alone whether the model was run.
- return 0;
+ private int getRangeDelta(int offset) {
+ return offset - mOrigStart;
}
- private int getEventDelta(SelectionEvent event) {
- return (clamp(event.mStart - mOrigStart) << 16)
- | (clamp(event.mEnd - mOrigStart) & 0xffff);
+ private int getSmartRangeDelta(int offset) {
+ return mSmartSelectionTriggered ? getRangeDelta(offset) : 0;
}
- private String getVersionTag(@Nullable SelectionEvent event) {
- final String widgetType;
+ private String getWidgetTypeName() {
switch (mWidgetType) {
case WidgetType.TEXTVIEW:
- widgetType = TEXTVIEW;
- break;
+ return TEXTVIEW;
case WidgetType.WEBVIEW:
- widgetType = WEBVIEW;
- break;
+ return WEBVIEW;
case WidgetType.EDITTEXT:
- widgetType = EDITTEXT;
- break;
+ return EDITTEXT;
case WidgetType.EDIT_WEBVIEW:
- widgetType = EDIT_WEBVIEW;
- break;
+ return EDIT_WEBVIEW;
+ case WidgetType.UNSELECTABLE_TEXTVIEW:
+ return UNSELECTABLE_TEXTVIEW;
+ case WidgetType.CUSTOM_TEXTVIEW:
+ return CUSTOM_TEXTVIEW;
+ case WidgetType.CUSTOM_EDITTEXT:
+ return CUSTOM_EDITTEXT;
+ case WidgetType.CUSTOM_UNSELECTABLE_TEXTVIEW:
+ return CUSTOM_UNSELECTABLE_TEXTVIEW;
default:
- widgetType = UNKNOWN;
+ return UNKNOWN;
}
- final String version = event == null
+ }
+
+ private String getModelName(@Nullable SelectionEvent event) {
+ return event == null
? SelectionEvent.NO_VERSION_TAG
: Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
- return String.format("%s/%s", widgetType, version);
}
private static String createSessionId() {
return UUID.randomUUID().toString();
}
- private static int clamp(int val) {
- return Math.max(Math.min(val, Short.MAX_VALUE), Short.MIN_VALUE);
- }
-
private static void debugLog(LogMaker log) {
if (!DEBUG_LOG_ENABLED) return;
- final String tag = Objects.toString(log.getTaggedData(VERSION_TAG), "tag");
+ final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
+ final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
+ final String widget = widgetVersion.isEmpty()
+ ? widgetType : widgetType + "-" + widgetVersion;
final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
- Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
+ Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
}
+ final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
+ final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
final String type = getLogTypeString(log.getType());
- final String subType = getLogSubTypeString(log.getSubtype());
-
- final int smartIndices = Integer.parseInt(
- Objects.toString(log.getTaggedData(SMART_INDICES), ZERO));
- final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16);
- final int smartEnd = (short) (smartIndices & 0xffff);
-
- final int eventIndices = Integer.parseInt(
- Objects.toString(log.getTaggedData(EVENT_INDICES), ZERO));
- final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16);
- final int eventEnd = (short) (eventIndices & 0xffff);
-
- Log.d(LOG_TAG, String.format("%2d: %s/%s, context=%d,%d - old=%d,%d (%s)",
- index, type, subType, eventStart, eventEnd, smartStart, smartEnd, tag));
+ final int smartStart = Integer.parseInt(
+ Objects.toString(log.getTaggedData(SMART_START), ZERO));
+ final int smartEnd = Integer.parseInt(
+ Objects.toString(log.getTaggedData(SMART_END), ZERO));
+ final int eventStart = Integer.parseInt(
+ Objects.toString(log.getTaggedData(EVENT_START), ZERO));
+ final int eventEnd = Integer.parseInt(
+ Objects.toString(log.getTaggedData(EVENT_END), ZERO));
+
+ Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
+ index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model));
}
/**
@@ -369,12 +358,12 @@ public final class SmartSelectionEventTracker {
/**
* Use this to specify an indeterminate positive index.
*/
- public static final int OUT_OF_BOUNDS = Short.MAX_VALUE;
+ public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE;
/**
* Use this to specify an indeterminate negative index.
*/
- public static final int OUT_OF_BOUNDS_NEGATIVE = Short.MIN_VALUE;
+ public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE;
private static final String NO_VERSION_TAG = "";
diff --git a/android/webkit/ServiceWorkerClient.java b/android/webkit/ServiceWorkerClient.java
index d6e8f36c..9124c855 100644
--- a/android/webkit/ServiceWorkerClient.java
+++ b/android/webkit/ServiceWorkerClient.java
@@ -29,9 +29,9 @@ public class ServiceWorkerClient {
* application to return the data. If the return value is {@code null}, the
* Service Worker will continue to load the resource as usual.
* Otherwise, the return response and data will be used.
- * NOTE: This method is called on a thread other than the UI thread
- * so clients should exercise caution when accessing private data
- * or the view system.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread other than the UI thread so
+ * clients should exercise caution when accessing private data or the view system.
*
* @param request Object containing the details of the request.
* @return A {@link android.webkit.WebResourceResponse} containing the
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 259bf60a..665d694e 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -191,7 +191,7 @@ import java.util.Map;
* {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
* (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
*
- * <p>NOTE: Using zoom if either the height or width is set to
+ * <p class="note"><b>Note:</b> Using zoom if either the height or width is set to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
* and should be avoided.
*
@@ -308,10 +308,15 @@ import java.util.Map;
* WebView may upload anonymous diagnostic data to Google when the user has consented. This data
* helps Google improve WebView. Data is collected on a per-app basis for each app which has
* instantiated a WebView. An individual app can opt out of this feature by putting the following
- * tag in its manifest:
+ * tag in its manifest's {@code <application>} element:
* <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- * android:value="true" /&gt;
+ * &lt;manifest&gt;
+ * &lt;application&gt;
+ * ...
+ * &lt;meta-data android:name=&quot;android.webkit.WebView.MetricsOptOut&quot;
+ * android:value=&quot;true&quot; /&gt;
+ * &lt;/application&gt;
+ * &lt;/manifest&gt;
* </pre>
* <p>
* Data will only be uploaded for a given app if the user has consented AND the app has not opted
@@ -323,11 +328,17 @@ import java.util.Map;
* If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
* user to allow them to navigate back safely or proceed to the malicious page.
* <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest:
+ * The recommended way for apps to enable the feature is putting the following tag in the manifest's
+ * {@code <application>} element:
* <p>
* <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- * android:value="true" /&gt;
+ * &lt;manifest&gt;
+ * &lt;application&gt;
+ * ...
+ * &lt;meta-data android:name=&quot;android.webkit.WebView.EnableSafeBrowsing&quot;
+ * android:value=&quot;true&quot; /&gt;
+ * &lt;/application&gt;
+ * &lt;/manifest&gt;
* </pre>
*
*/
@@ -536,9 +547,13 @@ public class WebView extends AbsoluteLayout
}
/**
- * Constructs a new WebView with a Context object.
+ * Constructs a new WebView with an Activity Context object.
*
- * @param context a Context object used to access application assets
+ * <p class="note"><b>Note:</b> WebView should always be instantiated with an Activity Context.
+ * If instantiated with an Application Context, WebView will be unable to provide several
+ * features, such as JavaScript dialogs and autofill.
+ *
+ * @param context an Activity Context to access application assets
*/
public WebView(Context context) {
this(context, null);
@@ -547,7 +562,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
*/
public WebView(Context context, AttributeSet attrs) {
@@ -557,7 +572,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -570,7 +585,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -587,7 +602,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -612,7 +627,7 @@ public class WebView extends AbsoluteLayout
* time. This guarantees that these interfaces will be available when the JS
* context is initialized.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
diff --git a/android/webkit/WebViewClient.java b/android/webkit/WebViewClient.java
index c5b64eb8..517ad07c 100644
--- a/android/webkit/WebViewClient.java
+++ b/android/webkit/WebViewClient.java
@@ -130,7 +130,7 @@ public class WebViewClient {
* <p>This method is called when the body of the HTTP response has started loading, is reflected
* in the DOM, and will be visible in subsequent draws. This callback occurs early in the
* document loading process, and as such you should expect that linked resources (for example,
- * css and images) may not be available.
+ * CSS and images) may not be available.
*
* <p>For more fine-grained notification of visual state updates, see {@link
* WebView#postVisualStateCallback}.
@@ -150,13 +150,15 @@ public class WebViewClient {
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is {@code null}, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called on a thread
+ * response and data will be used.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread
* other than the UI thread so clients should exercise caution
* when accessing private data or the view system.
*
- * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
- * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
- * the warning with {@link #onSafeBrowsingHit}.
+ * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+ * Browsing checks. If this is undesired, whitelist the URL with {@link
+ * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -178,13 +180,15 @@ public class WebViewClient {
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is {@code null}, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called on a thread
+ * response and data will be used.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread
* other than the UI thread so clients should exercise caution
* when accessing private data or the view system.
*
- * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
- * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
- * the warning with {@link #onSafeBrowsingHit}.
+ * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+ * Browsing checks. If this is undesired, whitelist the URL with {@link
+ * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -248,7 +252,7 @@ public class WebViewClient {
public static final int ERROR_FILE_NOT_FOUND = -14;
/** Too many requests during this load */
public static final int ERROR_TOO_MANY_REQUESTS = -15;
- /** Resource load was cancelled by Safe Browsing */
+ /** Resource load was canceled by Safe Browsing */
public static final int ERROR_UNSAFE_RESOURCE = -16;
/** @hide */
@@ -272,8 +276,8 @@ public class WebViewClient {
/**
* Report an error to the host application. These errors are unrecoverable
- * (i.e. the main resource is unavailable). The errorCode parameter
- * corresponds to one of the ERROR_* constants.
+ * (i.e. the main resource is unavailable). The {@code errorCode} parameter
+ * corresponds to one of the {@code ERROR_*} constants.
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
@@ -289,11 +293,11 @@ public class WebViewClient {
/**
* Report web resource loading error to the host application. These errors usually indicate
* inability to connect to the server. Note that unlike the deprecated version of the callback,
- * the new version will be called for any resource (iframe, image, etc), not just for the main
+ * the new version will be called for any resource (iframe, image, etc.), not just for the main
* page. Thus, it is recommended to perform minimum required work in this callback.
* @param view The WebView that is initiating the callback.
* @param request The originating request.
- * @param error Information about the error occured.
+ * @param error Information about the error occurred.
*/
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (request.isForMainFrame()) {
@@ -306,12 +310,12 @@ public class WebViewClient {
/**
* Notify the host application that an HTTP error has been received from the server while
* loading a resource. HTTP errors have status codes &gt;= 400. This callback will be called
- * for any resource (iframe, image, etc), not just for the main page. Thus, it is recommended to
- * perform minimum required work in this callback. Note that the content of the server
- * response may not be provided within the <b>errorResponse</b> parameter.
+ * for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended
+ * to perform minimum required work in this callback. Note that the content of the server
+ * response may not be provided within the {@code errorResponse} parameter.
* @param view The WebView that is initiating the callback.
* @param request The originating request.
- * @param errorResponse Information about the error occured.
+ * @param errorResponse Information about the error occurred.
*/
public void onReceivedHttpError(
WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
@@ -365,7 +369,7 @@ public class WebViewClient {
* if desired and providing the keys. There are three ways to
* respond: proceed(), cancel() or ignore(). Webview stores the response
* in memory (for the life of the application) if proceed() or cancel() is
- * called and does not call onReceivedClientCertRequest() again for the
+ * called and does not call {@code onReceivedClientCertRequest()} again for the
* same host and port pair. Webview does not store the response if ignore()
* is called. Note that, multiple layers in chromium network stack might be
* caching the responses, so the behavior for ignore is only a best case
@@ -432,7 +436,7 @@ public class WebViewClient {
/**
* Notify the host application that a key was not handled by the WebView.
* Except system keys, WebView always consumes the keys in the normal flow
- * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+ * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
* from where the key is dispatched. It gives the host application a chance
* to handle the unhandled key events.
*
@@ -446,7 +450,7 @@ public class WebViewClient {
/**
* Notify the host application that a input event was not handled by the WebView.
* Except system keys, WebView always consumes input events in the normal flow
- * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+ * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
* from where the event is dispatched. It gives the host application a chance
* to handle the unhandled input events.
*
@@ -503,7 +507,7 @@ public class WebViewClient {
}
/**
- * Notify host application that the given webview's render process has exited.
+ * Notify host application that the given WebView's render process has exited.
*
* Multiple WebView instances may be associated with a single render process;
* onRenderProcessGone will be called for each WebView that was affected.
@@ -513,10 +517,10 @@ public class WebViewClient {
*
* The given WebView can't be used, and should be removed from the view hierarchy,
* all references to it should be cleaned up, e.g any references in the Activity
- * or other classes saved using findViewById and similar calls, etc
+ * or other classes saved using {@link android.view.View#findViewById} and similar calls, etc.
*
* To cause an render process crash for test purpose, the application can
- * call loadUrl("chrome://crash") on the WebView. Note that multiple WebView
+ * call {@code loadUrl("chrome://crash")} on the WebView. Note that multiple WebView
* instances may be affected if they share a render process, not just the
* specific WebView which loaded chrome://crash.
*
@@ -537,12 +541,13 @@ public class WebViewClient {
* behavior is to show an interstitial to the user, with the reporting checkbox visible.
*
* If the application needs to show its own custom interstitial UI, the callback can be invoked
- * asynchronously with backToSafety() or proceed(), depending on user response.
+ * asynchronously with {@link SafeBrowsingResponse#backToSafety} or {@link
+ * SafeBrowsingResponse#proceed}, depending on user response.
*
* @param view The WebView that hit the malicious resource.
* @param request Object containing the details of the request.
* @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
- * SAFE_BROWSING_THREAT_* value.
+ * {@code SAFE_BROWSING_THREAT_*} value.
* @param callback Applications must invoke one of the callback methods.
*/
public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
diff --git a/android/webkit/WebViewFragment.java b/android/webkit/WebViewFragment.java
index d803f62d..e5b7c8d2 100644
--- a/android/webkit/WebViewFragment.java
+++ b/android/webkit/WebViewFragment.java
@@ -27,7 +27,10 @@ import android.webkit.WebView;
* A fragment that displays a WebView.
* <p>
* The WebView is automically paused or resumed when the Fragment is paused or resumed.
+ *
+ * @deprecated Manually call {@link WebView#onPause()} and {@link WebView#onResume()}
*/
+@Deprecated
public class WebViewFragment extends Fragment {
private WebView mWebView;
private boolean mIsWebViewAvailable;
diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java
index 341c69fd..de0b97d1 100644
--- a/android/webkit/WebViewLibraryLoader.java
+++ b/android/webkit/WebViewLibraryLoader.java
@@ -229,7 +229,9 @@ public class WebViewLibraryLoader {
/**
* Load WebView's native library into the current process.
- * Note: assumes that we have waited for relro creation.
+ *
+ * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
+ *
* @param clazzLoader class loader used to find the linker namespace to load the library into.
* @param packageInfo the package from which WebView is loaded.
*/
diff --git a/android/webkit/WebViewProvider.java b/android/webkit/WebViewProvider.java
index c46c681c..a8969252 100644
--- a/android/webkit/WebViewProvider.java
+++ b/android/webkit/WebViewProvider.java
@@ -316,7 +316,7 @@ public interface WebViewProvider {
/**
* Provides mechanism for the name-sake methods declared in View and ViewGroup to be delegated
* into the WebViewProvider instance.
- * NOTE For many of these methods, the WebView will provide a super.Foo() call before or after
+ * NOTE: For many of these methods, the WebView will provide a super.Foo() call before or after
* making the call into the provider instance. This is done for convenience in the common case
* of maintaining backward compatibility. For remaining super class calls (e.g. where the
* provider may need to only conditionally make the call based on some internal state) see the
diff --git a/android/widget/AbsListView.java b/android/widget/AbsListView.java
index 170582b3..e0c897d3 100644
--- a/android/widget/AbsListView.java
+++ b/android/widget/AbsListView.java
@@ -3866,6 +3866,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private void onTouchDown(MotionEvent ev) {
mHasPerformedLongPress = false;
mActivePointerId = ev.getPointerId(0);
+ hideSelector();
if (mTouchMode == TOUCH_MODE_OVERFLING) {
// Stopped the fling. It is a scroll.
@@ -5226,17 +5227,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
mRecycler.fullyDetachScrapViews();
+ boolean selectorOnScreen = false;
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
+ selectorOnScreen = true;
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
- positionSelector(INVALID_POSITION, getChildAt(childIndex));
+ positionSelector(mSelectorPosition, getChildAt(childIndex));
+ selectorOnScreen = true;
}
- } else {
+ }
+ if (!selectorOnScreen) {
mSelectorRect.setEmpty();
}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index e6da69dc..d477ffdf 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -3842,14 +3842,10 @@ public class Editor {
mProcessTextIntentActionsHandler.onInitializeMenu(menu);
}
- if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- if (mHasSelection && !mTextView.hasTransientState()) {
- mTextView.setHasTransientState(true);
- }
- return true;
- } else {
- return false;
+ if (mHasSelection && !mTextView.hasTransientState()) {
+ mTextView.setHasTransientState(true);
}
+ return true;
}
private Callback getCustomCallback() {
@@ -6557,12 +6553,12 @@ public class Editor {
* Adds "PROCESS_TEXT" menu items to the specified menu.
*/
public void onInitializeMenu(Menu menu) {
- final int size = mSupportedActivities.size();
loadSupportedActivities();
+ final int size = mSupportedActivities.size();
for (int i = 0; i < size; i++) {
final ResolveInfo resolveInfo = mSupportedActivities.get(i);
menu.add(Menu.NONE, Menu.NONE,
- Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i++,
+ Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
getLabel(resolveInfo))
.setIntent(createProcessTextIntentForResolveInfo(resolveInfo))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index 8dc8cab1..e91db139 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -1354,6 +1354,7 @@ public class PopupWindow {
}
mDecorView = createDecorView(mBackgroundView);
+ mDecorView.setIsRootNamespace(true);
// The background owner should be elevated so that it casts a shadow.
mBackgroundView.setElevation(mElevation);
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 5e22650a..d0ad27af 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -20,10 +20,12 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
+import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.LocaleList;
import android.text.Layout;
import android.text.Selection;
@@ -81,6 +83,7 @@ public final class SelectionActionModeHelper {
mEditor = Preconditions.checkNotNull(editor);
mTextView = mEditor.getTextView();
mTextClassificationHelper = new TextClassificationHelper(
+ mTextView.getContext(),
mTextView.getTextClassifier(),
getText(mTextView),
0, 1, mTextView.getTextLocales());
@@ -385,6 +388,7 @@ public final class SelectionActionModeHelper {
private void resetTextClassificationHelper() {
mTextClassificationHelper.init(
+ mTextView.getContext(),
mTextView.getTextClassifier(),
getText(mTextView),
mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -587,7 +591,9 @@ public final class SelectionActionModeHelper {
Preconditions.checkNotNull(textView);
final @SmartSelectionEventTracker.WidgetType int widgetType = textView.isTextEditable()
? SmartSelectionEventTracker.WidgetType.EDITTEXT
- : SmartSelectionEventTracker.WidgetType.TEXTVIEW;
+ : (textView.isTextSelectable()
+ ? SmartSelectionEventTracker.WidgetType.TEXTVIEW
+ : SmartSelectionEventTracker.WidgetType.UNSELECTABLE_TEXTVIEW);
mDelegate = new SmartSelectionEventTracker(textView.getContext(), widgetType);
mEditTextLogger = textView.isTextEditable();
mWordIterator = BreakIterator.getWordInstance(textView.getTextLocale());
@@ -787,6 +793,7 @@ public final class SelectionActionModeHelper {
private static final int TRIM_DELTA = 120; // characters
+ private Context mContext;
private TextClassifier mTextClassifier;
/** The original TextView text. **/
@@ -795,7 +802,10 @@ public final class SelectionActionModeHelper {
private int mSelectionStart;
/** End index relative to mText. */
private int mSelectionEnd;
- private LocaleList mLocales;
+
+ private final TextSelection.Options mSelectionOptions = new TextSelection.Options();
+ private final TextClassification.Options mClassificationOptions =
+ new TextClassification.Options();
/** Trimmed text starting from mTrimStart in mText. */
private CharSequence mTrimmedText;
@@ -816,21 +826,24 @@ public final class SelectionActionModeHelper {
/** Whether the TextClassifier has been initialized. */
private boolean mHot;
- TextClassificationHelper(TextClassifier textClassifier,
+ TextClassificationHelper(Context context, TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
- init(textClassifier, text, selectionStart, selectionEnd, locales);
+ init(context, textClassifier, text, selectionStart, selectionEnd, locales);
}
@UiThread
- public void init(TextClassifier textClassifier,
+ public void init(Context context, TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
+ mContext = Preconditions.checkNotNull(context);
mTextClassifier = Preconditions.checkNotNull(textClassifier);
mText = Preconditions.checkNotNull(text).toString();
mLastClassificationText = null; // invalidate.
Preconditions.checkArgument(selectionEnd > selectionStart);
mSelectionStart = selectionStart;
mSelectionEnd = selectionEnd;
- mLocales = locales;
+ mClassificationOptions.setDefaultLocales(locales);
+ mSelectionOptions.setDefaultLocales(locales)
+ .setDarkLaunchAllowed(true);
}
@WorkerThread
@@ -843,8 +856,16 @@ public final class SelectionActionModeHelper {
public SelectionResult suggestSelection() {
mHot = true;
trimText();
- final TextSelection selection = mTextClassifier.suggestSelection(
- mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
+ final TextSelection selection;
+ if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+ selection = mTextClassifier.suggestSelection(
+ mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
+ } else {
+ // Use old APIs.
+ selection = mTextClassifier.suggestSelection(
+ mTrimmedText, mRelativeStart, mRelativeEnd,
+ mSelectionOptions.getDefaultLocales());
+ }
// Do not classify new selection boundaries if TextClassifier should be dark launched.
if (!mTextClassifier.getSettings().isDarkLaunch()) {
mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
@@ -874,20 +895,28 @@ public final class SelectionActionModeHelper {
if (!Objects.equals(mText, mLastClassificationText)
|| mSelectionStart != mLastClassificationSelectionStart
|| mSelectionEnd != mLastClassificationSelectionEnd
- || !Objects.equals(mLocales, mLastClassificationLocales)) {
+ || !Objects.equals(
+ mClassificationOptions.getDefaultLocales(),
+ mLastClassificationLocales)) {
mLastClassificationText = mText;
mLastClassificationSelectionStart = mSelectionStart;
mLastClassificationSelectionEnd = mSelectionEnd;
- mLastClassificationLocales = mLocales;
+ mLastClassificationLocales = mClassificationOptions.getDefaultLocales();
trimText();
+ final TextClassification classification;
+ if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+ classification = mTextClassifier.classifyText(
+ mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
+ } else {
+ // Use old APIs.
+ classification = mTextClassifier.classifyText(
+ mTrimmedText, mRelativeStart, mRelativeEnd,
+ mClassificationOptions.getDefaultLocales());
+ }
mLastClassificationResult = new SelectionResult(
- mSelectionStart,
- mSelectionEnd,
- mTextClassifier.classifyText(
- mTrimmedText, mRelativeStart, mRelativeEnd, mLocales),
- selection);
+ mSelectionStart, mSelectionEnd, classification, selection);
}
return mLastClassificationResult;
diff --git a/androidx/app/slice/builders/MessagingSliceBuilder.java b/androidx/app/slice/builders/MessagingSliceBuilder.java
new file mode 100644
index 00000000..29189048
--- /dev/null
+++ b/androidx/app/slice/builders/MessagingSliceBuilder.java
@@ -0,0 +1,110 @@
+/*
+ * 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.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;
+
+/**
+ * Builder to construct slice content in a messaging format.
+ */
+public class MessagingSliceBuilder extends TemplateSliceBuilder {
+
+ /**
+ * The maximum number of messages that will be retained in the Slice itself (the
+ * number displayed is up to the platform).
+ */
+ public static final int MAXIMUM_RETAINED_MESSAGES = 50;
+
+ public MessagingSliceBuilder(@NonNull Uri uri) {
+ super(uri);
+ }
+
+ /**
+ * Create a {@link MessageBuilder} that will be added to this slice when
+ * {@link MessageBuilder#finish()} is called.
+ * @return a new message builder
+ */
+ public MessageBuilder startMessage() {
+ return new MessageBuilder(this);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public void apply(Slice.Builder builder) {
+ }
+
+ @Override
+ public void add(SubTemplateSliceBuilder builder) {
+ getBuilder().addSubSlice(builder.build());
+ }
+
+ /**
+ * Builder for adding a message to {@link MessagingSliceBuilder}.
+ */
+ public static final class MessageBuilder
+ extends SubTemplateSliceBuilder<MessagingSliceBuilder> {
+ /**
+ * @hide
+ */
+ @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);
+ return this;
+ }
+
+ /**
+ * Add the text to be used for this message.
+ */
+ public MessageBuilder addText(CharSequence text) {
+ getBuilder().addText(text);
+ return this;
+ }
+
+ /**
+ * Add the time at which this message arrived in ms since Unix epoch
+ */
+ public MessageBuilder addTimestamp(long timestamp) {
+ getBuilder().addTimestamp(timestamp);
+ return this;
+ }
+
+ /**
+ * Complete the construction of this message and add it to the parent builder.
+ * @return the parent builder so construction can continue.
+ */
+ public MessagingSliceBuilder endMessage() {
+ return finish();
+ }
+ }
+}
diff --git a/androidx/app/slice/builders/SliceHints.java b/androidx/app/slice/builders/SliceHints.java
new file mode 100644
index 00000000..5db12193
--- /dev/null
+++ b/androidx/app/slice/builders/SliceHints.java
@@ -0,0 +1,50 @@
+/*
+ * 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.builders;
+
+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
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class SliceHints {
+ /**
+ * Hint to indicate that this content has a toggle action associated with it. To indicate that
+ * the toggle is on, use {@link Slice#HINT_SELECTED}. When the toggle state changes, the intent
+ * associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} which can be
+ * retrieved to see the new state of the toggle.
+ */
+ public static final String HINT_TOGGLE = "toggle";
+
+ /**
+ * Key to retrieve an extra added to an intent when a control is changed.
+ */
+ 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.
+ */
+ public static final String HINT_HIDDEN = "hidden";
+}
diff --git a/androidx/app/slice/builders/TemplateSliceBuilder.java b/androidx/app/slice/builders/TemplateSliceBuilder.java
new file mode 100644
index 00000000..61fea7f9
--- /dev/null
+++ b/androidx/app/slice/builders/TemplateSliceBuilder.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.app.slice.builders;
+
+
+import android.app.slice.Slice;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Base class of builders of various template types.
+ */
+public abstract class TemplateSliceBuilder {
+
+ private final Slice.Builder mSliceBuilder;
+
+ public TemplateSliceBuilder(Uri uri) {
+ mSliceBuilder = new Slice.Builder(uri);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public Slice.Builder getBuilder() {
+ return mSliceBuilder;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public Slice.Builder createChildBuilder() {
+ return new Slice.Builder(mSliceBuilder);
+ }
+
+ /**
+ * Construct the slice.
+ */
+ public Slice build() {
+ apply(mSliceBuilder);
+ return mSliceBuilder.build();
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public abstract void apply(Slice.Builder builder);
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public abstract void add(SubTemplateSliceBuilder builder);
+
+ /**
+ * Base class of builders for sub-slices of {@link TemplateSliceBuilder}s.
+ * @param <T> Type of parent
+ */
+ public abstract static class SubTemplateSliceBuilder<T extends TemplateSliceBuilder> {
+
+ private final Slice.Builder mBuilder;
+ private final T mParent;
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public SubTemplateSliceBuilder(Slice.Builder builder, T parent) {
+ mBuilder = builder;
+ mParent = parent;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public SubTemplateSliceBuilder(Slice.Builder builder) {
+ mBuilder = builder;
+ mParent = null;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public Slice.Builder getBuilder() {
+ return mBuilder;
+ }
+
+ /**
+ * Construct the slice.
+ */
+ public Slice build() {
+ return mBuilder.build();
+ }
+
+ /**
+ * Construct the slice and return to the parent object. If this object was not
+ * created from a {@link TemplateSliceBuilder} it will return null.
+ * @return parent builder
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public T finish() {
+ if (mParent != null) {
+ mParent.add(this);
+ }
+ return mParent;
+ }
+ }
+}
diff --git a/androidx/app/slice/core/SliceQuery.java b/androidx/app/slice/core/SliceQuery.java
new file mode 100644
index 00000000..e1430d05
--- /dev/null
+++ b/androidx/app/slice/core/SliceQuery.java
@@ -0,0 +1,289 @@
+/*
+ * 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.Slice;
+import android.app.slice.SliceItem;
+import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import androidx.app.slice.builders.SliceHints;
+
+/**
+ * Utilities for finding content within a Slice.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SliceQuery {
+
+ /**
+ * @return Whether this item is appropriate to be considered a "start" item, i.e. go in the
+ * 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);
+ }
+
+ /**
+ * @return Finds the first slice that has non-slice children.
+ */
+ public static SliceItem findFirstSlice(SliceItem slice) {
+ if (slice.getType() != SliceItem.TYPE_SLICE) {
+ return slice;
+ }
+ List<SliceItem> items = slice.getSlice().getItems();
+ for (int i = 0; i < items.size(); i++) {
+ if (items.get(i).getType() == SliceItem.TYPE_SLICE) {
+ SliceItem childSlice = items.get(i);
+ return findFirstSlice(childSlice);
+ } else {
+ // Doesn't have slice children so return it
+ return slice;
+ }
+ }
+ // Slices all the way down, just return it
+ return slice;
+ }
+
+ /**
+ * @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) {
+ 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) {
+ hasImage = true;
+ } else if (child.getType() == SliceItem.TYPE_COLOR) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ */
+ public static boolean hasAnyHints(SliceItem item, String... hints) {
+ if (hints == null) return false;
+ List<String> itemHints = item.getHints();
+ for (String hint : hints) {
+ if (itemHints.contains(hint)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ */
+ public static boolean hasHints(SliceItem item, String... hints) {
+ if (hints == null) return true;
+ List<String> itemHints = item.getHints();
+ for (String hint : hints) {
+ if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ */
+ public static boolean hasHints(Slice item, String... hints) {
+ if (hints == null) return true;
+ List<String> itemHints = item.getHints();
+ for (String hint : hints) {
+ if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ */
+ public static SliceItem getPrimaryIcon(Slice slice) {
+ for (SliceItem item : slice.getItems()) {
+ if (item.getType() == SliceItem.TYPE_IMAGE) {
+ 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 (icon != null) {
+ return icon;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ */
+ public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
+ SliceItem ret = null;
+ while (ret == null && list.size() != 0) {
+ SliceItem remove = list.remove(0);
+ if (!contains(container, remove)) {
+ ret = remove;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ */
+ private static boolean contains(SliceItem container, SliceItem item) {
+ if (container == null || item == null) return false;
+ return stream(container).filter(s -> (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(Slice s, int type, String hints, String nonHints) {
+ return findAll(s, type, 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(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(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 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, int type) {
+ return find(s, type, (String[]) null, null);
+ }
+
+ /**
+ */
+ public static SliceItem find(SliceItem s, int type) {
+ return find(s, type, (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(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(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 Stream<SliceItem> stream(SliceItem slice) {
+ Queue<SliceItem> items = new LinkedList();
+ items.add(slice);
+ return getSliceItemStream(items);
+ }
+
+ /**
+ */
+ public static Stream<SliceItem> stream(Slice slice) {
+ Queue<SliceItem> items = new LinkedList();
+ items.addAll(slice.getItems());
+ return getSliceItemStream(items);
+ }
+
+ /**
+ */
+ private static Stream<SliceItem> getSliceItemStream(Queue<SliceItem> items) {
+ Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
+ @Override
+ public boolean hasNext() {
+ return items.size() != 0;
+ }
+
+ @Override
+ public SliceItem next() {
+ SliceItem item = items.poll();
+ if (item.getType() == SliceItem.TYPE_SLICE
+ || item.getType() == SliceItem.TYPE_ACTION) {
+ items.addAll(item.getSlice().getItems());
+ }
+ return item;
+ }
+ };
+ return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+ }
+}
diff --git a/androidx/app/slice/widget/ActionRow.java b/androidx/app/slice/widget/ActionRow.java
new file mode 100644
index 00000000..0a09620e
--- /dev/null
+++ b/androidx/app/slice/widget/ActionRow.java
@@ -0,0 +1,204 @@
+/*
+ * 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.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.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.support.annotation.RestrictTo;
+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;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+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/androidx/app/slice/widget/GridView.java b/androidx/app/slice/widget/GridView.java
new file mode 100644
index 00000000..c320c728
--- /dev/null
+++ b/androidx/app/slice/widget/GridView.java
@@ -0,0 +1,185 @@
+/*
+ * 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.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.content.Context;
+import android.graphics.Color;
+import android.support.annotation.RestrictTo;
+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 java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class GridView extends LinearLayout implements LargeSliceAdapter.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(height, MeasureSpec.EXACTLY);
+ 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.abc_slice_more_content, numExtra));
+ v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+ v.setGravity(Gravity.CENTER);
+ frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ addView(frame);
+ }
+
+ private boolean isFull() {
+ return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
+ }
+
+ /**
+ * Returns true if this item is just an image.
+ */
+ private boolean addItem(SliceItem item) {
+ if (item.getType() == SliceItem.TYPE_IMAGE) {
+ ImageView v = new ImageView(getContext());
+ v.setImageIcon(item.getIcon());
+ v.setScaleType(ScaleType.CENTER_CROP);
+ addView(v, new LayoutParams(0, MATCH_PARENT, 1));
+ return true;
+ } else {
+ LinearLayout v = new LinearLayout(getContext());
+ int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ 12, getContext().getResources().getDisplayMetrics());
+ v.setPadding(0, s, 0, 0);
+ v.setOrientation(LinearLayout.VERTICAL);
+ v.setGravity(Gravity.CENTER_HORIZONTAL);
+ // TODO: Unify sporadic inflates that happen throughout the code.
+ ArrayList<SliceItem> items = new ArrayList<>();
+ if (item.getType() == SliceItem.TYPE_SLICE) {
+ items.addAll(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;
+ }
+ });
+ addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
+ return false;
+ }
+ }
+}
diff --git a/androidx/app/slice/widget/LargeSliceAdapter.java b/androidx/app/slice/widget/LargeSliceAdapter.java
new file mode 100644
index 00000000..ff513c5f
--- /dev/null
+++ b/androidx/app/slice/widget/LargeSliceAdapter.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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.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;
+
+ 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);
+ }
+ }
+
+ private View inflateForType(int viewType) {
+ switch (viewType) {
+ case TYPE_GRID:
+ return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null);
+ case TYPE_MESSAGE:
+ return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null);
+ case TYPE_MESSAGE_LOCAL:
+ return LayoutInflater.from(mContext).inflate(R.layout.abc_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.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 RecyclerView.ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+ */
+ public static class SliceViewHolder extends RecyclerView.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_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/androidx/app/slice/widget/LargeTemplateView.java b/androidx/app/slice/widget/LargeTemplateView.java
new file mode 100644
index 00000000..c5e82aaf
--- /dev/null
+++ b/androidx/app/slice/widget/LargeTemplateView.java
@@ -0,0 +1,121 @@
+/*
+ * 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.widget;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class LargeTemplateView extends SliceView.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 @SliceView.SliceMode int 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 && SliceQuery.hasHints(mSlice, 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 (SliceQuery.hasHints(slice, 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);
+ }
+ });
+ }
+ mAdapter.setSliceItems(items, color);
+ }
+
+ private void addList(Slice slice, List<SliceItem> items) {
+ List<SliceItem> sliceItems = slice.getItems();
+ items.addAll(sliceItems);
+ }
+
+ /**
+ * 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/androidx/app/slice/widget/MessageView.java b/androidx/app/slice/widget/MessageView.java
new file mode 100644
index 00000000..9db35bb3
--- /dev/null
+++ b/androidx/app/slice/widget/MessageView.java
@@ -0,0 +1,79 @@
+/*
+ * 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.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.RestrictTo;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class MessageView extends LinearLayout implements LargeSliceAdapter.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/androidx/app/slice/widget/RemoteInputView.java b/androidx/app/slice/widget/RemoteInputView.java
new file mode 100644
index 00000000..9d45501d
--- /dev/null
+++ b/androidx/app/slice/widget/RemoteInputView.java
@@ -0,0 +1,423 @@
+/*
+ * 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.widget;
+
+import android.animation.Animator;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.RestrictTo;
+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 androidx.app.slice.view.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+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
+ && 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;
+
+ // TODO: Figure out API for telling the system about slice interaction.
+ // 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(getContext(), 0, fillInIntent);
+ reset();
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Unable to send remote input result", e);
+ Toast.makeText(getContext(), "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.abc_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();
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ 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 = getScrollY();
+ r.bottom = getScrollY() + (getBottom() - getTop());
+ }
+
+ @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 = getContext().getSystemService(
+ InputMethodManager.class);
+ 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);
+ }
+
+ }
+ }
+
+ /** Whether key will, by default, trigger a click on the focused view.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public static final boolean isConfirmKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_NUMPAD_ENTER:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/androidx/app/slice/widget/ShortcutView.java b/androidx/app/slice/widget/ShortcutView.java
new file mode 100644
index 00000000..b6e77cd0
--- /dev/null
+++ b/androidx/app/slice/widget/ShortcutView.java
@@ -0,0 +1,174 @@
+/*
+ * 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.widget;
+
+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;
+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 android.support.annotation.RestrictTo;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class ShortcutView extends androidx.app.slice.widget.SliceView.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.abc_slice_icon_size);
+ mLargeIconSize = res.getDimensionPixelSize(R.dimen.abc_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)
+ || mIcon.hasHint(Slice.HINT_SOURCE);
+ 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 @SliceView.SliceMode int 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) {
+ 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(slice, 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(slice, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+ null);
+ }
+ if (mLabel == null) {
+ mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ null);
+ }
+ // Second fallback: first image and text
+ if (mIcon == null) {
+ mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, (String) null,
+ null);
+ }
+ if (mLabel == null) {
+ mLabel = SliceQuery.find(slice, 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) {
+ Slice.Builder sb = new Slice.Builder(slice.getUri());
+ Drawable icon = pm.getApplicationIcon(appInfo);
+ sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), Slice.HINT_LARGE);
+ mIcon = sb.build().getItems().get(0);
+ }
+ if (mLabel == null) {
+ Slice.Builder sb = new Slice.Builder(slice.getUri());
+ sb.addText(pm.getApplicationLabel(appInfo));
+ mLabel = sb.build().getItems().get(0);
+ }
+ if (mAction == null) {
+ mAction = PendingIntent.getActivity(context, 0,
+ pm.getLaunchIntentForPackage(appInfo.packageName), 0);
+ }
+ }
+ }
+ }
+}
diff --git a/androidx/app/slice/widget/SliceLiveData.java b/androidx/app/slice/widget/SliceLiveData.java
new file mode 100644
index 00000000..eaaef502
--- /dev/null
+++ b/androidx/app/slice/widget/SliceLiveData.java
@@ -0,0 +1,76 @@
+/*
+ * 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.widget;
+
+import android.app.slice.Slice;
+import android.arch.lifecycle.LiveData;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+
+/**
+ * Class with factory methods for creating LiveData that observes slices.
+ *
+ * @see #fromUri(Context, Uri)
+ * @see LiveData
+ */
+public final class SliceLiveData {
+
+ /**
+ * Produces an {@link LiveData} that tracks a Slice for a given Uri. 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> fromUri(Context context, Uri uri) {
+ return new SliceLiveDataImpl(context.getApplicationContext(), uri);
+ }
+
+ private static class SliceLiveDataImpl extends LiveData<Slice> {
+ private final Uri mUri;
+ private final Context mContext;
+
+ private SliceLiveDataImpl(Context context, Uri uri) {
+ super();
+ mContext = context;
+ mUri = uri;
+ // TODO: Check if uri points at a Slice?
+ }
+
+ @Override
+ protected void onActive() {
+ AsyncTask.execute(this::updateSlice);
+ mContext.getContentResolver().registerContentObserver(mUri, false, mObserver);
+ }
+
+ @Override
+ protected void onInactive() {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ private void updateSlice() {
+ postValue(Slice.bindSlice(mContext.getContentResolver(), mUri));
+ }
+
+ private final ContentObserver mObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ AsyncTask.execute(SliceLiveDataImpl.this::updateSlice);
+ }
+ };
+ }
+}
diff --git a/androidx/app/slice/widget/SliceView.java b/androidx/app/slice/widget/SliceView.java
new file mode 100644
index 00000000..6e12dbab
--- /dev/null
+++ b/androidx/app/slice/widget/SliceView.java
@@ -0,0 +1,312 @@
+/*
+ * 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.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.arch.lifecycle.Observer;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * 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>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * LiveData<Slice> liveData = SliceLiveData.fromUri(sliceUri);
+ * liveData.observe(lifecycleOwner, v);
+ * </pre>
+ * @see SliceLiveData
+ */
+public class SliceView extends ViewGroup implements Observer<Slice> {
+
+ private static final String TAG = "SliceView";
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public abstract static class SliceModeView extends FrameLayout {
+
+ public SliceModeView(Context context) {
+ super(context);
+ }
+
+ /**
+ * @return the mode of the slice being presented.
+ */
+ public abstract int getMode();
+
+ /**
+ * @param slice the slice to show in this view.
+ */
+ public abstract void setSlice(Slice slice);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @IntDef({
+ MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+ })
+ public @interface SliceMode {}
+
+ /**
+ * Mode indicating this slice should be presented in small template format.
+ */
+ public static final int MODE_SMALL = 1;
+ /**
+ * Mode indicating this slice should be presented in large template format.
+ */
+ 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.
+ */
+ public static final int MODE_SHORTCUT = 3;
+
+ /**
+ * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+ * that selection.
+ */
+ private static final int MODE_AUTO = 0;
+
+ private int mMode = MODE_AUTO;
+ private SliceModeView mCurrentView;
+ private final ActionRow mActions;
+ private Slice mCurrentSlice;
+ private boolean mShowActions = true;
+ private boolean mIsScrollable;
+ 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);
+ mActions = new ActionRow(getContext(), true);
+ mActions.setBackground(new ColorDrawable(0xffeeeeee));
+ mCurrentView = new LargeTemplateView(getContext());
+ addView(mCurrentView, getChildLp(mCurrentView));
+ addView(mActions, getChildLp(mActions));
+ mShortcutSize = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int mode = MeasureSpec.getMode(widthMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ if (MODE_SHORTCUT == mMode) {
+ width = mShortcutSize;
+ }
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.UNSPECIFIED) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+ }
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ int actionHeight = mActions.getVisibility() != View.GONE
+ ? mActions.getMeasuredHeight()
+ : 0;
+ int newHeightSpec = MeasureSpec.makeMeasureSpec(
+ mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
+ setMeasuredDimension(width, newHeightSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mCurrentView.layout(0, 0, mCurrentView.getMeasuredWidth(),
+ mCurrentView.getMeasuredHeight());
+ if (mActions.getVisibility() != View.GONE) {
+ mActions.layout(0, mCurrentView.getMeasuredHeight(), mActions.getMeasuredWidth(),
+ mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public void onChanged(@Nullable Slice slice) {
+ setSlice(slice);
+ }
+
+ /**
+ * Populates this view to the provided {@link Slice}.
+ *
+ * This will not update automatically if the slice content changes, for live
+ * content see {@link SliceLiveData}.
+ */
+ public void setSlice(@Nullable Slice slice) {
+ mCurrentSlice = slice;
+ reinflate();
+ }
+
+ /**
+ * Set the mode this view should present in.
+ */
+ public void setMode(@SliceMode int 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
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public void setMode(@SliceMode int 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 int getMode() {
+ if (mMode == MODE_AUTO) {
+ return MODE_LARGE;
+ }
+ return mMode;
+ }
+
+ /**
+ * @hide
+ *
+ * Whether this view should show a row of actions with it.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public void setShowActionRow(boolean show) {
+ mShowActions = show;
+ reinflate();
+ }
+
+ private SliceModeView createView(int mode) {
+ switch (mode) {
+ case MODE_SHORTCUT:
+ return new ShortcutView(getContext());
+ case MODE_SMALL:
+ return new SmallTemplateView(getContext());
+ }
+ return new LargeTemplateView(getContext());
+ }
+
+ 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,
+ null);
+ int mode = getMode();
+ if (mode != mCurrentView.getMode()) {
+ removeAllViews();
+ mCurrentView = createView(mode);
+ addView(mCurrentView, getChildLp(mCurrentView));
+ addView(mActions, getChildLp(mActions));
+ }
+ if (mode == 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 != 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);
+ }
+ }
+}
diff --git a/androidx/app/slice/widget/SliceViewUtil.java b/androidx/app/slice/widget/SliceViewUtil.java
new file mode 100644
index 00000000..62844c1b
--- /dev/null
+++ b/androidx/app/slice/widget/SliceViewUtil.java
@@ -0,0 +1,199 @@
+/*
+ * 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.widget;
+
+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.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.text.format.DateUtils;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.Calendar;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SliceViewUtil {
+
+ /**
+ */
+ @ColorInt
+ public static int getColorAccent(@NonNull Context context) {
+ return getColorAttr(context, android.R.attr.colorAccent);
+ }
+
+ /**
+ */
+ @ColorInt
+ public static int getColorError(@NonNull Context context) {
+ return getColorAttr(context, android.R.attr.colorError);
+ }
+
+ /**
+ */
+ @ColorInt
+ public static int getDefaultColor(@NonNull Context context, int resId) {
+ final ColorStateList list = context.getResources().getColorStateList(resId,
+ context.getTheme());
+
+ return list.getDefaultColor();
+ }
+
+ /**
+ */
+ @ColorInt
+ public static int getDisabled(@NonNull Context context, int inputColor) {
+ return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+ }
+
+ /**
+ */
+ @ColorInt
+ public static int applyAlphaAttr(@NonNull Context context, @AttrRes int attr, int inputColor) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ float alpha = ta.getFloat(0, 0);
+ ta.recycle();
+ return applyAlpha(alpha, inputColor);
+ }
+
+ /**
+ */
+ @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));
+ }
+
+ /**
+ */
+ @ColorInt
+ public static int getColorAttr(@NonNull Context context, @AttrRes int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ @ColorInt int colorAccent = ta.getColor(0, 0);
+ ta.recycle();
+ return colorAccent;
+ }
+
+ /**
+ */
+ public static int getThemeAttr(@NonNull Context context, @AttrRes int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ int theme = ta.getResourceId(0, 0);
+ ta.recycle();
+ return theme;
+ }
+
+ /**
+ */
+ public static Drawable getDrawable(@NonNull Context context, @AttrRes int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ Drawable drawable = ta.getDrawable(0);
+ ta.recycle();
+ return drawable;
+ }
+
+ /**
+ */
+ 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);
+ }
+
+ /**
+ */
+ public static void createCircledIcon(@NonNull Context context, int color, int iconSizePx,
+ 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(iconSizePx, iconSizePx, Config.ARGB_8888);
+ Canvas iconCanvas = new Canvas(iconBm);
+ v.layout(0, 0, iconSizePx, iconSizePx);
+ v.draw(iconCanvas);
+ v.setImageBitmap(getCircularBitmap(iconBm));
+ } else {
+ v.setColorFilter(Color.WHITE);
+ }
+ lp.width = iconSizePx;
+ lp.height = iconSizePx;
+ lp.gravity = Gravity.CENTER;
+ }
+
+ /**
+ */
+ public static @NonNull 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;
+ }
+
+ /**
+ */
+ public static CharSequence getRelativeTimeString(long time) {
+ return DateUtils.getRelativeTimeSpanString(time, Calendar.getInstance().getTimeInMillis(),
+ DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
+ }
+}
diff --git a/androidx/app/slice/widget/SmallTemplateView.java b/androidx/app/slice/widget/SmallTemplateView.java
new file mode 100644
index 00000000..f430602d
--- /dev/null
+++ b/androidx/app/slice/widget/SmallTemplateView.java
@@ -0,0 +1,367 @@
+/*
+ * 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.widget;
+
+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.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.builders.SliceHints;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * Small template is also used to construct list items for use with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SmallTemplateView extends SliceView.SliceModeView implements
+ LargeSliceAdapter.SliceListView, View.OnClickListener {
+
+ private static final String TAG = "SmallTemplateView";
+
+ // The number of items that fit on the right hand side of a small slice
+ private static final int MAX_END_ITEMS = 3;
+
+ private SliceItem mColorItem;
+
+ private int mIconSize;
+ private int mPadding;
+
+ private LinearLayout mStartContainer;
+ private LinearLayout mContent;
+ private TextView mPrimaryText;
+ private TextView mSecondaryText;
+ private LinearLayout mEndContainer;
+
+ private SliceItem mRowAction;
+ private View mDivider;
+ private Switch mToggle;
+
+ public SmallTemplateView(Context context) {
+ super(context);
+ mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+ 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);
+ mContent = (LinearLayout) findViewById(android.R.id.content);
+ mPrimaryText = (TextView) findViewById(android.R.id.title);
+ mSecondaryText = (TextView) findViewById(android.R.id.summary);
+ mDivider = findViewById(R.id.divider);
+ mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+ }
+
+ @Override
+ public @SliceView.SliceMode int getMode() {
+ return SliceView.MODE_SMALL;
+ }
+
+ @Override
+ public void setColor(SliceItem color) {
+ mColorItem = color;
+ }
+
+
+ @Override
+ public void setSliceItem(SliceItem slice) {
+ populateViews(slice, slice);
+ }
+ @Override
+ public void setSlice(Slice slice) {
+ Slice.Builder sb = new Slice.Builder(slice.getUri());
+ sb.addSubSlice(slice);
+ Slice parentSlice = sb.build();
+ populateViews(parentSlice.getItems().get(0), getFirstSlice(slice));
+ }
+
+ private SliceItem getFirstSlice(Slice slice) {
+ List<SliceItem> items = slice.getItems();
+ if (items.size() > 0 && items.get(0).getType() == SliceItem.TYPE_SLICE) {
+ // Check if this slice is appropriate to use to populate small template
+ SliceItem firstSlice = items.get(0);
+ if (firstSlice.hasHint(Slice.HINT_LIST)) {
+ // Check for header, use that if it exists
+ SliceItem header = SliceQuery.find(firstSlice, SliceItem.TYPE_SLICE,
+ null,
+ new String[] {
+ Slice.HINT_LIST_ITEM, Slice.HINT_LIST
+ });
+ if (header != null) {
+ return SliceQuery.findFirstSlice(header);
+ } else {
+ // Otherwise use the first list item
+ SliceItem newFirst = firstSlice.getSlice().getItems().get(0);
+ return SliceQuery.findFirstSlice(newFirst);
+ }
+ } else {
+ // Not a list, find first slice with non-slice children
+ return SliceQuery.findFirstSlice(firstSlice);
+ }
+ }
+ // Get it as a SliceItem type slice
+ Slice.Builder sb = new Slice.Builder(slice.getUri());
+ Slice s = sb.addSubSlice(slice).build();
+ return s.getItems().get(0);
+ }
+
+ private void populateViews(SliceItem fullSlice, SliceItem sliceItem) {
+ resetViews();
+ ArrayList<SliceItem> items = new ArrayList<>();
+ if (sliceItem.getType() == SliceItem.TYPE_SLICE) {
+ items = new ArrayList<>(sliceItem.getSlice().getItems());
+ } else {
+ items.add(sliceItem);
+ }
+
+ // These are the things that can go in our small template
+ SliceItem startItem = null;
+ SliceItem titleItem = null;
+ SliceItem subTitle = null;
+ ArrayList<SliceItem> endItems = new ArrayList<>();
+
+ // 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 (!SliceQuery.isSimpleAction(firstSlice)) {
+ mRowAction = firstSlice;
+ items.remove(0);
+ // Populating with first action, bias to use slice associated with this action
+ items.addAll(0, mRowAction.getSlice().getItems());
+ } else {
+ // It's simple so maybe it's a start item
+ startItem = items.get(0);
+ }
+ }
+
+ // Look through our items and try to figure out main content
+ 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)) {
+ // Things with these hints could go in the title / start position
+ if ((startItem == null || !startItem.hasHint(Slice.HINT_TITLE))
+ && SliceQuery.isStartType(item)) {
+ startItem = item;
+ } else if ((titleItem == null || !titleItem.hasHint(Slice.HINT_TITLE))
+ && itemType == SliceItem.TYPE_TEXT) {
+ titleItem = item;
+ } else {
+ endItems.add(item);
+ }
+ } else if (item.getType() == SliceItem.TYPE_TEXT) {
+ if (titleItem == null) {
+ titleItem = item;
+ } else if (subTitle == null) {
+ subTitle = item;
+ } else {
+ endItems.add(item);
+ }
+ } else if (item.getType() == SliceItem.TYPE_SLICE) {
+ List<SliceItem> subItems = item.getSlice().getItems();
+ for (int j = 0; j < subItems.size(); j++) {
+ endItems.add(subItems.get(j));
+ }
+ } else {
+ endItems.add(item);
+ }
+ }
+
+ // Populate main part of the template
+ if (startItem != null) {
+ // TODO - check for icon, timestamp, action with icon
+ }
+ if (titleItem != null) {
+ mPrimaryText.setText(titleItem.getText());
+ }
+ mPrimaryText.setVisibility(titleItem != null ? View.VISIBLE : View.GONE);
+ if (subTitle != null) {
+ mSecondaryText.setText(subTitle.getText());
+ }
+ mSecondaryText.setVisibility(subTitle != null ? View.VISIBLE : View.GONE);
+
+ // Figure out what end items we're showing
+ // If we're showing an action in this row check if it's a toggle
+ if (mRowAction != null && SliceQuery.hasHints(mRowAction.getSlice(), SliceHints.HINT_TOGGLE)
+ && addToggle(mRowAction)) {
+ // Can't show more end actions if we have a toggle so we're done
+ makeClickable(this);
+ return;
+ }
+ // 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)))
+ .findFirst().orElse(null);
+ if (toggleItem != null) {
+ if (addToggle(toggleItem)) {
+ mDivider.setVisibility(mRowAction != null ? View.VISIBLE : View.GONE);
+ makeClickable(mRowAction != null ? mContent : this);
+ // Can't show more end actions if we have a toggle so we're done
+ return;
+ }
+ }
+ // If we're here we can still show end items
+ SliceItem colorItem = SliceQuery.find(fullSlice, SliceItem.TYPE_COLOR);
+ int color = colorItem != null
+ ? colorItem.getColor()
+ : (mColorItem != null)
+ ? mColorItem.getColor()
+ : -1;
+ boolean clickableEndItem = false;
+ int itemCount = 0;
+ 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 (SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE)) {
+ if (addToggle(item)) {
+ break;
+ }
+ }
+ if (addIcon(item, color, mEndContainer)) {
+ clickableEndItem = true;
+ 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(SliceViewUtil.getRelativeTimeString(item.getTimestamp()));
+ mEndContainer.addView(tv);
+ itemCount++;
+ }
+ }
+ }
+ if (mRowAction != null) {
+ makeClickable(clickableEndItem ? mContent : this);
+ }
+ }
+
+ /**
+ * @return Whether a toggle was added.
+ */
+ private boolean addToggle(SliceItem toggleItem) {
+ if (toggleItem.getType() != SliceItem.TYPE_ACTION
+ || !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);
+ }
+ });
+ return true;
+ }
+
+ /**
+ * @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;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mRowAction != null && mRowAction.getType() == SliceItem.TYPE_ACTION) {
+ 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);
+ }
+ });
+ }
+ }
+
+ private void makeClickable(View layout) {
+ layout.setOnClickListener(this);
+ layout.setBackground(SliceViewUtil.getDrawable(getContext(),
+ android.R.attr.selectableItemBackground));
+ }
+
+ private void resetViews() {
+ mStartContainer.removeAllViews();
+ mEndContainer.removeAllViews();
+ mPrimaryText.setText(null);
+ mSecondaryText.setText(null);
+ }
+}
diff --git a/androidx/recyclerview/selection/ActivationCallbacks.java b/androidx/recyclerview/selection/ActivationCallbacks.java
new file mode 100644
index 00000000..606f35a7
--- /dev/null
+++ b/androidx/recyclerview/selection/ActivationCallbacks.java
@@ -0,0 +1,55 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ActivationCallbacks<K> {
+
+ static <K> ActivationCallbacks<K> dummy() {
+ return new ActivationCallbacks<K>() {
+ @Override
+ public boolean onItemActivated(ItemDetails item, MotionEvent e) {
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Called when an item is activated. An item is activitated, for example, when
+ * there is no active selection and the user double clicks an item with a
+ * pointing device like a Mouse.
+ *
+ * @param item details of the item.
+ * @param e the event associated with item.
+ * @return true if the event was handled.
+ */
+ public abstract boolean onItemActivated(ItemDetails<K> item, MotionEvent e);
+}
diff --git a/androidx/recyclerview/selection/AutoScroller.java b/androidx/recyclerview/selection/AutoScroller.java
new file mode 100644
index 00000000..13e87bd0
--- /dev/null
+++ b/androidx/recyclerview/selection/AutoScroller.java
@@ -0,0 +1,42 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Point;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Provides support for auto-scrolling a view.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class AutoScroller {
+
+ /**
+ * Resets state of the scroller. Call this when the user activity that is driving
+ * auto-scrolling is done.
+ */
+ protected abstract void reset();
+
+ /**
+ * Processes a new input location.
+ * @param location
+ */
+ protected abstract void scroll(Point location);
+}
diff --git a/androidx/recyclerview/selection/BandPredicate.java b/androidx/recyclerview/selection/BandPredicate.java
new file mode 100644
index 00000000..9a5ae477
--- /dev/null
+++ b/androidx/recyclerview/selection/BandPredicate.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Provides a means of controlling when and where band selection can be initiated.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class BandPredicate {
+
+ /** @return true if band selection can be initiated in response to the {@link MotionEvent}. */
+ public abstract boolean canInitiate(MotionEvent e);
+
+ private static boolean hasSupportedLayoutManager(RecyclerView recView) {
+ RecyclerView.LayoutManager lm = recView.getLayoutManager();
+ return lm instanceof GridLayoutManager
+ || lm instanceof LinearLayoutManager;
+ }
+
+ /**
+ * Creates a new band predicate that permits initiation of band on areas
+ * of a RecyclerView that map to RecyclerView.NO_POSITION.
+ *
+ * @param recView
+ * @return
+ */
+ @SuppressWarnings("unused")
+ public static BandPredicate noPosition(RecyclerView recView) {
+ return new NoPosition(recView);
+ }
+
+ /**
+ * Creates a new band predicate that permits initiation of band
+ * anywhere doesn't correspond to a draggable region of a item.
+ *
+ * @param detailsLookup
+ * @return
+ */
+ public static BandPredicate notDraggable(
+ RecyclerView recView, ItemDetailsLookup detailsLookup) {
+ return new NotDraggable(recView, detailsLookup);
+ }
+
+ /**
+ * A BandPredicate that allows initiation of band selection only in areas of RecyclerView
+ * that have {@link RecyclerView#NO_POSITION}. In most cases, this will be the empty areas
+ * between views.
+ */
+ private static final class NoPosition extends BandPredicate {
+
+ private final RecyclerView mRecView;
+
+ NoPosition(RecyclerView recView) {
+ checkArgument(recView != null);
+
+ mRecView = recView;
+ }
+
+ @Override
+ public boolean canInitiate(MotionEvent e) {
+ if (!hasSupportedLayoutManager(mRecView)
+ || mRecView.hasPendingAdapterUpdates()) {
+ return false;
+ }
+
+ View itemView = mRecView.findChildViewUnder(e.getX(), e.getY());
+ int position = itemView != null
+ ? mRecView.getChildAdapterPosition(itemView)
+ : RecyclerView.NO_POSITION;
+
+ return position == RecyclerView.NO_POSITION;
+ }
+ }
+
+ /**
+ * A BandPredicate that allows initiation of band selection in any area that is not
+ * draggable as determined by consulting
+ * {@link ItemDetailsLookup#inItemDragRegion(MotionEvent)}.
+ */
+ private static final class NotDraggable extends BandPredicate {
+
+ private final RecyclerView mRecView;
+ private final ItemDetailsLookup mDetailsLookup;
+
+ NotDraggable(RecyclerView recView, ItemDetailsLookup detailsLookup) {
+ checkArgument(recView != null);
+ checkArgument(detailsLookup != null);
+
+ mRecView = recView;
+ mDetailsLookup = detailsLookup;
+ }
+
+ @Override
+ public boolean canInitiate(MotionEvent e) {
+ if (!hasSupportedLayoutManager(mRecView)
+ || mRecView.hasPendingAdapterUpdates()) {
+ return false;
+ }
+
+ @Nullable ItemDetailsLookup.ItemDetails details = mDetailsLookup.getItemDetails(e);
+ return (details == null) || !details.inDragRegion(e);
+ }
+ }
+}
diff --git a/androidx/recyclerview/selection/BandSelectionHelper.java b/androidx/recyclerview/selection/BandSelectionHelper.java
new file mode 100644
index 00000000..5362e2b5
--- /dev/null
+++ b/androidx/recyclerview/selection/BandSelectionHelper.java
@@ -0,0 +1,355 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Provides mouse driven band-selection support when used in conjunction with a {@link RecyclerView}
+ * instance. This class is responsible for rendering a band overlay and manipulating selection
+ * status of the items it intersects with.
+ *
+ * <p> Given the recycling nature of RecyclerView items that have scrolled off-screen would not
+ * be selectable with a band that itself was partially rendered off-screen. To address this,
+ * BandSelectionController builds a model of the list/grid information presented by RecyclerView as
+ * the user interacts with items using their pointer (and the band). Selectable items that intersect
+ * with the band, both on and off screen, are selected on pointer up.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+class BandSelectionHelper<K> implements OnItemTouchListener {
+
+ static final String TAG = "BandSelectionHelper";
+ static final boolean DEBUG = false;
+
+ private final BandHost mHost;
+ private final ItemKeyProvider<K> mKeyProvider;
+ private final SelectionHelper<K> mSelectionHelper;
+ private final SelectionPredicate<K> mSelectionPredicate;
+ private final BandPredicate mBandPredicate;
+ private final FocusCallbacks<K> mFocusCallbacks;
+ private final ContentLock mLock;
+ private final AutoScroller mScroller;
+ private final GridModel.SelectionObserver mGridObserver;
+
+ private @Nullable Point mCurrentPosition;
+ private @Nullable Point mOrigin;
+ private @Nullable GridModel mModel;
+
+ /**
+ * See {@link BandSelectionHelper#create}.
+ */
+ BandSelectionHelper(
+ BandHost host,
+ AutoScroller scroller,
+ ItemKeyProvider<K> keyProvider,
+ SelectionHelper<K> selectionHelper,
+ SelectionPredicate<K> selectionPredicate,
+ BandPredicate bandPredicate,
+ FocusCallbacks<K> focusCallbacks,
+ ContentLock lock) {
+
+ checkArgument(host != null);
+ checkArgument(scroller != null);
+ checkArgument(keyProvider != null);
+ checkArgument(selectionHelper != null);
+ checkArgument(selectionPredicate != null);
+ checkArgument(bandPredicate != null);
+ checkArgument(focusCallbacks != null);
+ checkArgument(lock != null);
+
+ mHost = host;
+ mKeyProvider = keyProvider;
+ mSelectionHelper = selectionHelper;
+ mSelectionPredicate = selectionPredicate;
+ mBandPredicate = bandPredicate;
+ mFocusCallbacks = focusCallbacks;
+ mLock = lock;
+
+ mHost.addOnScrollListener(
+ new OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ BandSelectionHelper.this.onScrolled(recyclerView, dx, dy);
+ }
+ });
+
+ mScroller = scroller;
+
+ mGridObserver = new GridModel.SelectionObserver<K>() {
+ @Override
+ public void onSelectionChanged(Set<K> updatedSelection) {
+ mSelectionHelper.setProvisionalSelection(updatedSelection);
+ }
+ };
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @return new BandSelectionHelper instance.
+ */
+ static <K> BandSelectionHelper create(
+ RecyclerView recView,
+ AutoScroller scroller,
+ @DrawableRes int bandOverlayId,
+ ItemKeyProvider<K> keyProvider,
+ SelectionHelper<K> selectionHelper,
+ SelectionPredicate<K> selectionPredicate,
+ BandPredicate bandPredicate,
+ FocusCallbacks<K> focusCallbacks,
+ ContentLock lock) {
+
+ return new BandSelectionHelper<>(
+ new DefaultBandHost<>(recView, bandOverlayId, keyProvider, selectionPredicate),
+ scroller,
+ keyProvider,
+ selectionHelper,
+ selectionPredicate,
+ bandPredicate,
+ focusCallbacks,
+ lock);
+ }
+
+ @VisibleForTesting
+ boolean isActive() {
+ boolean active = mModel != null;
+ if (DEBUG && active) {
+ mLock.checkLocked();
+ }
+ return active;
+ }
+
+ /**
+ * Clients must call reset when there are any material changes to the layout of items
+ * in RecyclerView.
+ */
+ void reset() {
+ if (!isActive()) {
+ return;
+ }
+
+ mHost.hideBand();
+ if (mModel != null) {
+ mModel.stopCapturing();
+ mModel.onDestroy();
+ }
+
+ mModel = null;
+ mOrigin = null;
+
+ mScroller.reset();
+ mLock.unblock();
+ }
+
+ @VisibleForTesting
+ boolean shouldStart(MotionEvent e) {
+ // b/30146357 && b/23793622. onInterceptTouchEvent does not dispatch events to onTouchEvent
+ // unless the event is != ACTION_DOWN. Thus, we need to actually start band selection when
+ // mouse moves.
+ return MotionEvents.isPrimaryButtonPressed(e)
+ && MotionEvents.isActionMove(e)
+ && mBandPredicate.canInitiate(e)
+ && !isActive();
+ }
+
+ @VisibleForTesting
+ boolean shouldStop(MotionEvent e) {
+ return isActive()
+ && (MotionEvents.isActionUp(e)
+ || MotionEvents.isActionPointerUp(e)
+ || MotionEvents.isActionCancel(e));
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) {
+ if (shouldStart(e)) {
+ startBandSelect(e);
+ } else if (shouldStop(e)) {
+ endBandSelect();
+ }
+
+ return isActive();
+ }
+
+ /**
+ * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
+ */
+ @Override
+ public void onTouchEvent(RecyclerView unused, MotionEvent e) {
+ if (shouldStop(e)) {
+ endBandSelect();
+ return;
+ }
+
+ // We shouldn't get any events in this method when band select is not active,
+ // but it turns some guests show up late to the party.
+ // Probably happening when a re-layout is happening to the ReyclerView (ie. Pull-To-Refresh)
+ if (!isActive()) {
+ return;
+ }
+
+ if (DEBUG) {
+ checkArgument(MotionEvents.isActionMove(e));
+ checkState(mModel != null);
+ }
+
+ mCurrentPosition = MotionEvents.getOrigin(e);
+
+ mModel.resizeSelection(mCurrentPosition);
+
+ resizeBand();
+ mScroller.scroll(mCurrentPosition);
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ }
+
+ /**
+ * Starts band select by adding the drawable to the RecyclerView's overlay.
+ */
+ private void startBandSelect(MotionEvent e) {
+ checkState(!isActive());
+
+ if (!MotionEvents.isCtrlKeyPressed(e)) {
+ mSelectionHelper.clearSelection();
+ }
+
+ Point origin = MotionEvents.getOrigin(e);
+ if (DEBUG) Log.d(TAG, "Starting band select @ " + origin);
+
+ mModel = mHost.createGridModel();
+ mModel.addOnSelectionChangedListener(mGridObserver);
+
+ mLock.block();
+ mFocusCallbacks.clearFocus();
+ mOrigin = origin;
+ // NOTE: Pay heed that resizeBand modifies the y coordinates
+ // in onScrolled. Not sure if model expects this. If not
+ // it should be defending against this.
+ mModel.startCapturing(mOrigin);
+ }
+
+ /**
+ * Resizes the band select rectangle by using the origin and the current pointer position as
+ * two opposite corners of the selection.
+ */
+ private void resizeBand() {
+ Rect bounds = new Rect(Math.min(mOrigin.x, mCurrentPosition.x),
+ Math.min(mOrigin.y, mCurrentPosition.y),
+ Math.max(mOrigin.x, mCurrentPosition.x),
+ Math.max(mOrigin.y, mCurrentPosition.y));
+
+ if (VERBOSE) Log.v(TAG, "Resizing band! " + bounds);
+ mHost.showBand(bounds);
+ }
+
+ /**
+ * Ends band select by removing the overlay.
+ */
+ private void endBandSelect() {
+ if (DEBUG) {
+ Log.d(TAG, "Ending band select.");
+ checkState(mModel != null);
+ }
+
+ // TODO: Currently when a band select operation ends outside
+ // of an item (e.g. in the empty area between items),
+ // getPositionNearestOrigin may return an unselected item.
+ // Since the point of this code is to establish the
+ // anchor point for subsequent range operations (SHIFT+CLICK)
+ // we really want to do a better job figuring out the last
+ // item selected (and nearest to the cursor).
+ int firstSelected = mModel.getPositionNearestOrigin();
+ if (firstSelected != GridModel.NOT_SET
+ && mSelectionHelper.isSelected(mKeyProvider.getKey(firstSelected))) {
+ // Establish the band selection point as range anchor. This
+ // allows touch and keyboard based selection activities
+ // to be based on the band selection anchor point.
+ mSelectionHelper.anchorRange(firstSelected);
+ }
+
+ mSelectionHelper.mergeProvisionalSelection();
+ reset();
+ }
+
+ /**
+ * @see RecyclerView.OnScrollListener
+ */
+ private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ if (!isActive()) {
+ return;
+ }
+
+ // Adjust the y-coordinate of the origin the opposite number of pixels so that the
+ // origin remains in the same place relative to the view's items.
+ mOrigin.y -= dy;
+ resizeBand();
+ }
+
+ /**
+ * Provides functionality for BandController. Exists primarily to tests that are
+ * fully isolated from RecyclerView.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+ abstract static class BandHost<K> {
+
+ /**
+ * Returns a new GridModel instance.
+ */
+ abstract GridModel<K> createGridModel();
+
+ /**
+ * Show the band covering the bounds.
+ *
+ * @param bounds The boundaries of the band to show.
+ */
+ abstract void showBand(Rect bounds);
+
+ /**
+ * Hide the band.
+ */
+ abstract void hideBand();
+
+ /**
+ * Add a listener to be notified on scroll events.
+ *
+ * @param listener
+ */
+ abstract void addOnScrollListener(RecyclerView.OnScrollListener listener);
+ }
+}
diff --git a/androidx/recyclerview/selection/ContentLock.java b/androidx/recyclerview/selection/ContentLock.java
new file mode 100644
index 00000000..6891eab6
--- /dev/null
+++ b/androidx/recyclerview/selection/ContentLock.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.content.Loader;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+
+/**
+ * ContentLock provides a mechanism to block content from reloading while selection
+ * activities like gesture and band selection are active. Clients using live data
+ * (data loaded, for example by a {@link Loader}), should route calls to load
+ * content through this lock using {@link ContentLock#runWhenUnlocked(Runnable)}.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class ContentLock {
+
+ private static final String TAG = "ContentLock";
+
+ private int mLocks = 0;
+ private @Nullable Runnable mCallback;
+
+ /**
+ * Increment the block count by 1
+ */
+ @MainThread
+ synchronized void block() {
+ mLocks++;
+ if (DEBUG) Log.v(TAG, "Incremented content lock count to " + mLocks + ".");
+ }
+
+ /**
+ * Decrement the block count by 1; If no other object is trying to block and there exists some
+ * callback, that callback will be run
+ */
+ @MainThread
+ synchronized void unblock() {
+ checkState(mLocks > 0);
+
+ mLocks--;
+ if (DEBUG) Log.v(TAG, "Decremented content lock count to " + mLocks + ".");
+
+ if (mLocks == 0 && mCallback != null) {
+ mCallback.run();
+ mCallback = null;
+ }
+ }
+
+ /**
+ * Attempts to run the given Runnable if not-locked, or else the Runnable is set to be ran next
+ * (replacing any previous set Runnables).
+ */
+ @SuppressWarnings("unused")
+ public synchronized void runWhenUnlocked(Runnable runnable) {
+ if (mLocks == 0) {
+ runnable.run();
+ } else {
+ mCallback = runnable;
+ }
+ }
+
+ /**
+ * Allows other selection code to perform a precondition check asserting the state is locked.
+ */
+ void checkLocked() {
+ checkState(mLocks > 0);
+ }
+
+ /**
+ * Allows other selection code to perform a precondition check asserting the state is unlocked.
+ */
+ void checkUnlocked() {
+ checkState(mLocks == 0);
+ }
+}
diff --git a/androidx/recyclerview/selection/DefaultBandHost.java b/androidx/recyclerview/selection/DefaultBandHost.java
new file mode 100644
index 00000000..f0fd4fe6
--- /dev/null
+++ b/androidx/recyclerview/selection/DefaultBandHost.java
@@ -0,0 +1,153 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ItemDecoration;
+import android.view.View;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * RecyclerView backed {@link BandSelectionHelper.BandHost}.
+ */
+final class DefaultBandHost<K> extends GridModel.GridHost<K> {
+
+ private static final Rect NILL_RECT = new Rect(0, 0, 0, 0);
+
+ private final RecyclerView mRecView;
+ private final Drawable mBand;
+ private final ItemKeyProvider<K> mKeyProvider;
+ private final SelectionPredicate<K> mSelectionPredicate;
+
+ DefaultBandHost(
+ RecyclerView recView,
+ @DrawableRes int bandOverlayId,
+ ItemKeyProvider<K> keyProvider,
+ SelectionPredicate<K> selectionPredicate) {
+
+ checkArgument(recView != null);
+
+ mRecView = recView;
+ mBand = mRecView.getContext().getResources().getDrawable(bandOverlayId);
+
+ checkArgument(mBand != null);
+ checkArgument(keyProvider != null);
+ checkArgument(selectionPredicate != null);
+
+ mKeyProvider = keyProvider;
+ mSelectionPredicate = selectionPredicate;
+
+ mRecView.addItemDecoration(
+ new ItemDecoration() {
+ @Override
+ public void onDrawOver(
+ Canvas canvas,
+ RecyclerView unusedParent,
+ RecyclerView.State unusedState) {
+ DefaultBandHost.this.onDrawBand(canvas);
+ }
+ });
+ }
+
+ @Override
+ GridModel<K> createGridModel() {
+ return new GridModel<>(this, mKeyProvider, mSelectionPredicate);
+ }
+
+ @Override
+ int getAdapterPositionAt(int index) {
+ return mRecView.getChildAdapterPosition(mRecView.getChildAt(index));
+ }
+
+ @Override
+ void addOnScrollListener(RecyclerView.OnScrollListener listener) {
+ mRecView.addOnScrollListener(listener);
+ }
+
+ @Override
+ void removeOnScrollListener(RecyclerView.OnScrollListener listener) {
+ mRecView.removeOnScrollListener(listener);
+ }
+
+ @Override
+ Point createAbsolutePoint(Point relativePoint) {
+ return new Point(relativePoint.x + mRecView.computeHorizontalScrollOffset(),
+ relativePoint.y + mRecView.computeVerticalScrollOffset());
+ }
+
+ @Override
+ Rect getAbsoluteRectForChildViewAt(int index) {
+ final View child = mRecView.getChildAt(index);
+ final Rect childRect = new Rect();
+ child.getHitRect(childRect);
+ childRect.left += mRecView.computeHorizontalScrollOffset();
+ childRect.right += mRecView.computeHorizontalScrollOffset();
+ childRect.top += mRecView.computeVerticalScrollOffset();
+ childRect.bottom += mRecView.computeVerticalScrollOffset();
+ return childRect;
+ }
+
+ @Override
+ int getVisibleChildCount() {
+ return mRecView.getChildCount();
+ }
+
+ @Override
+ int getColumnCount() {
+ RecyclerView.LayoutManager layoutManager = mRecView.getLayoutManager();
+ if (layoutManager instanceof GridLayoutManager) {
+ return ((GridLayoutManager) layoutManager).getSpanCount();
+ }
+
+ // Otherwise, it is a list with 1 column.
+ return 1;
+ }
+
+ @Override
+ void showBand(Rect rect) {
+ mBand.setBounds(rect);
+ // TODO: mRecView.invalidateItemDecorations() should work, but it isn't currently.
+ // NOTE: That without invalidating rv, the band only gets updated
+ // when the pointer moves off a the item view into "NO_POSITION" territory.
+ mRecView.invalidate();
+ }
+
+ @Override
+ void hideBand() {
+ mBand.setBounds(NILL_RECT);
+ // TODO: mRecView.invalidateItemDecorations() should work, but it isn't currently.
+ mRecView.invalidate();
+ }
+
+ private void onDrawBand(Canvas c) {
+ mBand.draw(c);
+ }
+
+ @Override
+ boolean hasView(int pos) {
+ return mRecView.findViewHolderForAdapterPosition(pos) != null;
+ }
+}
diff --git a/androidx/recyclerview/selection/DefaultSelectionHelper.java b/androidx/recyclerview/selection/DefaultSelectionHelper.java
new file mode 100644
index 00000000..5625e3da
--- /dev/null
+++ b/androidx/recyclerview/selection/DefaultSelectionHelper.java
@@ -0,0 +1,475 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import androidx.recyclerview.selection.Range.RangeType;
+
+/**
+ * {@link SelectionHelper} providing support for traditional multi-item selection on top
+ * of {@link RecyclerView}.
+ *
+ * <p>The class supports running in a single-select mode, which can be enabled
+ * by passing {@code #MODE_SINGLE} to the constructor.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class DefaultSelectionHelper<K> extends SelectionHelper<K> {
+
+ private static final String TAG = "DefaultSelectionHelper";
+
+ private final Selection<K> mSelection = new Selection<>();
+ private final List<SelectionObserver> mObservers = new ArrayList<>(1);
+ private final ItemKeyProvider<K> mKeyProvider;
+ private final SelectionPredicate<K> mSelectionPredicate;
+ private final RangeCallbacks mRangeCallbacks;
+ private final boolean mSingleSelect;
+
+ private @Nullable Range mRange;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param keyProvider client supplied class providing access to stable ids.
+ * @param selectionPredicate A predicate allowing the client to disallow selection
+ * of individual elements.
+ */
+ public DefaultSelectionHelper(
+ ItemKeyProvider keyProvider,
+ SelectionPredicate selectionPredicate) {
+
+ checkArgument(keyProvider != null);
+ checkArgument(selectionPredicate != null);
+
+ mKeyProvider = keyProvider;
+ mSelectionPredicate = selectionPredicate;
+ mRangeCallbacks = new RangeCallbacks();
+
+ mSingleSelect = !selectionPredicate.canSelectMultiple();
+ }
+
+ @Override
+ public void addObserver(SelectionObserver callback) {
+ checkArgument(callback != null);
+ mObservers.add(callback);
+ }
+
+ @Override
+ public boolean hasSelection() {
+ return !mSelection.isEmpty();
+ }
+
+ @Override
+ public Selection getSelection() {
+ return mSelection;
+ }
+
+ @Override
+ public void copySelection(Selection dest) {
+ dest.copyFrom(mSelection);
+ }
+
+ @Override
+ public boolean isSelected(@Nullable K key) {
+ return mSelection.contains(key);
+ }
+
+ @Override
+ public void restoreSelection(Selection other) {
+ checkArgument(other != null);
+ setItemsSelectedQuietly(other.mSelection, true);
+ // NOTE: We intentionally don't restore provisional selection. It's provisional.
+ notifySelectionRestored();
+ }
+
+ @Override
+ public boolean setItemsSelected(Iterable<K> keys, boolean selected) {
+ boolean changed = setItemsSelectedQuietly(keys, selected);
+ notifySelectionChanged();
+ return changed;
+ }
+
+ private boolean setItemsSelectedQuietly(Iterable<K> keys, boolean selected) {
+ boolean changed = false;
+ for (K key: keys) {
+ boolean itemChanged = selected
+ ? canSetState(key, true) && mSelection.add(key)
+ : canSetState(key, false) && mSelection.remove(key);
+ if (itemChanged) {
+ notifyItemStateChanged(key, selected);
+ }
+ changed |= itemChanged;
+ }
+ return changed;
+ }
+
+ @Override
+ public void clearSelection() {
+ if (!hasSelection()) {
+ return;
+ }
+
+ Selection prev = clearSelectionQuietly();
+ notifySelectionCleared(prev);
+ notifySelectionChanged();
+ }
+
+ @Override
+ public boolean clear() {
+ boolean somethingChanged = hasSelection();
+ clearProvisionalSelection();
+ clearSelection();
+ return somethingChanged;
+ }
+
+ /**
+ * Clears the selection, without notifying selection listeners.
+ * Returns items in previous selection. Callers are responsible for notifying
+ * listeners about changes.
+ */
+ private Selection clearSelectionQuietly() {
+ mRange = null;
+
+ Selection prevSelection = new Selection();
+ if (hasSelection()) {
+ copySelection(prevSelection);
+ mSelection.clear();
+ }
+
+ return prevSelection;
+ }
+
+ @Override
+ public boolean select(K key) {
+ checkArgument(key != null);
+
+ if (!mSelection.contains(key)) {
+ if (!canSetState(key, true)) {
+ if (DEBUG) Log.d(TAG, "Select cancelled by selection predicate test.");
+ return false;
+ }
+
+ // Enforce single selection policy.
+ if (mSingleSelect && hasSelection()) {
+ Selection prev = clearSelectionQuietly();
+ notifySelectionCleared(prev);
+ }
+
+ mSelection.add(key);
+ notifyItemStateChanged(key, true);
+ notifySelectionChanged();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean deselect(K key) {
+ checkArgument(key != null);
+
+ if (mSelection.contains(key)) {
+ if (!canSetState(key, false)) {
+ if (DEBUG) Log.d(TAG, "Deselect cancelled by selection predicate test.");
+ return false;
+ }
+ mSelection.remove(key);
+ notifyItemStateChanged(key, false);
+ notifySelectionChanged();
+ if (mSelection.isEmpty() && isRangeActive()) {
+ // if there's nothing in the selection and there is an active ranger it results
+ // in unexpected behavior when the user tries to start range selection: the item
+ // which the ranger 'thinks' is the already selected anchor becomes unselectable
+ endRange();
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void startRange(int position) {
+ select(mKeyProvider.getKey(position));
+ anchorRange(position);
+ }
+
+ @Override
+ public void extendRange(int position) {
+ extendRange(position, Range.TYPE_PRIMARY);
+ }
+
+ @Override
+ public void endRange() {
+ mRange = null;
+ // Clean up in case there was any leftover provisional selection
+ clearProvisionalSelection();
+ }
+
+ @Override
+ public void anchorRange(int position) {
+ checkArgument(position != RecyclerView.NO_POSITION);
+ checkArgument(mSelection.contains(mKeyProvider.getKey(position)));
+
+ mRange = new Range(position, mRangeCallbacks);
+ }
+
+ @Override
+ public void extendProvisionalRange(int position) {
+ if (mSingleSelect) {
+ return;
+ }
+
+ if (DEBUG) Log.i(TAG, "Extending provision range to position: " + position);
+ checkState(isRangeActive(), "Range start point not set.");
+ extendRange(position, Range.TYPE_PROVISIONAL);
+ }
+
+ /**
+ * Sets the end point for the current range selection, started by a call to
+ * {@link #startRange(int)}. This function should only be called when a range selection
+ * is active (see {@link #isRangeActive()}. Items in the range [anchor, end] will be
+ * selected or in provisional select, depending on the type supplied. Note that if the type is
+ * provisional selection, one should do {@link #mergeProvisionalSelection()} at some
+ * point before calling on {@link #endRange()}.
+ *
+ * @param position The new end position for the selection range.
+ * @param type The type of selection the range should utilize.
+ */
+ private void extendRange(int position, @RangeType int type) {
+ checkState(isRangeActive(), "Range start point not set.");
+
+ mRange.extendRange(position, type);
+
+ // We're being lazy here notifying even when something might not have changed.
+ // To make this more correct, we'd need to update the Ranger class to return
+ // information about what has changed.
+ notifySelectionChanged();
+ }
+
+ @Override
+ public void setProvisionalSelection(Set<K> newSelection) {
+ if (mSingleSelect) {
+ return;
+ }
+
+ Map<K, Boolean> delta = mSelection.setProvisionalSelection(newSelection);
+ for (Map.Entry<K, Boolean> entry: delta.entrySet()) {
+ notifyItemStateChanged(entry.getKey(), entry.getValue());
+ }
+
+ notifySelectionChanged();
+ }
+
+ @Override
+ public void mergeProvisionalSelection() {
+ mSelection.mergeProvisionalSelection();
+
+ // Note, that for almost all functional purposes, merging a provisional selection
+ // into a the primary selection doesn't change the selection, just an internal
+ // representation of it. But there are some nuanced areas cases where
+ // that isn't true. equality for 1. So, we notify regardless.
+
+ notifySelectionChanged();
+ }
+
+ @Override
+ public void clearProvisionalSelection() {
+ for (K key : mSelection.mProvisionalSelection) {
+ notifyItemStateChanged(key, false);
+ }
+ mSelection.clearProvisionalSelection();
+ }
+
+ @Override
+ public boolean isRangeActive() {
+ return mRange != null;
+ }
+
+ private boolean canSetState(K key, boolean nextState) {
+ return mSelectionPredicate.canSetStateForKey(key, nextState);
+ }
+
+ @Override
+ void onDataSetChanged() {
+ mSelection.clearProvisionalSelection();
+
+ notifySelectionReset();
+
+ for (K key : mSelection) {
+ // If the underlying data set has changed, before restoring
+ // selection we must re-verify that it can be selected.
+ // Why? Because if the dataset has changed, then maybe the
+ // selectability of an item has changed.
+ if (!canSetState(key, true)) {
+ deselect(key);
+ } else {
+ int lastListener = mObservers.size() - 1;
+ for (int i = lastListener; i >= 0; i--) {
+ mObservers.get(i).onItemStateChanged(key, true);
+ }
+ }
+ }
+
+ notifySelectionChanged();
+ }
+
+ /**
+ * Notifies registered listeners when the selection status of a single item
+ * (identified by {@code position}) changes.
+ */
+ private void notifyItemStateChanged(K key, boolean selected) {
+ checkArgument(key != null);
+
+ int lastListenerIndex = mObservers.size() - 1;
+ for (int i = lastListenerIndex; i >= 0; i--) {
+ mObservers.get(i).onItemStateChanged(key, selected);
+ }
+ }
+
+ private void notifySelectionCleared(Selection<K> selection) {
+ for (K key: selection.mSelection) {
+ notifyItemStateChanged(key, false);
+ }
+ for (K key: selection.mProvisionalSelection) {
+ notifyItemStateChanged(key, false);
+ }
+ }
+
+ /**
+ * Notifies registered listeners when the selection has changed. This
+ * notification should be sent only once a full series of changes
+ * is complete, e.g. clearingSelection, or updating the single
+ * selection from one item to another.
+ */
+ private void notifySelectionChanged() {
+ int lastListenerIndex = mObservers.size() - 1;
+ for (int i = lastListenerIndex; i >= 0; i--) {
+ mObservers.get(i).onSelectionChanged();
+ }
+ }
+
+ private void notifySelectionRestored() {
+ int lastListenerIndex = mObservers.size() - 1;
+ for (int i = lastListenerIndex; i >= 0; i--) {
+ mObservers.get(i).onSelectionRestored();
+ }
+ }
+
+ private void notifySelectionReset() {
+ int lastListenerIndex = mObservers.size() - 1;
+ for (int i = lastListenerIndex; i >= 0; i--) {
+ mObservers.get(i).onSelectionReset();
+ }
+ }
+
+ private void updateForRange(int begin, int end, boolean selected, @RangeType int type) {
+ switch (type) {
+ case Range.TYPE_PRIMARY:
+ updateForRegularRange(begin, end, selected);
+ break;
+ case Range.TYPE_PROVISIONAL:
+ updateForProvisionalRange(begin, end, selected);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid range type: " + type);
+ }
+ }
+
+ private void updateForRegularRange(int begin, int end, boolean selected) {
+ checkArgument(end >= begin);
+
+ for (int i = begin; i <= end; i++) {
+ K key = mKeyProvider.getKey(i);
+ if (key == null) {
+ continue;
+ }
+
+ if (selected) {
+ select(key);
+ } else {
+ deselect(key);
+ }
+ }
+ }
+
+ private void updateForProvisionalRange(int begin, int end, boolean selected) {
+ checkArgument(end >= begin);
+
+ for (int i = begin; i <= end; i++) {
+ K key = mKeyProvider.getKey(i);
+ if (key == null) {
+ continue;
+ }
+
+ boolean changedState = false;
+ if (selected) {
+ boolean canSelect = canSetState(key, true);
+ if (canSelect && !mSelection.mSelection.contains(key)) {
+ mSelection.mProvisionalSelection.add(key);
+ changedState = true;
+ }
+ } else {
+ mSelection.mProvisionalSelection.remove(key);
+ changedState = true;
+ }
+
+ // Only notify item callbacks when something's state is actually changed in provisional
+ // selection.
+ if (changedState) {
+ notifyItemStateChanged(key, selected);
+ }
+ }
+
+ notifySelectionChanged();
+ }
+
+ private final class RangeCallbacks extends Range.Callbacks {
+ @Override
+ void updateForRange(int begin, int end, boolean selected, int type) {
+ switch (type) {
+ case Range.TYPE_PRIMARY:
+ updateForRegularRange(begin, end, selected);
+ break;
+ case Range.TYPE_PROVISIONAL:
+ updateForProvisionalRange(begin, end, selected);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid range type: " + type);
+ }
+ }
+ }
+}
diff --git a/androidx/recyclerview/selection/EventBridge.java b/androidx/recyclerview/selection/EventBridge.java
new file mode 100644
index 00000000..b418ad4f
--- /dev/null
+++ b/androidx/recyclerview/selection/EventBridge.java
@@ -0,0 +1,137 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+/**
+ * Provides the necessary glue to notify RecyclerView when selection data changes,
+ * and to notify SelectionHelper when the underlying RecyclerView.Adapter data changes.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@VisibleForTesting
+public class EventBridge {
+
+ private static final String TAG = "EventsRelays";
+
+ /**
+ * Installs the event bridge for on the supplied adapter/helper.
+ *
+ * @param adapter
+ * @param selectionHelper
+ * @param keyProvider
+ * @param <K>
+ */
+ @VisibleForTesting
+ public static <K> void install(
+ RecyclerView.Adapter<?> adapter,
+ SelectionHelper<K> selectionHelper,
+ ItemKeyProvider<K> keyProvider) {
+ new AdapterToSelectionHelper(adapter, selectionHelper);
+ new SelectionHelperToAdapter<>(selectionHelper, keyProvider, adapter);
+ }
+
+ private static final class AdapterToSelectionHelper extends RecyclerView.AdapterDataObserver {
+
+ private final SelectionHelper<?> mSelectionHelper;
+
+ AdapterToSelectionHelper(
+ RecyclerView.Adapter<?> adapter,
+ SelectionHelper<?> selectionHelper) {
+ adapter.registerAdapterDataObserver(this);
+
+ checkArgument(selectionHelper != null);
+ mSelectionHelper = selectionHelper;
+ }
+
+ @Override
+ public void onChanged() {
+ mSelectionHelper.onDataSetChanged();
+ }
+
+ @Override
+ public void onItemRangeChanged(int startPosition, int itemCount, Object payload) {
+ // No change in position. Ignore, since we assume
+ // selection is a user driven activity. So changes
+ // in properties of items shouldn't result in a
+ // change of selection.
+ // TODO: It is possible properties of items chould change to make them unselectable.
+ }
+
+ @Override
+ public void onItemRangeInserted(int startPosition, int itemCount) {
+ // Uninteresting to us since selection is stable ID based.
+ }
+
+ @Override
+ public void onItemRangeRemoved(int startPosition, int itemCount) {
+ // Uninteresting to us since selection is stable ID based.
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ // Uninteresting to us since selection is stable ID based.
+ }
+ }
+
+ private static final class SelectionHelperToAdapter<K>
+ extends SelectionHelper.SelectionObserver<K> {
+
+ private final ItemKeyProvider<K> mKeyProvider;
+ private final RecyclerView.Adapter<?> mAdapter;
+
+ SelectionHelperToAdapter(
+ SelectionHelper<K> selectionHelper,
+ ItemKeyProvider<K> keyProvider,
+ RecyclerView.Adapter<?> adapter) {
+
+ selectionHelper.addObserver(this);
+
+ checkArgument(keyProvider != null);
+ checkArgument(adapter != null);
+
+ mKeyProvider = keyProvider;
+ mAdapter = adapter;
+ }
+
+ /**
+ * Called when state of an item has been changed.
+ */
+ @Override
+ public void onItemStateChanged(K key, boolean selected) {
+ int position = mKeyProvider.getPosition(key);
+ if (VERBOSE) Log.v(TAG, "ITEM " + key + " CHANGED at pos: " + position);
+
+ if (position < 0) {
+ Log.w(TAG, "Item change notification received for unknown item: " + key);
+ return;
+ }
+
+ mAdapter.notifyItemChanged(position, SelectionHelper.SELECTION_CHANGED_MARKER);
+ }
+ }
+}
diff --git a/androidx/recyclerview/selection/FocusCallbacks.java b/androidx/recyclerview/selection/FocusCallbacks.java
new file mode 100644
index 00000000..4c1c12ef
--- /dev/null
+++ b/androidx/recyclerview/selection/FocusCallbacks.java
@@ -0,0 +1,78 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class FocusCallbacks<K> {
+
+ static final <K> FocusCallbacks<K> dummy() {
+ return new FocusCallbacks<K>() {
+ @Override
+ public void focusItem(ItemDetails<K> item) {
+ }
+
+ @Override
+ public boolean hasFocusedItem() {
+ return false;
+ }
+
+ @Override
+ public int getFocusedPosition() {
+ return RecyclerView.NO_POSITION;
+ }
+
+ @Override
+ public void clearFocus() {
+ }
+ };
+ }
+
+ /**
+ * If environment supports focus, focus {@code item}.
+ */
+ public abstract void focusItem(ItemDetails<K> item);
+
+ /**
+ * @return true if there is a focused item.
+ */
+ public abstract boolean hasFocusedItem();
+
+ /**
+ * @return the position of the currently focused item, if any.
+ */
+ public abstract int getFocusedPosition();
+
+ /**
+ * If the environment supports focus and something is focused, unfocus it.
+ */
+ public abstract void clearFocus();
+}
diff --git a/androidx/recyclerview/selection/GestureRouter.java b/androidx/recyclerview/selection/GestureRouter.java
new file mode 100644
index 00000000..82fab878
--- /dev/null
+++ b/androidx/recyclerview/selection/GestureRouter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.Nullable;
+import android.view.GestureDetector.OnDoubleTapListener;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+/**
+ * GestureRouter is responsible for routing gestures detected by a GestureDetector
+ * to registered handlers. The primary function is to divide events by tool-type
+ * allowing handlers to cleanly implement tool-type specific policies.
+ *
+ * @param <T> listener type. Must extend OnGestureListener & OnDoubleTapListener.
+ */
+final class GestureRouter<T extends OnGestureListener & OnDoubleTapListener>
+ implements OnGestureListener, OnDoubleTapListener {
+
+ private final ToolHandlerRegistry<T> mDelegates;
+
+ GestureRouter(T defaultDelegate) {
+ checkArgument(defaultDelegate != null);
+ mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
+ }
+
+ GestureRouter() {
+ this((T) new SimpleOnGestureListener());
+ }
+
+ /**
+ * @param toolType
+ * @param delegate the delegate, or null to unregister.
+ */
+ public void register(int toolType, @Nullable T delegate) {
+ mDelegates.set(toolType, delegate);
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return mDelegates.get(e).onSingleTapConfirmed(e);
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ return mDelegates.get(e).onDoubleTap(e);
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return mDelegates.get(e).onDoubleTapEvent(e);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return mDelegates.get(e).onDown(e);
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ mDelegates.get(e).onShowPress(e);
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return mDelegates.get(e).onSingleTapUp(e);
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return mDelegates.get(e2).onScroll(e1, e2, distanceX, distanceY);
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ mDelegates.get(e).onLongPress(e);
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ return mDelegates.get(e2).onFling(e1, e2, velocityX, velocityY);
+ }
+}
diff --git a/androidx/recyclerview/selection/GestureSelectionHelper.java b/androidx/recyclerview/selection/GestureSelectionHelper.java
new file mode 100644
index 00000000..2a28fc5d
--- /dev/null
+++ b/androidx/recyclerview/selection/GestureSelectionHelper.java
@@ -0,0 +1,287 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.graphics.Point;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * GestureSelectionHelper provides logic that interprets a combination
+ * of motions and gestures in order to provide gesture driven selection support
+ * when used in conjunction with RecyclerView and other classes in the ReyclerView
+ * selection support package.
+ */
+final class GestureSelectionHelper implements OnItemTouchListener {
+
+ private static final String TAG = "GestureSelectionHelper";
+
+ private final SelectionHelper<?> mSelectionMgr;
+ private final AutoScroller mScroller;
+ private final ViewDelegate mView;
+ private final ContentLock mLock;
+
+ private int mLastStartedItemPos = -1;
+ private boolean mStarted = false;
+ private Point mLastInterceptedPoint;
+
+ /**
+ * See {@link #create(SelectionHelper, RecyclerView, AutoScroller, ContentLock)} for convenience
+ * method.
+ */
+ GestureSelectionHelper(
+ SelectionHelper<?> selectionHelper,
+ ViewDelegate view,
+ AutoScroller scroller,
+ ContentLock lock) {
+
+ checkArgument(selectionHelper != null);
+ checkArgument(view != null);
+ checkArgument(scroller != null);
+ checkArgument(lock != null);
+
+ mSelectionMgr = selectionHelper;
+ mView = view;
+ mScroller = scroller;
+ mLock = lock;
+ }
+
+ /**
+ * Explicitly kicks off a gesture multi-select.
+ */
+ void start() {
+ checkState(!mStarted);
+ checkState(mLastStartedItemPos > -1);
+
+ // Partner code in MotionInputHandler ensures items
+ // are selected and range established prior to
+ // start being called.
+ // Verify the truth of that statement here
+ // to make the implicit coupling less of a time bomb.
+ checkState(mSelectionMgr.isRangeActive());
+
+ mLock.checkUnlocked();
+
+ mStarted = true;
+ mLock.block();
+ }
+
+ @Override
+ /** @hide */
+ public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) {
+ if (MotionEvents.isMouseEvent(e)) {
+ if (Shared.DEBUG) Log.w(TAG, "Unexpected Mouse event. Check configuration.");
+ }
+
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // NOTE: Unlike events with other actions, RecyclerView eats
+ // "DOWN" events. So even if we return true here we'll
+ // never see an event w/ ACTION_DOWN passed to onTouchEvent.
+ return handleInterceptedDownEvent(e);
+ case MotionEvent.ACTION_MOVE:
+ return mStarted;
+ }
+
+ return false;
+ }
+
+ @Override
+ /** @hide */
+ public void onTouchEvent(RecyclerView unused, MotionEvent e) {
+ checkState(mStarted);
+
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ handleMoveEvent(e);
+ break;
+ case MotionEvent.ACTION_UP:
+ handleUpEvent(e);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ handleCancelEvent(e);
+ break;
+ }
+ }
+
+ @Override
+ /** @hide */
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ }
+
+ // Called when an ACTION_DOWN event is intercepted.
+ // If down event happens on an item, we mark that item's position as last started.
+ private boolean handleInterceptedDownEvent(MotionEvent e) {
+ mLastStartedItemPos = mView.getItemUnder(e);
+ return mLastStartedItemPos != RecyclerView.NO_POSITION;
+ }
+
+ // Called when ACTION_UP event is to be handled.
+ // Essentially, since this means all gesture movement is over, reset everything and apply
+ // provisional selection.
+ private void handleUpEvent(MotionEvent e) {
+ mSelectionMgr.mergeProvisionalSelection();
+ endSelection();
+ if (mLastStartedItemPos > -1) {
+ mSelectionMgr.startRange(mLastStartedItemPos);
+ }
+ }
+
+ // Called when ACTION_CANCEL event is to be handled.
+ // This means this gesture selection is aborted, so reset everything and abandon provisional
+ // selection.
+ private void handleCancelEvent(MotionEvent unused) {
+ mSelectionMgr.clearProvisionalSelection();
+ endSelection();
+ }
+
+ private void endSelection() {
+ checkState(mStarted);
+
+ mLastStartedItemPos = -1;
+ mStarted = false;
+ mScroller.reset();
+ mLock.unblock();
+ }
+
+ // Call when an intercepted ACTION_MOVE event is passed down.
+ // At this point, we are sure user wants to gesture multi-select.
+ private void handleMoveEvent(MotionEvent e) {
+ mLastInterceptedPoint = MotionEvents.getOrigin(e);
+
+ int lastGlidedItemPos = mView.getLastGlidedItemPosition(e);
+ if (lastGlidedItemPos != RecyclerView.NO_POSITION) {
+ extendSelection(lastGlidedItemPos);
+ }
+
+ mScroller.scroll(mLastInterceptedPoint);
+ }
+
+ // It's possible for events to go over the top/bottom of the RecyclerView.
+ // We want to get a Y-coordinate within the RecyclerView so we can find the childView underneath
+ // correctly.
+ private static float getInboundY(float max, float y) {
+ if (y < 0f) {
+ return 0f;
+ } else if (y > max) {
+ return max;
+ }
+ return y;
+ }
+
+ /* Given the end position, select everything in-between.
+ * @param endPos The adapter position of the end item.
+ */
+ private void extendSelection(int endPos) {
+ mSelectionMgr.extendProvisionalRange(endPos);
+ }
+
+ /**
+ * Returns a new instance of GestureSelectionHelper.
+ */
+ static GestureSelectionHelper create(
+ SelectionHelper selectionMgr,
+ RecyclerView recView,
+ AutoScroller scroller,
+ ContentLock lock) {
+
+ return new GestureSelectionHelper(
+ selectionMgr,
+ new RecyclerViewDelegate(recView),
+ scroller,
+ lock);
+ }
+
+ @VisibleForTesting
+ abstract static class ViewDelegate {
+ abstract int getHeight();
+
+ abstract int getItemUnder(MotionEvent e);
+
+ abstract int getLastGlidedItemPosition(MotionEvent e);
+ }
+
+ @VisibleForTesting
+ static final class RecyclerViewDelegate extends ViewDelegate {
+
+ private final RecyclerView mRecView;
+
+ RecyclerViewDelegate(RecyclerView view) {
+ checkArgument(view != null);
+ mRecView = view;
+ }
+
+ @Override
+ int getHeight() {
+ return mRecView.getHeight();
+ }
+
+ @Override
+ int getItemUnder(MotionEvent e) {
+ View child = mRecView.findChildViewUnder(e.getX(), e.getY());
+ return child != null
+ ? mRecView.getChildAdapterPosition(child)
+ : RecyclerView.NO_POSITION;
+ }
+
+ @Override
+ int getLastGlidedItemPosition(MotionEvent e) {
+ // If user has moved his pointer to the bottom-right empty pane (ie. to the right of the
+ // last item of the recycler view), we would want to set that as the currentItemPos
+ View lastItem = mRecView.getLayoutManager()
+ .getChildAt(mRecView.getLayoutManager().getChildCount() - 1);
+ int direction = ViewCompat.getLayoutDirection(mRecView);
+ final boolean pastLastItem = isPastLastItem(lastItem.getTop(),
+ lastItem.getLeft(),
+ lastItem.getRight(),
+ e,
+ direction);
+
+ // Since views get attached & detached from RecyclerView,
+ // {@link LayoutManager#getChildCount} can return a different number from the actual
+ // number
+ // of items in the adapter. Using the adapter is the for sure way to get the actual last
+ // item position.
+ final float inboundY = getInboundY(mRecView.getHeight(), e.getY());
+ return (pastLastItem) ? mRecView.getAdapter().getItemCount() - 1
+ : mRecView.getChildAdapterPosition(
+ mRecView.findChildViewUnder(e.getX(), inboundY));
+ }
+
+ /*
+ * Check to see if MotionEvent if past a particular item, i.e. to the right or to the bottom
+ * of the item.
+ * For RTL, it would to be to the left or to the bottom of the item.
+ */
+ @VisibleForTesting
+ static boolean isPastLastItem(int top, int left, int right, MotionEvent e, int direction) {
+ if (direction == View.LAYOUT_DIRECTION_LTR) {
+ return e.getX() > right && e.getY() > top;
+ } else {
+ return e.getX() < left && e.getY() > top;
+ }
+ }
+ }
+}
diff --git a/androidx/recyclerview/selection/GridModel.java b/androidx/recyclerview/selection/GridModel.java
new file mode 100644
index 00000000..43589581
--- /dev/null
+++ b/androidx/recyclerview/selection/GridModel.java
@@ -0,0 +1,786 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Provides a band selection item model for views within a RecyclerView. This class queries the
+ * RecyclerView to determine where its items are placed; then, once band selection is underway,
+ * it alerts listeners of which items are covered by the selections.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+final class GridModel<K> {
+
+ // Magical value indicating that a value has not been previously set. primitive null :)
+ static final int NOT_SET = -1;
+
+ // Enum values used to determine the corner at which the origin is located within the
+ private static final int UPPER = 0x00;
+ private static final int LOWER = 0x01;
+ private static final int LEFT = 0x00;
+ private static final int RIGHT = 0x02;
+ private static final int UPPER_LEFT = UPPER | LEFT;
+ private static final int UPPER_RIGHT = UPPER | RIGHT;
+ private static final int LOWER_LEFT = LOWER | LEFT;
+ private static final int LOWER_RIGHT = LOWER | RIGHT;
+
+ private final GridHost<K> mHost;
+ private final ItemKeyProvider<K> mKeyProvider;
+ private final SelectionPredicate<K> mSelectionPredicate;
+
+ private final List<SelectionObserver> mOnSelectionChangedListeners = new ArrayList<>();
+
+ // Map from the x-value of the left side of a SparseBooleanArray of adapter positions, keyed
+ // by their y-offset. For example, if the first column of the view starts at an x-value of 5,
+ // mColumns.get(5) would return an array of positions in that column. Within that array, the
+ // value for key y is the adapter position for the item whose y-offset is y.
+ private final SparseArray<SparseIntArray> mColumns = new SparseArray<>();
+
+ // List of limits along the x-axis (columns).
+ // This list is sorted from furthest left to furthest right.
+ private final List<Limits> mColumnBounds = new ArrayList<>();
+
+ // List of limits along the y-axis (rows). Note that this list only contains items which
+ // have been in the viewport.
+ private final List<Limits> mRowBounds = new ArrayList<>();
+
+ // The adapter positions which have been recorded so far.
+ private final SparseBooleanArray mKnownPositions = new SparseBooleanArray();
+
+ // Array passed to registered OnSelectionChangedListeners. One array is created and reused
+ // throughout the lifetime of the object.
+ private final Set<K> mSelection = new HashSet<>();
+
+ // The current pointer (in absolute positioning from the top of the view).
+ private Point mPointer;
+
+ // The bounds of the band selection.
+ private RelativePoint mRelOrigin;
+ private RelativePoint mRelPointer;
+
+ private boolean mIsActive;
+
+ // Tracks where the band select originated from. This is used to determine where selections
+ // should expand from when Shift+click is used.
+ private int mPositionNearestOrigin = NOT_SET;
+
+ private final OnScrollListener mScrollListener;
+
+ GridModel(
+ GridHost host,
+ ItemKeyProvider<K> keyProvider,
+ SelectionPredicate<K> selectionPredicate) {
+
+ checkArgument(host != null);
+ checkArgument(keyProvider != null);
+ checkArgument(selectionPredicate != null);
+
+ mHost = host;
+ mKeyProvider = keyProvider;
+ mSelectionPredicate = selectionPredicate;
+
+ mScrollListener = new OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ GridModel.this.onScrolled(recyclerView, dx, dy);
+ }
+ };
+
+ mHost.addOnScrollListener(mScrollListener);
+ }
+
+ /**
+ * Start a band select operation at the given point.
+ *
+ * @param relativeOrigin The origin of the band select operation, relative to the viewport.
+ * For example, if the view is scrolled to the bottom, the top-left of
+ * the
+ * viewport
+ * would have a relative origin of (0, 0), even though its absolute point
+ * has a higher
+ * y-value.
+ */
+ void startCapturing(Point relativeOrigin) {
+ recordVisibleChildren();
+ if (isEmpty()) {
+ // The selection band logic works only if there is at least one visible child.
+ return;
+ }
+
+ mIsActive = true;
+ mPointer = mHost.createAbsolutePoint(relativeOrigin);
+ mRelOrigin = createRelativePoint(mPointer);
+ mRelPointer = createRelativePoint(mPointer);
+ computeCurrentSelection();
+ notifySelectionChanged();
+ }
+
+ /**
+ * Ends the band selection.
+ */
+ void stopCapturing() {
+ mIsActive = false;
+ }
+
+ /**
+ * Resizes the selection by adjusting the pointer (i.e., the corner of the selection
+ * opposite the origin.
+ *
+ * @param relativePointer The pointer (opposite of the origin) of the band select operation,
+ * relative to the viewport. For example, if the view is scrolled to the
+ * bottom, the
+ * top-left of the viewport would have a relative origin of (0, 0), even
+ * though its
+ * absolute point has a higher y-value.
+ */
+ @VisibleForTesting
+ void resizeSelection(Point relativePointer) {
+ mPointer = mHost.createAbsolutePoint(relativePointer);
+ updateModel();
+ }
+
+ /**
+ * @return The adapter position for the item nearest the origin corresponding to the latest
+ * band select operation, or NOT_SET if the selection did not cover any items.
+ */
+ int getPositionNearestOrigin() {
+ return mPositionNearestOrigin;
+ }
+
+ private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ if (!mIsActive) {
+ return;
+ }
+
+ mPointer.x += dx;
+ mPointer.y += dy;
+ recordVisibleChildren();
+ updateModel();
+ }
+
+ /**
+ * Queries the view for all children and records their location metadata.
+ */
+ private void recordVisibleChildren() {
+ for (int i = 0; i < mHost.getVisibleChildCount(); i++) {
+ int adapterPosition = mHost.getAdapterPositionAt(i);
+ // Sometimes the view is not attached, as we notify the multi selection manager
+ // synchronously, while views are attached asynchronously. As a result items which
+ // are in the adapter may not actually have a corresponding view (yet).
+ if (mHost.hasView(adapterPosition)
+ && mSelectionPredicate.canSetStateAtPosition(adapterPosition, true)
+ && !mKnownPositions.get(adapterPosition)) {
+ mKnownPositions.put(adapterPosition, true);
+ recordItemData(mHost.getAbsoluteRectForChildViewAt(i), adapterPosition);
+ }
+ }
+ }
+
+ /**
+ * Checks if there are any recorded children.
+ */
+ private boolean isEmpty() {
+ return mColumnBounds.size() == 0 || mRowBounds.size() == 0;
+ }
+
+ /**
+ * Updates the limits lists and column map with the given item metadata.
+ *
+ * @param absoluteChildRect The absolute rectangle for the child view being processed.
+ * @param adapterPosition The position of the child view being processed.
+ */
+ private void recordItemData(Rect absoluteChildRect, int adapterPosition) {
+ if (mColumnBounds.size() != mHost.getColumnCount()) {
+ // If not all x-limits have been recorded, record this one.
+ recordLimits(
+ mColumnBounds, new Limits(absoluteChildRect.left, absoluteChildRect.right));
+ }
+
+ recordLimits(mRowBounds, new Limits(absoluteChildRect.top, absoluteChildRect.bottom));
+
+ SparseIntArray columnList = mColumns.get(absoluteChildRect.left);
+ if (columnList == null) {
+ columnList = new SparseIntArray();
+ mColumns.put(absoluteChildRect.left, columnList);
+ }
+ columnList.put(absoluteChildRect.top, adapterPosition);
+ }
+
+ /**
+ * Ensures limits exists within the sorted list limitsList, and adds it to the list if it
+ * does not exist.
+ */
+ private void recordLimits(List<Limits> limitsList, Limits limits) {
+ int index = Collections.binarySearch(limitsList, limits);
+ if (index < 0) {
+ limitsList.add(~index, limits);
+ }
+ }
+
+ /**
+ * Handles a moved pointer; this function determines whether the pointer movement resulted
+ * in a selection change and, if it has, notifies listeners of this change.
+ */
+ private void updateModel() {
+ RelativePoint old = mRelPointer;
+ mRelPointer = createRelativePoint(mPointer);
+ if (old != null && mRelPointer.equals(old)) {
+ return;
+ }
+
+ computeCurrentSelection();
+ notifySelectionChanged();
+ }
+
+ /**
+ * Computes the currently-selected items.
+ */
+ private void computeCurrentSelection() {
+ if (areItemsCoveredByBand(mRelPointer, mRelOrigin)) {
+ updateSelection(computeBounds());
+ } else {
+ mSelection.clear();
+ mPositionNearestOrigin = NOT_SET;
+ }
+ }
+
+ /**
+ * Notifies all listeners of a selection change. Note that this function simply passes
+ * mSelection, so computeCurrentSelection() should be called before this
+ * function.
+ */
+ private void notifySelectionChanged() {
+ for (SelectionObserver listener : mOnSelectionChangedListeners) {
+ listener.onSelectionChanged(mSelection);
+ }
+ }
+
+ /**
+ * @param rect Rectangle including all covered items.
+ */
+ private void updateSelection(Rect rect) {
+ int columnStart =
+ Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left));
+
+ checkArgument(columnStart >= 0, "Rect doesn't intesect any known column.");
+
+ int columnEnd = columnStart;
+
+ for (int i = columnStart; i < mColumnBounds.size()
+ && mColumnBounds.get(i).lowerLimit <= rect.right; i++) {
+ columnEnd = i;
+ }
+
+ int rowStart = Collections.binarySearch(mRowBounds, new Limits(rect.top, rect.top));
+ if (rowStart < 0) {
+ mPositionNearestOrigin = NOT_SET;
+ return;
+ }
+
+ int rowEnd = rowStart;
+ for (int i = rowStart; i < mRowBounds.size()
+ && mRowBounds.get(i).lowerLimit <= rect.bottom; i++) {
+ rowEnd = i;
+ }
+
+ updateSelection(columnStart, columnEnd, rowStart, rowEnd);
+ }
+
+ /**
+ * Computes the selection given the previously-computed start- and end-indices for each
+ * row and column.
+ */
+ private void updateSelection(
+ int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) {
+
+ if (BandSelectionHelper.DEBUG) {
+ Log.d(BandSelectionHelper.TAG, String.format(
+ "updateSelection: %d, %d, %d, %d",
+ columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex));
+ }
+
+ mSelection.clear();
+ for (int column = columnStartIndex; column <= columnEndIndex; column++) {
+ SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit);
+ for (int row = rowStartIndex; row <= rowEndIndex; row++) {
+ // The default return value for SparseIntArray.get is 0, which is a valid
+ // position. Use a sentry value to prevent erroneously selecting item 0.
+ final int rowKey = mRowBounds.get(row).lowerLimit;
+ int position = items.get(rowKey, NOT_SET);
+ if (position != NOT_SET) {
+ K key = mKeyProvider.getKey(position);
+ if (key != null) {
+ // The adapter inserts items for UI layout purposes that aren't
+ // associated with files. Those will have a null model ID.
+ // Don't select them.
+ if (canSelect(key)) {
+ mSelection.add(key);
+ }
+ }
+ if (isPossiblePositionNearestOrigin(column, columnStartIndex, columnEndIndex,
+ row, rowStartIndex, rowEndIndex)) {
+ // If this is the position nearest the origin, record it now so that it
+ // can be returned by endSelection() later.
+ mPositionNearestOrigin = position;
+ }
+ }
+ }
+ }
+ }
+
+ private boolean canSelect(K key) {
+ return mSelectionPredicate.canSetStateForKey(key, true);
+ }
+
+ /**
+ * @return Returns true if the position is the nearest to the origin, or, in the case of the
+ * lower-right corner, whether it is possible that the position is the nearest to the
+ * origin. See comment below for reasoning for this special case.
+ */
+ private boolean isPossiblePositionNearestOrigin(int columnIndex, int columnStartIndex,
+ int columnEndIndex, int rowIndex, int rowStartIndex, int rowEndIndex) {
+ int corner = computeCornerNearestOrigin();
+ switch (corner) {
+ case UPPER_LEFT:
+ return columnIndex == columnStartIndex && rowIndex == rowStartIndex;
+ case UPPER_RIGHT:
+ return columnIndex == columnEndIndex && rowIndex == rowStartIndex;
+ case LOWER_LEFT:
+ return columnIndex == columnStartIndex && rowIndex == rowEndIndex;
+ case LOWER_RIGHT:
+ // Note that in some cases, the last row will not have as many items as there
+ // are columns (e.g., if there are 4 items and 3 columns, the second row will
+ // only have one item in the first column). This function is invoked for each
+ // position from left to right, so return true for any position in the bottom
+ // row and only the right-most position in the bottom row will be recorded.
+ return rowIndex == rowEndIndex;
+ default:
+ throw new RuntimeException("Invalid corner type.");
+ }
+ }
+
+ /**
+ * Listener for changes in which items have been band selected.
+ */
+ public abstract static class SelectionObserver<K> {
+ abstract void onSelectionChanged(Set<K> updatedSelection);
+ }
+
+ void addOnSelectionChangedListener(SelectionObserver listener) {
+ mOnSelectionChangedListeners.add(listener);
+ }
+
+ /**
+ * Called when {@link BandSelectionHelper} is finished with a GridModel.
+ */
+ void onDestroy() {
+ mOnSelectionChangedListeners.clear();
+ // Cleanup listeners to prevent memory leaks.
+ mHost.removeOnScrollListener(mScrollListener);
+ }
+
+ /**
+ * Limits of a view item. For example, if an item's left side is at x-value 5 and its right side
+ * is at x-value 10, the limits would be from 5 to 10. Used to record the left- and right sides
+ * of item columns and the top- and bottom sides of item rows so that it can be determined
+ * whether the pointer is located within the bounds of an item.
+ */
+ private static class Limits implements Comparable<Limits> {
+ public int lowerLimit;
+ public int upperLimit;
+
+ Limits(int lowerLimit, int upperLimit) {
+ this.lowerLimit = lowerLimit;
+ this.upperLimit = upperLimit;
+ }
+
+ @Override
+ public int compareTo(Limits other) {
+ return lowerLimit - other.lowerLimit;
+ }
+
+ @Override
+ public int hashCode() {
+ return lowerLimit ^ upperLimit;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Limits)) {
+ return false;
+ }
+
+ return ((Limits) other).lowerLimit == lowerLimit
+ && ((Limits) other).upperLimit == upperLimit;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + lowerLimit + ", " + upperLimit + ")";
+ }
+ }
+
+ /**
+ * The location of a coordinate relative to items. This class represents a general area of the
+ * view as it relates to band selection rather than an explicit point. For example, two
+ * different points within an item are considered to have the same "location" because band
+ * selection originating within the item would select the same items no matter which point
+ * was used. Same goes for points between items as well as those at the very beginning or end
+ * of the view.
+ *
+ * Tracking a coordinate (e.g., an x-value) as a CoordinateLocation instead of as an int has the
+ * advantage of tying the value to the Limits of items along that axis. This allows easy
+ * selection of items within those Limits as opposed to a search through every item to see if a
+ * given coordinate value falls within those Limits.
+ */
+ private static class RelativeCoordinate
+ implements Comparable<RelativeCoordinate> {
+ /**
+ * Location describing points after the last known item.
+ */
+ static final int AFTER_LAST_ITEM = 0;
+
+ /**
+ * Location describing points before the first known item.
+ */
+ static final int BEFORE_FIRST_ITEM = 1;
+
+ /**
+ * Location describing points between two items.
+ */
+ static final int BETWEEN_TWO_ITEMS = 2;
+
+ /**
+ * Location describing points within the limits of one item.
+ */
+ static final int WITHIN_LIMITS = 3;
+
+ /**
+ * The type of this coordinate, which is one of AFTER_LAST_ITEM, BEFORE_FIRST_ITEM,
+ * BETWEEN_TWO_ITEMS, or WITHIN_LIMITS.
+ */
+ public final int type;
+
+ /**
+ * The limits before the coordinate; only populated when type == WITHIN_LIMITS or type ==
+ * BETWEEN_TWO_ITEMS.
+ */
+ public Limits limitsBeforeCoordinate;
+
+ /**
+ * The limits after the coordinate; only populated when type == BETWEEN_TWO_ITEMS.
+ */
+ public Limits limitsAfterCoordinate;
+
+ // Limits of the first known item; only populated when type == BEFORE_FIRST_ITEM.
+ public Limits mFirstKnownItem;
+ // Limits of the last known item; only populated when type == AFTER_LAST_ITEM.
+ public Limits mLastKnownItem;
+
+ /**
+ * @param limitsList The sorted limits list for the coordinate type. If this
+ * CoordinateLocation is an x-value, mXLimitsList should be passed;
+ * otherwise,
+ * mYLimitsList should be pased.
+ * @param value The coordinate value.
+ */
+ RelativeCoordinate(List<Limits> limitsList, int value) {
+ int index = Collections.binarySearch(limitsList, new Limits(value, value));
+
+ if (index >= 0) {
+ this.type = WITHIN_LIMITS;
+ this.limitsBeforeCoordinate = limitsList.get(index);
+ } else if (~index == 0) {
+ this.type = BEFORE_FIRST_ITEM;
+ this.mFirstKnownItem = limitsList.get(0);
+ } else if (~index == limitsList.size()) {
+ Limits lastLimits = limitsList.get(limitsList.size() - 1);
+ if (lastLimits.lowerLimit <= value && value <= lastLimits.upperLimit) {
+ this.type = WITHIN_LIMITS;
+ this.limitsBeforeCoordinate = lastLimits;
+ } else {
+ this.type = AFTER_LAST_ITEM;
+ this.mLastKnownItem = lastLimits;
+ }
+ } else {
+ Limits limitsBeforeIndex = limitsList.get(~index - 1);
+ if (limitsBeforeIndex.lowerLimit <= value
+ && value <= limitsBeforeIndex.upperLimit) {
+ this.type = WITHIN_LIMITS;
+ this.limitsBeforeCoordinate = limitsList.get(~index - 1);
+ } else {
+ this.type = BETWEEN_TWO_ITEMS;
+ this.limitsBeforeCoordinate = limitsList.get(~index - 1);
+ this.limitsAfterCoordinate = limitsList.get(~index);
+ }
+ }
+ }
+
+ int toComparisonValue() {
+ if (type == BEFORE_FIRST_ITEM) {
+ return mFirstKnownItem.lowerLimit - 1;
+ } else if (type == AFTER_LAST_ITEM) {
+ return mLastKnownItem.upperLimit + 1;
+ } else if (type == BETWEEN_TWO_ITEMS) {
+ return limitsBeforeCoordinate.upperLimit + 1;
+ } else {
+ return limitsBeforeCoordinate.lowerLimit;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mFirstKnownItem.lowerLimit
+ ^ mLastKnownItem.upperLimit
+ ^ limitsBeforeCoordinate.upperLimit
+ ^ limitsBeforeCoordinate.lowerLimit;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof RelativeCoordinate)) {
+ return false;
+ }
+
+ RelativeCoordinate otherCoordinate = (RelativeCoordinate) other;
+ return toComparisonValue() == otherCoordinate.toComparisonValue();
+ }
+
+ @Override
+ public int compareTo(RelativeCoordinate other) {
+ return toComparisonValue() - other.toComparisonValue();
+ }
+ }
+
+ RelativePoint createRelativePoint(Point point) {
+ return new RelativePoint(
+ new RelativeCoordinate(mColumnBounds, point.x),
+ new RelativeCoordinate(mRowBounds, point.y));
+ }
+
+ /**
+ * The location of a point relative to the Limits of nearby items; consists of both an x- and
+ * y-RelativeCoordinateLocation.
+ */
+ private static class RelativePoint {
+
+ final RelativeCoordinate mX;
+ final RelativeCoordinate mY;
+
+ RelativePoint(List<Limits> columnLimits, List<Limits> rowLimits, Point point) {
+ this.mX = new RelativeCoordinate(columnLimits, point.x);
+ this.mY = new RelativeCoordinate(rowLimits, point.y);
+ }
+
+ RelativePoint(RelativeCoordinate x, RelativeCoordinate y) {
+ this.mX = x;
+ this.mY = y;
+ }
+
+ @Override
+ public int hashCode() {
+ return mX.toComparisonValue() ^ mY.toComparisonValue();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof RelativePoint)) {
+ return false;
+ }
+
+ RelativePoint otherPoint = (RelativePoint) other;
+ return mX.equals(otherPoint.mX) && mY.equals(otherPoint.mY);
+ }
+ }
+
+ /**
+ * Generates a rectangle which contains the items selected by the pointer and origin.
+ *
+ * @return The rectangle, or null if no items were selected.
+ */
+ private Rect computeBounds() {
+ Rect rect = new Rect();
+ rect.left = getCoordinateValue(
+ min(mRelOrigin.mX, mRelPointer.mX),
+ mColumnBounds,
+ true);
+ rect.right = getCoordinateValue(
+ max(mRelOrigin.mX, mRelPointer.mX),
+ mColumnBounds,
+ false);
+ rect.top = getCoordinateValue(
+ min(mRelOrigin.mY, mRelPointer.mY),
+ mRowBounds,
+ true);
+ rect.bottom = getCoordinateValue(
+ max(mRelOrigin.mY, mRelPointer.mY),
+ mRowBounds,
+ false);
+ return rect;
+ }
+
+ /**
+ * Computes the corner of the selection nearest the origin.
+ */
+ private int computeCornerNearestOrigin() {
+ int cornerValue = 0;
+
+ if (mRelOrigin.mY.equals(min(mRelOrigin.mY, mRelPointer.mY))) {
+ cornerValue |= UPPER;
+ } else {
+ cornerValue |= LOWER;
+ }
+
+ if (mRelOrigin.mX.equals(min(mRelOrigin.mX, mRelPointer.mX))) {
+ cornerValue |= LEFT;
+ } else {
+ cornerValue |= RIGHT;
+ }
+
+ return cornerValue;
+ }
+
+ private RelativeCoordinate min(RelativeCoordinate first, RelativeCoordinate second) {
+ return first.compareTo(second) < 0 ? first : second;
+ }
+
+ private RelativeCoordinate max(RelativeCoordinate first, RelativeCoordinate second) {
+ return first.compareTo(second) > 0 ? first : second;
+ }
+
+ /**
+ * @return The absolute coordinate (i.e., the x- or y-value) of the given relative
+ * coordinate.
+ */
+ private int getCoordinateValue(
+ RelativeCoordinate coordinate, List<Limits> limitsList, boolean isStartOfRange) {
+
+ switch (coordinate.type) {
+ case RelativeCoordinate.BEFORE_FIRST_ITEM:
+ return limitsList.get(0).lowerLimit;
+ case RelativeCoordinate.AFTER_LAST_ITEM:
+ return limitsList.get(limitsList.size() - 1).upperLimit;
+ case RelativeCoordinate.BETWEEN_TWO_ITEMS:
+ if (isStartOfRange) {
+ return coordinate.limitsAfterCoordinate.lowerLimit;
+ } else {
+ return coordinate.limitsBeforeCoordinate.upperLimit;
+ }
+ case RelativeCoordinate.WITHIN_LIMITS:
+ return coordinate.limitsBeforeCoordinate.lowerLimit;
+ }
+
+ throw new RuntimeException("Invalid coordinate value.");
+ }
+
+ private boolean areItemsCoveredByBand(
+ RelativePoint first, RelativePoint second) {
+
+ return doesCoordinateLocationCoverItems(first.mX, second.mX)
+ && doesCoordinateLocationCoverItems(first.mY, second.mY);
+ }
+
+ private boolean doesCoordinateLocationCoverItems(
+ RelativeCoordinate pointerCoordinate, RelativeCoordinate originCoordinate) {
+
+ if (pointerCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM
+ && originCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM) {
+ return false;
+ }
+
+ if (pointerCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM
+ && originCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM) {
+ return false;
+ }
+
+ if (pointerCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS
+ && originCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS
+ && pointerCoordinate.limitsBeforeCoordinate.equals(
+ originCoordinate.limitsBeforeCoordinate)
+ && pointerCoordinate.limitsAfterCoordinate.equals(
+ originCoordinate.limitsAfterCoordinate)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Provides functionality for BandController. Exists primarily to tests that are
+ * fully isolated from RecyclerView.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+ abstract static class GridHost<K> extends BandSelectionHelper.BandHost<K> {
+
+ /**
+ * Remove the listener.
+ *
+ * @param listener
+ */
+ abstract void removeOnScrollListener(RecyclerView.OnScrollListener listener);
+
+ /**
+ * @param relativePoint for which to create absolute point.
+ * @return absolute point.
+ */
+ abstract Point createAbsolutePoint(Point relativePoint);
+
+ /**
+ * @param index index of child.
+ * @return rectangle describing child at {@code index}.
+ */
+ abstract Rect getAbsoluteRectForChildViewAt(int index);
+
+ /**
+ * @param index index of child.
+ * @return child adapter position for the child at {@code index}
+ */
+ abstract int getAdapterPositionAt(int index);
+
+ /** @return column count. */
+ abstract int getColumnCount();
+
+ /** @return number of children visible in the view. */
+ abstract int getVisibleChildCount();
+
+ /**
+ * @return true if the item at adapter position is attached to a view.
+ */
+ abstract boolean hasView(int adapterPosition);
+ }
+}
diff --git a/androidx/recyclerview/selection/ItemDetailsLookup.java b/androidx/recyclerview/selection/ItemDetailsLookup.java
new file mode 100644
index 00000000..da30c97a
--- /dev/null
+++ b/androidx/recyclerview/selection/ItemDetailsLookup.java
@@ -0,0 +1,149 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+/**
+ * Provides event handlers w/ access to details about documents details
+ * view items Documents in the UI (RecyclerView).
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ItemDetailsLookup<K> {
+
+ /** @return true if there is an item under the finger/cursor. */
+ public boolean overItem(MotionEvent e) {
+ return getItemPosition(e) != RecyclerView.NO_POSITION;
+ }
+
+ /** @return true if there is an item w/ a stable ID under the finger/cursor. */
+ public boolean overItemWithSelectionKey(MotionEvent e) {
+ return overItem(e) && hasSelectionKey(getItemDetails(e));
+ }
+
+ /**
+ * @return true if the event is over an area that can be dragged via touch
+ * or via mouse. List items have a white area that is not draggable.
+ */
+ public boolean inItemDragRegion(MotionEvent e) {
+ return overItem(e) && getItemDetails(e).inDragRegion(e);
+ }
+
+ /**
+ * @return true if the event is in the "selection hot spot" region.
+ * The hot spot region instantly selects in touch mode, vs launches.
+ */
+ public boolean inItemSelectRegion(MotionEvent e) {
+ return overItem(e) && getItemDetails(e).inSelectionHotspot(e);
+ }
+
+ /**
+ * @return the adapter position of the item under the finger/cursor.
+ */
+ public int getItemPosition(MotionEvent e) {
+ @Nullable ItemDetails<?> item = getItemDetails(e);
+ return item != null
+ ? item.getPosition()
+ : RecyclerView.NO_POSITION;
+ }
+
+ private static boolean hasSelectionKey(@Nullable ItemDetails<?> item) {
+ return item != null && item.getSelectionKey() != null;
+ }
+
+ private static boolean hasPosition(@Nullable ItemDetails<?> item) {
+ return item != null && item.getPosition() != RecyclerView.NO_POSITION;
+ }
+
+ /**
+ * @return the DocumentDetails for the item under the event, or null.
+ */
+ public abstract @Nullable ItemDetails<K> getItemDetails(MotionEvent e);
+
+ /**
+ * Abstract class providing helper classes with access to information about
+ * RecyclerView item associated with a MotionEvent.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+ // TODO: Can this be merged with ViewHolder?
+ public abstract static class ItemDetails<K> {
+
+ /** @return the position of an item. */
+ public abstract int getPosition();
+
+ /** @return true if the item has a stable id. */
+ public boolean hasSelectionKey() {
+ return getSelectionKey() != null;
+ }
+
+ /** @return the stable id of an item. */
+ public abstract @Nullable K getSelectionKey();
+
+ /**
+ * @return true if the event is in an area of the item that should be
+ * directly interpreted as a user wishing to select the item. This
+ * is useful for checkboxes and other UI affordances focused on enabling
+ * selection.
+ */
+ public boolean inSelectionHotspot(MotionEvent e) {
+ return false;
+ }
+
+ /**
+ * Events in the drag region will dealt with differently that events outside
+ * of the drag region. This allows the client to implement custom handling
+ * for events related to drag and drop.
+ */
+ public boolean inDragRegion(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ItemDetails) {
+ return isEqualTo((ItemDetails) obj);
+ }
+ return false;
+ }
+
+ private boolean isEqualTo(ItemDetails other) {
+ K key = getSelectionKey();
+ boolean sameKeys = false;
+ if (key == null) {
+ sameKeys = other.getSelectionKey() == null;
+ } else {
+ sameKeys = key.equals(other.getSelectionKey());
+ }
+ return sameKeys && this.getPosition() == other.getPosition();
+ }
+
+ @Override
+ public int hashCode() {
+ return getPosition() >>> 8;
+ }
+ }
+}
diff --git a/androidx/recyclerview/selection/ItemKeyProvider.java b/androidx/recyclerview/selection/ItemKeyProvider.java
new file mode 100644
index 00000000..134c4420
--- /dev/null
+++ b/androidx/recyclerview/selection/ItemKeyProvider.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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides support for sting based stable ids in the RecyclerView selection helper.
+ * Client code can use this to look up stable ids when working with selection
+ * in application code.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ItemKeyProvider<K> {
+
+ /**
+ * Provides access to all data, regardless of whether it is bound to a view or not.
+ * Key providers with this access type enjoy support for enhanced features like:
+ * SHIFT+click range selection, and band selection.
+ */
+ @VisibleForTesting // otherwise protected would do nicely.
+ public static final int SCOPE_MAPPED = 0;
+
+ /**
+ * Provides access cached data based on what was recently bound in the view.
+ * Employing this provider will result in a reduced feature-set, as some
+ * featuers like SHIFT+click range selection and band selection are dependent
+ * on mapped access.
+ */
+ @VisibleForTesting // otherwise protected would do nicely.
+ public static final int SCOPE_CACHED = 1;
+
+ @IntDef({
+ SCOPE_MAPPED,
+ SCOPE_CACHED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ protected @interface Scope {}
+
+ private final @Scope int mScope;
+
+ /**
+ * Creates a new provider with the given scope.
+ * @param scope Scope can't change at runtime (at least code won't adapt)
+ * so it must be specified in the constructor.
+ */
+ protected ItemKeyProvider(@Scope int scope) {
+ checkArgument(scope == SCOPE_MAPPED || scope == SCOPE_CACHED);
+
+ mScope = scope;
+ }
+
+ final boolean hasAccess(@Scope int scope) {
+ return scope == mScope;
+ }
+
+ /**
+ * @return The selection key of the item at the given adapter position.
+ */
+ public abstract @Nullable K getKey(int position);
+
+ /**
+ * @return the position of a stable ID, or RecyclerView.NO_POSITION.
+ */
+ public abstract int getPosition(K key);
+}
diff --git a/androidx/recyclerview/selection/MotionEvents.java b/androidx/recyclerview/selection/MotionEvents.java
new file mode 100644
index 00000000..dd9e54f2
--- /dev/null
+++ b/androidx/recyclerview/selection/MotionEvents.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 androidx.recyclerview.selection;
+
+import android.graphics.Point;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Utility methods for working with {@link MotionEvent} instances.
+ */
+final class MotionEvents {
+
+ private MotionEvents() {}
+
+ static boolean isMouseEvent(MotionEvent e) {
+ return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
+ }
+
+ static boolean isTouchEvent(MotionEvent e) {
+ return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER;
+ }
+
+ static boolean isActionMove(MotionEvent e) {
+ return e.getActionMasked() == MotionEvent.ACTION_MOVE;
+ }
+
+ static boolean isActionDown(MotionEvent e) {
+ return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+ }
+
+ static boolean isActionUp(MotionEvent e) {
+ return e.getActionMasked() == MotionEvent.ACTION_UP;
+ }
+
+ static boolean isActionPointerUp(MotionEvent e) {
+ return e.getActionMasked() == MotionEvent.ACTION_POINTER_UP;
+ }
+
+ @SuppressWarnings("unused")
+ static boolean isActionPointerDown(MotionEvent e) {
+ return e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN;
+ }
+
+ static boolean isActionCancel(MotionEvent e) {
+ return e.getActionMasked() == MotionEvent.ACTION_CANCEL;
+ }
+
+ static Point getOrigin(MotionEvent e) {
+ return new Point((int) e.getX(), (int) e.getY());
+ }
+
+ static boolean isPrimaryButtonPressed(MotionEvent e) {
+ return isButtonPressed(e, MotionEvent.BUTTON_PRIMARY);
+ }
+
+ static boolean isSecondaryButtonPressed(MotionEvent e) {
+ return isButtonPressed(e, MotionEvent.BUTTON_SECONDARY);
+ }
+
+ static boolean isTertiaryButtonPressed(MotionEvent e) {
+ return isButtonPressed(e, MotionEvent.BUTTON_TERTIARY);
+ }
+
+ // TODO: Replace with MotionEvent.isButtonPressed once targeting 21 or higher.
+ private static boolean isButtonPressed(MotionEvent e, int button) {
+ if (button == 0) {
+ return false;
+ }
+ return (e.getButtonState() & button) == button;
+ }
+
+ static boolean isShiftKeyPressed(MotionEvent e) {
+ return hasBit(e.getMetaState(), KeyEvent.META_SHIFT_ON);
+ }
+
+ static boolean isCtrlKeyPressed(MotionEvent e) {
+ return hasBit(e.getMetaState(), KeyEvent.META_CTRL_ON);
+ }
+
+ static boolean isAltKeyPressed(MotionEvent e) {
+ return hasBit(e.getMetaState(), KeyEvent.META_ALT_ON);
+ }
+
+ static boolean isTouchpadScroll(MotionEvent e) {
+ // Touchpad inputs are treated as mouse inputs, and when scrolling, there are no buttons
+ // returned.
+ return isMouseEvent(e) && isActionMove(e) && e.getButtonState() == 0;
+ }
+
+ private static boolean hasBit(int metaState, int bit) {
+ return (metaState & bit) != 0;
+ }
+}
diff --git a/androidx/recyclerview/selection/MotionInputHandler.java b/androidx/recyclerview/selection/MotionInputHandler.java
new file mode 100644
index 00000000..1c063028
--- /dev/null
+++ b/androidx/recyclerview/selection/MotionInputHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Base class for handlers that can be registered w/ {@link GestureRouter}.
+ */
+abstract class MotionInputHandler<K> extends SimpleOnGestureListener {
+
+ protected final SelectionHelper<K> mSelectionHelper;
+
+ private final ItemKeyProvider<K> mKeyProvider;
+ private final FocusCallbacks<K> mFocusCallbacks;
+
+ MotionInputHandler(
+ SelectionHelper<K> selectionHelper,
+ ItemKeyProvider<K> keyProvider,
+ FocusCallbacks<K> focusCallbacks) {
+
+ checkArgument(selectionHelper != null);
+ checkArgument(keyProvider != null);
+ checkArgument(focusCallbacks != null);
+
+ mSelectionHelper = selectionHelper;
+ mKeyProvider = keyProvider;
+ mFocusCallbacks = focusCallbacks;
+ }
+
+ final boolean selectItem(ItemDetails<K> details) {
+ checkArgument(details != null);
+ checkArgument(hasPosition(details));
+ checkArgument(hasSelectionKey(details));
+
+ if (mSelectionHelper.select(details.getSelectionKey())) {
+ mSelectionHelper.anchorRange(details.getPosition());
+ }
+
+ // we set the focus on this doc so it will be the origin for keyboard events or shift+clicks
+ // if there is only a single item selected, otherwise clear focus
+ if (mSelectionHelper.getSelection().size() == 1) {
+ mFocusCallbacks.focusItem(details);
+ } else {
+ mFocusCallbacks.clearFocus();
+ }
+ return true;
+ }
+
+ protected final boolean focusItem(ItemDetails<K> details) {
+ checkArgument(details != null);
+ checkArgument(hasSelectionKey(details));
+
+ mSelectionHelper.clearSelection();
+ mFocusCallbacks.focusItem(details);
+ return true;
+ }
+
+ protected final void extendSelectionRange(ItemDetails<K> details) {
+ checkState(mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED));
+ checkArgument(hasPosition(details));
+ checkArgument(hasSelectionKey(details));
+
+ mSelectionHelper.extendRange(details.getPosition());
+ mFocusCallbacks.focusItem(details);
+ }
+
+ final boolean isRangeExtension(MotionEvent e) {
+ return MotionEvents.isShiftKeyPressed(e)
+ && mSelectionHelper.isRangeActive()
+ // Without full corpus access we can't reliably implement range
+ // as a user can scroll *anywhere* then SHIFT+click.
+ && mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED);
+ }
+
+ boolean shouldClearSelection(MotionEvent e, ItemDetails<K> item) {
+ return !MotionEvents.isCtrlKeyPressed(e)
+ && !item.inSelectionHotspot(e)
+ && !mSelectionHelper.isSelected(item.getSelectionKey());
+ }
+
+ static boolean hasSelectionKey(@Nullable ItemDetails<?> item) {
+ return item != null && item.getSelectionKey() != null;
+ }
+
+ static boolean hasPosition(@Nullable ItemDetails<?> item) {
+ return item != null && item.getPosition() != RecyclerView.NO_POSITION;
+ }
+}
diff --git a/androidx/recyclerview/selection/MouseCallbacks.java b/androidx/recyclerview/selection/MouseCallbacks.java
new file mode 100644
index 00000000..05c47c1f
--- /dev/null
+++ b/androidx/recyclerview/selection/MouseCallbacks.java
@@ -0,0 +1,48 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class MouseCallbacks {
+
+ static final MouseCallbacks DUMMY = new MouseCallbacks() {
+ @Override
+ public boolean onContextClick(MotionEvent e) {
+ return false;
+ }
+ };
+
+ /**
+ * Called when user performs a context click, usually via mouse pointer
+ * right-click.
+ *
+ * @param e the event associated with the click.
+ * @return true if the event was handled.
+ */
+ public abstract boolean onContextClick(MotionEvent e);
+}
diff --git a/androidx/recyclerview/selection/MouseInputHandler.java b/androidx/recyclerview/selection/MouseInputHandler.java
new file mode 100644
index 00000000..0b4ea2ca
--- /dev/null
+++ b/androidx/recyclerview/selection/MouseInputHandler.java
@@ -0,0 +1,223 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * A MotionInputHandler that provides the high-level glue for mouse/stylus driven selection. This
+ * class works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper}
+ * to provide robust user drive selection support.
+ */
+final class MouseInputHandler<K> extends MotionInputHandler<K> {
+
+ private static final String TAG = "MouseInputDelegate";
+
+ private final ItemDetailsLookup<K> mDetailsLookup;
+ private final MouseCallbacks mMouseCallbacks;
+ private final ActivationCallbacks<K> mActivationCallbacks;
+ private final FocusCallbacks<K> mFocusCallbacks;
+
+ // The event has been handled in onSingleTapUp
+ private boolean mHandledTapUp;
+ // true when the previous event has consumed a right click motion event
+ private boolean mHandledOnDown;
+
+ MouseInputHandler(
+ SelectionHelper<K> selectionHelper,
+ ItemKeyProvider<K> keyProvider,
+ ItemDetailsLookup<K> detailsLookup,
+ MouseCallbacks mouseCallbacks,
+ ActivationCallbacks<K> activationCallbacks,
+ FocusCallbacks<K> focusCallbacks) {
+
+ super(selectionHelper, keyProvider, focusCallbacks);
+
+ checkArgument(detailsLookup != null);
+ checkArgument(mouseCallbacks != null);
+ checkArgument(activationCallbacks != null);
+
+ mDetailsLookup = detailsLookup;
+ mMouseCallbacks = mouseCallbacks;
+ mActivationCallbacks = activationCallbacks;
+ mFocusCallbacks = focusCallbacks;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ if (VERBOSE) Log.v(TAG, "Delegated onDown event.");
+ if ((MotionEvents.isAltKeyPressed(e) && MotionEvents.isPrimaryButtonPressed(e))
+ || MotionEvents.isSecondaryButtonPressed(e)) {
+ mHandledOnDown = true;
+ return onRightClick(e);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ // Don't scroll content window in response to mouse drag
+ // If it's two-finger trackpad scrolling, we want to scroll
+ return !MotionEvents.isTouchpadScroll(e2);
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // See b/27377794. Since we don't get a button state back from UP events, we have to
+ // explicitly save this state to know whether something was previously handled by
+ // DOWN events or not.
+ if (mHandledOnDown) {
+ if (VERBOSE) Log.v(TAG, "Ignoring onSingleTapUp, previously handled in onDown.");
+ mHandledOnDown = false;
+ return false;
+ }
+
+ if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+ if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
+ mSelectionHelper.clearSelection();
+ mFocusCallbacks.clearFocus();
+ return false;
+ }
+
+ if (MotionEvents.isTertiaryButtonPressed(e)) {
+ if (DEBUG) Log.d(TAG, "Ignoring middle click");
+ return false;
+ }
+
+ if (mSelectionHelper.hasSelection()) {
+ onItemClick(e, mDetailsLookup.getItemDetails(e));
+ mHandledTapUp = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ // tap on an item when there is an existing selection. We could extend
+ // a selection, we could clear selection (then launch)
+ private void onItemClick(MotionEvent e, ItemDetails<K> item) {
+ checkState(mSelectionHelper.hasSelection());
+ checkArgument(item != null);
+
+ if (isRangeExtension(e)) {
+ extendSelectionRange(item);
+ } else {
+ if (shouldClearSelection(e, item)) {
+ mSelectionHelper.clearSelection();
+ }
+ if (mSelectionHelper.isSelected(item.getSelectionKey())) {
+ if (mSelectionHelper.deselect(item.getSelectionKey())) {
+ mFocusCallbacks.clearFocus();
+ }
+ } else {
+ selectOrFocusItem(item, e);
+ }
+ }
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if (mHandledTapUp) {
+ if (VERBOSE) {
+ Log.v(TAG,
+ "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
+ }
+ mHandledTapUp = false;
+ return false;
+ }
+
+ if (mSelectionHelper.hasSelection()) {
+ return false; // should have been handled by onSingleTapUp.
+ }
+
+ if (!mDetailsLookup.overItem(e)) {
+ if (DEBUG) Log.d(TAG, "Ignoring Confirmed Tap on non-item.");
+ return false;
+ }
+
+ if (MotionEvents.isTertiaryButtonPressed(e)) {
+ if (DEBUG) Log.d(TAG, "Ignoring middle click");
+ return false;
+ }
+
+ @Nullable ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+ if (item == null || !item.hasSelectionKey()) {
+ return false;
+ }
+
+ if (mFocusCallbacks.hasFocusedItem() && MotionEvents.isShiftKeyPressed(e)) {
+ mSelectionHelper.startRange(mFocusCallbacks.getFocusedPosition());
+ mSelectionHelper.extendRange(item.getPosition());
+ } else {
+ selectOrFocusItem(item, e);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ mHandledTapUp = false;
+
+ if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+ if (DEBUG) Log.d(TAG, "Ignoring DoubleTap on non-model-backed item.");
+ return false;
+ }
+
+ if (MotionEvents.isTertiaryButtonPressed(e)) {
+ if (DEBUG) Log.d(TAG, "Ignoring middle click");
+ return false;
+ }
+
+ ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+ return (item != null) && mActivationCallbacks.onItemActivated(item, e);
+ }
+
+ private boolean onRightClick(MotionEvent e) {
+ if (mDetailsLookup.overItemWithSelectionKey(e)) {
+ @Nullable ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+ if (item != null && !mSelectionHelper.isSelected(item.getSelectionKey())) {
+ mSelectionHelper.clearSelection();
+ selectItem(item);
+ }
+ }
+
+ // We always delegate final handling of the event,
+ // since the handler might want to show a context menu
+ // in an empty area or some other weirdo view.
+ return mMouseCallbacks.onContextClick(e);
+ }
+
+ private void selectOrFocusItem(ItemDetails<K> item, MotionEvent e) {
+ if (item.inSelectionHotspot(e) || MotionEvents.isCtrlKeyPressed(e)) {
+ selectItem(item);
+ } else {
+ focusItem(item);
+ }
+ }
+}
diff --git a/androidx/recyclerview/selection/MutableSelection.java b/androidx/recyclerview/selection/MutableSelection.java
new file mode 100644
index 00000000..6e116986
--- /dev/null
+++ b/androidx/recyclerview/selection/MutableSelection.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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * Subclass of Selection exposing public support for mutating the underlying selection data.
+ * This is useful for clients of {@link SelectionHelper} that wish to manipulate
+ * a copy of selection data obtained via {@link SelectionHelper#copySelection(Selection)}.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class MutableSelection<K> extends Selection<K> {
+
+ @Override
+ public boolean add(K key) {
+ return super.add(key);
+ }
+
+ @Override
+ public boolean remove(K key) {
+ return super.remove(key);
+ }
+
+ @Override
+ public void copyFrom(Selection<K> source) {
+ super.copyFrom(source);
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ }
+}
diff --git a/androidx/recyclerview/selection/Range.java b/androidx/recyclerview/selection/Range.java
new file mode 100644
index 00000000..632e4363
--- /dev/null
+++ b/androidx/recyclerview/selection/Range.java
@@ -0,0 +1,186 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.support.annotation.IntDef;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class providing support for managing range selections.
+ */
+final class Range {
+
+ static final int TYPE_PRIMARY = 0;
+
+ /**
+ * "Provisional" selection represents a overlay on the primary selection. A provisional
+ * selection maybe be eventually added to the primary selection, or it may be abandoned.
+ *
+ * <p>E.g. BandSelectionHelper creates a provisional selection while a user is actively
+ * selecting items with a band. GestureSelectionHelper creates a provisional selection
+ * while a user is active selecting via gesture.
+ *
+ * <p>Provisionally selected items are considered to be selected in
+ * {@link Selection#contains(String)} and related methods. A provisional may be abandoned or
+ * merged into the promary selection.
+ *
+ * <p>A provisional selection may intersect with the primary selection, however clearing the
+ * provisional selection will not affect the primary selection where the two may intersect.
+ */
+ static final int TYPE_PROVISIONAL = 1;
+ @IntDef({
+ TYPE_PRIMARY,
+ TYPE_PROVISIONAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RangeType {}
+
+ private static final String TAG = "Range";
+
+ private final Callbacks mCallbacks;
+ private final int mBegin;
+ private int mEnd = NO_POSITION;
+
+ /**
+ * Creates a new range anchored at {@code position}.
+ *
+ * @param position
+ * @param callbacks
+ */
+ Range(int position, Callbacks callbacks) {
+ mBegin = position;
+ mCallbacks = callbacks;
+ if (DEBUG) Log.d(TAG, "Creating new Range anchored @ " + position);
+ }
+
+ void extendRange(int position, @RangeType int type) {
+ checkArgument(position != NO_POSITION, "Position cannot be NO_POSITION.");
+
+ if (mEnd == NO_POSITION || mEnd == mBegin) {
+ // Reset mEnd so it can be established in establishRange.
+ mEnd = NO_POSITION;
+ establishRange(position, type);
+ } else {
+ reviseRange(position, type);
+ }
+ }
+
+ private void establishRange(int position, @RangeType int type) {
+ checkArgument(mEnd == NO_POSITION, "End has already been set.");
+
+ mEnd = position;
+
+ if (position > mBegin) {
+ if (DEBUG) log(type, "Establishing initial range at @ " + position);
+ updateRange(mBegin + 1, position, true, type);
+ } else if (position < mBegin) {
+ if (DEBUG) log(type, "Establishing initial range at @ " + position);
+ updateRange(position, mBegin - 1, true, type);
+ }
+ }
+
+ private void reviseRange(int position, @RangeType int type) {
+ checkArgument(mEnd != NO_POSITION, "End must already be set.");
+ checkArgument(mBegin != mEnd, "Beging and end point to same position.");
+
+ if (position == mEnd) {
+ if (DEBUG) log(type, "Ignoring no-op revision for range @ " + position);
+ }
+
+ if (mEnd > mBegin) {
+ reviseAscending(position, type);
+ } else if (mEnd < mBegin) {
+ reviseDescending(position, type);
+ }
+ // the "else" case is covered by checkState at beginning of method.
+
+ mEnd = position;
+ }
+
+ /**
+ * Updates an existing ascending selection.
+ */
+ private void reviseAscending(int position, @RangeType int type) {
+ if (DEBUG) log(type, "*ascending* Revising range @ " + position);
+
+ if (position < mEnd) {
+ if (position < mBegin) {
+ updateRange(mBegin + 1, mEnd, false, type);
+ updateRange(position, mBegin - 1, true, type);
+ } else {
+ updateRange(position + 1, mEnd, false, type);
+ }
+ } else if (position > mEnd) { // Extending the range...
+ updateRange(mEnd + 1, position, true, type);
+ }
+ }
+
+ private void reviseDescending(int position, @RangeType int type) {
+ if (DEBUG) log(type, "*descending* Revising range @ " + position);
+
+ if (position > mEnd) {
+ if (position > mBegin) {
+ updateRange(mEnd, mBegin - 1, false, type);
+ updateRange(mBegin + 1, position, true, type);
+ } else {
+ updateRange(mEnd, position - 1, false, type);
+ }
+ } else if (position < mEnd) { // Extending the range...
+ updateRange(position, mEnd - 1, true, type);
+ }
+ }
+
+ /**
+ * Try to set selection state for all elements in range. Not that callbacks can cancel
+ * selection of specific items, so some or even all items may not reflect the desired state
+ * after the update is complete.
+ *
+ * @param begin Adapter position for range start (inclusive).
+ * @param end Adapter position for range end (inclusive).
+ * @param selected New selection state.
+ */
+ private void updateRange(
+ int begin, int end, boolean selected, @RangeType int type) {
+ mCallbacks.updateForRange(begin, end, selected, type);
+ }
+
+ @Override
+ public String toString() {
+ return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
+ }
+
+ private void log(@RangeType int type, String message) {
+ String opType = type == TYPE_PRIMARY ? "PRIMARY" : "PROVISIONAL";
+ Log.d(TAG, String.valueOf(this) + ": " + message + " (" + opType + ")");
+ }
+
+ /*
+ * @see {@link DefaultSelectionHelper#updateForRange(int, int , boolean, int)}.
+ */
+ abstract static class Callbacks {
+ abstract void updateForRange(
+ int begin, int end, boolean selected, @RangeType int type);
+ }
+}
diff --git a/androidx/recyclerview/selection/Selection.java b/androidx/recyclerview/selection/Selection.java
new file mode 100644
index 00000000..a6225307
--- /dev/null
+++ b/androidx/recyclerview/selection/Selection.java
@@ -0,0 +1,259 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Object representing the current selection and provisional selection. Provides read only public
+ * access, and private write access.
+ * <p>
+ * This class tracks selected items by managing two sets:
+ *
+ * <li>primary selection
+ *
+ * Primary selection consists of items tapped by a user or by lassoed by band select operation.
+ *
+ * <li>provisional selection
+ *
+ * Provisional selections are selections which have been temporarily created
+ * by an in-progress band select or gesture selection. Once the user releases the mouse button
+ * or lifts their finger the corresponding provisional selection should be converted into
+ * primary selection.
+ *
+ * <p>The total selection is the combination of
+ * both the core selection and the provisional selection. Tracking both separately is necessary to
+ * ensure that items in the core selection are not "erased" from the core selection when they
+ * are temporarily included in a secondary selection (like band selection).
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class Selection<K> implements Iterable<K> {
+
+ // NOTE: Not currently private as DefaultSelectionHelper directly manipulates values.
+ final Set<K> mSelection;
+ final Set<K> mProvisionalSelection;
+
+ Selection() {
+ mSelection = new HashSet<>();
+ mProvisionalSelection = new HashSet<>();
+ }
+
+ /**
+ * Used by {@link SelectionStorage} when restoring selection.
+ */
+ Selection(Set<K> selection) {
+ mSelection = selection;
+ mProvisionalSelection = new HashSet<>();
+ }
+
+ /**
+ * @param key
+ * @return true if the position is currently selected.
+ */
+ public boolean contains(@Nullable K key) {
+ return mSelection.contains(key) || mProvisionalSelection.contains(key);
+ }
+
+ /**
+ * Returns an {@link Iterator} that iterators over the selection, *excluding*
+ * any provisional selection.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public Iterator<K> iterator() {
+ return mSelection.iterator();
+ }
+
+ /**
+ * @return size of the selection including both final and provisional selected items.
+ */
+ public int size() {
+ return mSelection.size() + mProvisionalSelection.size();
+ }
+
+ /**
+ * @return true if the selection is empty.
+ */
+ public boolean isEmpty() {
+ return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
+ }
+
+ /**
+ * Sets the provisional selection, which is a temporary selection that can be saved,
+ * canceled, or adjusted at a later time. When a new provision selection is applied, the old
+ * one (if it exists) is abandoned.
+ * @return Map of ids added or removed. Added ids have a value of true, removed are false.
+ */
+ Map<K, Boolean> setProvisionalSelection(Set<K> newSelection) {
+ Map<K, Boolean> delta = new HashMap<>();
+
+ for (K key: mProvisionalSelection) {
+ // Mark each item that used to be in the provisional selection
+ // but is not in the new provisional selection.
+ if (!newSelection.contains(key) && !mSelection.contains(key)) {
+ delta.put(key, false);
+ }
+ }
+
+ for (K key: mSelection) {
+ // Mark each item that used to be in the selection but is unsaved and not in the new
+ // provisional selection.
+ if (!newSelection.contains(key)) {
+ delta.put(key, false);
+ }
+ }
+
+ for (K key: newSelection) {
+ // Mark each item that was not previously in the selection but is in the new
+ // provisional selection.
+ if (!mSelection.contains(key) && !mProvisionalSelection.contains(key)) {
+ delta.put(key, true);
+ }
+ }
+
+ // Now, iterate through the changes and actually add/remove them to/from the current
+ // selection. This could not be done in the previous loops because changing the size of
+ // the selection mid-iteration changes iteration order erroneously.
+ for (Map.Entry<K, Boolean> entry: delta.entrySet()) {
+ K key = entry.getKey();
+ if (entry.getValue()) {
+ mProvisionalSelection.add(key);
+ } else {
+ mProvisionalSelection.remove(key);
+ }
+ }
+
+ return delta;
+ }
+
+ /**
+ * Saves the existing provisional selection. Once the provisional selection is saved,
+ * subsequent provisional selections which are different from this existing one cannot
+ * cause items in this existing provisional selection to become deselected.
+ */
+ @VisibleForTesting
+ protected void mergeProvisionalSelection() {
+ mSelection.addAll(mProvisionalSelection);
+ mProvisionalSelection.clear();
+ }
+
+ /**
+ * Abandons the existing provisional selection so that all items provisionally selected are
+ * now deselected.
+ */
+ @VisibleForTesting
+ void clearProvisionalSelection() {
+ mProvisionalSelection.clear();
+ }
+
+ /**
+ * Adds a new item to the primary selection.
+ *
+ * @return true if the operation resulted in a modification to the selection.
+ */
+ boolean add(K key) {
+ if (mSelection.contains(key)) {
+ return false;
+ }
+
+ mSelection.add(key);
+ return true;
+ }
+
+ /**
+ * Removes an item from the primary selection.
+ *
+ * @return true if the operation resulted in a modification to the selection.
+ */
+ boolean remove(K key) {
+ if (!mSelection.contains(key)) {
+ return false;
+ }
+
+ mSelection.remove(key);
+ return true;
+ }
+
+ /**
+ * Clears the primary selection. The provisional selection, if any, is unaffected.
+ */
+ void clear() {
+ mSelection.clear();
+ }
+
+ /**
+ * Clones primary and provisional selection from supplied {@link Selection}.
+ * Does not copy active range data.
+ */
+ void copyFrom(Selection<K> source) {
+ mSelection.clear();
+ mSelection.addAll(source.mSelection);
+
+ mProvisionalSelection.clear();
+ mProvisionalSelection.addAll(source.mProvisionalSelection);
+ }
+
+ @Override
+ public String toString() {
+ if (size() <= 0) {
+ return "size=0, items=[]";
+ }
+
+ StringBuilder buffer = new StringBuilder(size() * 28);
+ buffer.append("Selection{")
+ .append("primary{size=" + mSelection.size())
+ .append(", entries=" + mSelection)
+ .append("}, provisional{size=" + mProvisionalSelection.size())
+ .append(", entries=" + mProvisionalSelection)
+ .append("}}");
+ return buffer.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ return other instanceof Selection && isEqualTo((Selection) other);
+ }
+
+ private boolean isEqualTo(Selection other) {
+ return mSelection.equals(other.mSelection)
+ && mProvisionalSelection.equals(other.mProvisionalSelection);
+ }
+}
diff --git a/androidx/recyclerview/selection/SelectionHelper.java b/androidx/recyclerview/selection/SelectionHelper.java
new file mode 100644
index 00000000..276f9034
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionHelper.java
@@ -0,0 +1,251 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.Set;
+
+/**
+ * SelectionManager provides support for managing selection within a RecyclerView instance.
+ *
+ * @see DefaultSelectionHelper for details on instantiation.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class SelectionHelper<K> {
+
+ /**
+ * This value is included in the payload when SelectionHelper implementations
+ * notify RecyclerView of changes. Clients can look for this in
+ * {@code onBindViewHolder} to know if the bind event is occurring in response
+ * to a selection state change.
+ */
+ public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+
+ /**
+ * Adds {@code observer} to be notified when changes to selection occur.
+ * This method allows observers to closely track changes to selection
+ * avoiding the need to poll selection at performance critical points.
+ */
+ public abstract void addObserver(SelectionObserver observer);
+
+ /** @return true if has a selection */
+ public abstract boolean hasSelection();
+
+ /**
+ * Returns a Selection object that provides a live view on the current selection.
+ *
+ * @return The current selection.
+ * @see #copySelection(Selection) on how to get a snapshot
+ * of the selection that will not reflect future changes
+ * to selection.
+ */
+ public abstract Selection getSelection();
+
+ /**
+ * Updates {@code dest} to reflect the current selection.
+ */
+ public abstract void copySelection(Selection dest);
+
+ /**
+ * @return true if the item specified by its id is selected. Shorthand for
+ * {@code getSelection().contains(K)}.
+ */
+ public abstract boolean isSelected(@Nullable K key);
+
+ /**
+ * Restores the selected state of specified items. Used in cases such as restore the selection
+ * after rotation etc. Provisional selection, being provisional 'n all, isn't restored.
+ *
+ * <p>This affords clients the ability to restore selection from selection saved
+ * in Activity state. See {@link android.app.Activity#onCreate(Bundle)}.
+ *
+ * @param savedSelection selection being restored.
+ */
+ public abstract void restoreSelection(Selection savedSelection);
+
+ abstract void onDataSetChanged();
+
+ /**
+ * Clears both primary selection and provisional selection.
+ *
+ * @return true if anything changed.
+ */
+ public abstract boolean clear();
+
+ /**
+ * Clears the selection and notifies (if something changes).
+ */
+ public abstract void clearSelection();
+
+ /**
+ * Sets the selected state of the specified items. Note that the callback will NOT
+ * be consulted to see if an item can be selected.
+ */
+ public abstract boolean setItemsSelected(Iterable<K> keys, boolean selected);
+
+ /**
+ * Attempts to select an item.
+ *
+ * @return true if the item was selected. False if the item was not selected, or was
+ * was already selected prior to the method being called.
+ */
+ public abstract boolean select(K key);
+
+ /**
+ * Attempts to deselect an item.
+ *
+ * @return true if the item was deselected. False if the item was not deselected, or was
+ * was already deselected prior to the method being called.
+ */
+ public abstract boolean deselect(K key);
+
+ /**
+ * Selects the item at position and establishes the "anchor" for a range selection,
+ * replacing any existing range anchor.
+ *
+ * @param position The anchor position for the selection range.
+ */
+ public abstract void startRange(int position);
+
+ /**
+ * Sets the end point for the active range selection.
+ *
+ * <p>This function should only be called when a range selection is active
+ * (see {@link #isRangeActive()}. Items in the range [anchor, end] will be
+ * selected.
+ *
+ * @param position The new end position for the selection range.
+ * @throws IllegalStateException if a range selection is not active. Range selection
+ * must have been started by a call to {@link #startRange(int)}.
+ */
+ public abstract void extendRange(int position);
+
+ /**
+ * Stops an in-progress range selection. All selection done with
+ * {@link #extendProvisionalRange(int)} will be lost if
+ * {@link Selection#mergeProvisionalSelection()} is not called beforehand.
+ */
+ public abstract void endRange();
+
+ /**
+ * @return Whether or not there is a current range selection active.
+ */
+ public abstract boolean isRangeActive();
+
+ /**
+ * Establishes the "anchor" at which a selection range begins. This "anchor" is consulted
+ * when determining how to extend, and modify selection ranges. Calling this when a
+ * range selection is active will reset the range selection.
+ *
+ * @param position the anchor position. Must already be selected.
+ */
+ protected abstract void anchorRange(int position);
+
+ /**
+ * @param position
+ */
+ // TODO: This is smelly. Maybe this type of logic needs to move into range selection,
+ // then selection manager can have a startProvisionalRange and startRange. Or
+ // maybe ranges always start life as provisional.
+ protected abstract void extendProvisionalRange(int position);
+
+ /**
+ * Sets the provisional selection, replacing any existing selection.
+ * @param newSelection
+ */
+ public abstract void setProvisionalSelection(Set<K> newSelection);
+
+ /** Clears any existing provisional selection */
+ public abstract void clearProvisionalSelection();
+
+ /**
+ * Converts the provisional selection into primary selection, then clears
+ * provisional selection.
+ */
+ public abstract void mergeProvisionalSelection();
+
+ /**
+ * Observer interface providing access to information about Selection state changes.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public abstract static class SelectionObserver<K> {
+
+ /**
+ * Called when state of an item has been changed.
+ */
+ public void onItemStateChanged(K key, boolean selected) {
+ }
+
+ /**
+ * Called when the underlying data set has change. After this method is called
+ * the selection manager will attempt traverse the existing selection,
+ * calling {@link #onItemStateChanged(K, boolean)} for each selected item,
+ * and deselecting any items that cannot be selected given the updated dataset.
+ */
+ public void onSelectionReset() {
+ }
+
+ /**
+ * Called immediately after completion of any set of changes, excluding
+ * those resulting in calls to {@link #onSelectionReset()} and
+ * {@link #onSelectionRestored()}.
+ */
+ public void onSelectionChanged() {
+ }
+
+ /**
+ * Called immediately after selection is restored.
+ * {@link #onItemStateChanged(K, boolean)} will not be called
+ * for individual items in the selection.
+ */
+ public void onSelectionRestored() {
+ }
+ }
+
+ /**
+ * Implement SelectionPredicate to control when items can be selected or unselected.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public abstract static class SelectionPredicate<K> {
+
+ /** @return true if the item at {@code id} can be set to {@code nextState}. */
+ public abstract boolean canSetStateForKey(K key, boolean nextState);
+
+ /** @return true if the item at {@code id} can be set to {@code nextState}. */
+ public abstract boolean canSetStateAtPosition(int position, boolean nextState);
+
+ /** @return true if more than a single item can be selected. */
+ public abstract boolean canSelectMultiple();
+ }
+}
diff --git a/androidx/recyclerview/selection/SelectionHelperBuilder.java b/androidx/recyclerview/selection/SelectionHelperBuilder.java
new file mode 100644
index 00000000..abdefaf5
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionHelperBuilder.java
@@ -0,0 +1,342 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Builder class for assembling selection support. Example usage:
+ *
+ * <p><pre>SelectionHelperBuilder selSupport = new SelectionHelperBuilder(
+ mRecView, new DemoStableIdProvider(mAdapter), detailsLookup);
+
+ // By default multi-select is supported.
+ SelectionHelper selHelper = selSupport
+ .build();
+
+ // This configuration support single selection for any element.
+ SelectionHelper selHelper = selSupport
+ .withSelectionPredicate(SelectionHelper.SelectionPredicate.SINGLE_ANYTHING)
+ .build();
+
+ // Lazily bind SelectionHelper. Allows us to defer initialization of the
+ // SelectionHelper dependency until after the adapter is created.
+ mAdapter.bindSelectionHelper(selHelper);
+
+ * </pre></p>
+ *
+ * @see SelectionStorage for important deatils on retaining selection across Activity
+ * lifecycle events.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionHelperBuilder<K> {
+
+ private final RecyclerView mRecView;
+ private final RecyclerView.Adapter<?> mAdapter;
+ private final Context mContext;
+
+ // Content lock provides a mechanism to block content reload while selection
+ // activities are active. If using a loader to load content, route
+ // the call through the content lock using ContentLock#runWhenUnlocked.
+ // This is especially useful when listening on content change notification.
+ private final ContentLock mLock = new ContentLock();
+
+ private SelectionPredicate<K> mSelectionPredicate = SelectionPredicates.selectAnything();
+ private ItemKeyProvider<K> mKeyProvider;
+ private ItemDetailsLookup<K> mDetailsLookup;
+
+ private ActivationCallbacks<K> mActivationCallbacks = ActivationCallbacks.dummy();
+ private FocusCallbacks<K> mFocusCallbacks = FocusCallbacks.dummy();
+ private TouchCallbacks mTouchCallbacks = TouchCallbacks.DUMMY;
+ private MouseCallbacks mMouseCallbacks = MouseCallbacks.DUMMY;
+
+ private BandPredicate mBandPredicate;
+ private int mBandOverlayId = R.drawable.selection_band_overlay;
+
+ private int[] mGestureToolTypes = new int[] {
+ MotionEvent.TOOL_TYPE_FINGER,
+ MotionEvent.TOOL_TYPE_UNKNOWN
+ };
+
+ private int[] mBandToolTypes = new int[] {
+ MotionEvent.TOOL_TYPE_MOUSE,
+ MotionEvent.TOOL_TYPE_STYLUS
+ };
+
+ public SelectionHelperBuilder(
+ RecyclerView recView,
+ ItemKeyProvider<K> keyProvider,
+ ItemDetailsLookup<K> detailsLookup) {
+
+ checkArgument(recView != null);
+
+ mRecView = recView;
+ mContext = recView.getContext();
+ mAdapter = recView.getAdapter();
+
+ checkArgument(mAdapter != null);
+ checkArgument(keyProvider != null);
+ checkArgument(detailsLookup != null);
+
+ mDetailsLookup = detailsLookup;
+ mKeyProvider = keyProvider;
+
+ mBandPredicate = BandPredicate.notDraggable(mRecView, detailsLookup);
+ }
+
+ /**
+ * Install seleciton predicate.
+ * @param predicate
+ * @return
+ */
+ public SelectionHelperBuilder<K> withSelectionPredicate(SelectionPredicate<K> predicate) {
+ checkArgument(predicate != null);
+ mSelectionPredicate = predicate;
+ return this;
+ }
+
+ /**
+ * Add activation callbacks to respond to taps/enter/double-click on items.
+ *
+ * @param callbacks
+ * @return
+ */
+ public SelectionHelperBuilder<K> withActivationCallbacks(ActivationCallbacks<K> callbacks) {
+ checkArgument(callbacks != null);
+ mActivationCallbacks = callbacks;
+ return this;
+ }
+
+ /**
+ * Add focus callbacks to interfact with selection related focus changes.
+ * @param callbacks
+ * @return
+ */
+ public SelectionHelperBuilder<K> withFocusCallbacks(FocusCallbacks<K> callbacks) {
+ checkArgument(callbacks != null);
+ mFocusCallbacks = callbacks;
+ return this;
+ }
+
+ /**
+ * Configures mouse callbacks, replacing defaults.
+ *
+ * @param callbacks
+ * @return
+ */
+ public SelectionHelperBuilder<K> withMouseCallbacks(MouseCallbacks callbacks) {
+ checkArgument(callbacks != null);
+
+ mMouseCallbacks = callbacks;
+ return this;
+ }
+
+ /**
+ * Replaces default touch callbacks.
+ *
+ * @param callbacks
+ * @return
+ */
+ public SelectionHelperBuilder<K> withTouchCallbacks(TouchCallbacks callbacks) {
+ checkArgument(callbacks != null);
+
+ mTouchCallbacks = callbacks;
+ return this;
+ }
+
+ /**
+ * Replaces default gesture tooltypes.
+ * @param toolTypes
+ * @return
+ */
+ public SelectionHelperBuilder<K> withTouchTooltypes(int... toolTypes) {
+ mGestureToolTypes = toolTypes;
+ return this;
+ }
+
+ /**
+ * Replaces default band overlay.
+ *
+ * @param bandOverlayId
+ * @return
+ */
+ public SelectionHelperBuilder<K> withBandOverlay(@DrawableRes int bandOverlayId) {
+ mBandOverlayId = bandOverlayId;
+ return this;
+ }
+
+ /**
+ * Replaces default band predicate.
+ * @param bandPredicate
+ * @return
+ */
+ public SelectionHelperBuilder<K> withBandPredicate(BandPredicate bandPredicate) {
+
+ checkArgument(bandPredicate != null);
+
+ mBandPredicate = bandPredicate;
+ return this;
+ }
+
+ /**
+ * Replaces default band tools types.
+ * @param toolTypes
+ * @return
+ */
+ public SelectionHelperBuilder<K> withBandTooltypes(int... toolTypes) {
+ mBandToolTypes = toolTypes;
+ return this;
+ }
+
+ /**
+ * Prepares selection support and returns the corresponding SelectionHelper.
+ *
+ * @return
+ */
+ public SelectionHelper<K> build() {
+
+ SelectionHelper<K> selectionHelper =
+ new DefaultSelectionHelper<>(mKeyProvider, mSelectionPredicate);
+
+ // Event glue between RecyclerView and SelectionHelper keeps the classes separate
+ // so that a SelectionHelper can be shared across RecyclerView instances that
+ // represent the same data in different ways.
+ EventBridge.install(mAdapter, selectionHelper, mKeyProvider);
+
+ AutoScroller scroller = new ViewAutoScroller(ViewAutoScroller.createScrollHost(mRecView));
+
+ // Setup basic input handling, with the touch handler as the default consumer
+ // of events. If mouse handling is configured as well, the mouse input
+ // related handlers will intercept mouse input events.
+
+ // GestureRouter is responsible for routing GestureDetector events
+ // to tool-type specific handlers.
+ GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>();
+ GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter);
+
+ // TouchEventRouter takes its name from RecyclerView#OnItemTouchListener.
+ // Despite "Touch" being in the name, it receives events for all types of tools.
+ // This class is responsible for routing events to tool-type specific handlers,
+ // and if not handled by a handler, on to a GestureDetector for analysis.
+ TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector);
+
+ // GestureSelectionHelper provides logic that interprets a combination
+ // of motions and gestures in order to provide gesture driven selection support
+ // when used in conjunction with RecyclerView.
+ final GestureSelectionHelper gestureHelper =
+ GestureSelectionHelper.create(selectionHelper, mRecView, scroller, mLock);
+
+ // Finally hook the framework up to listening to recycle view events.
+ mRecView.addOnItemTouchListener(eventRouter);
+
+ // But before you move on, there's more work to do. Event plumbing has been
+ // installed, but we haven't registered any of our helpers or callbacks.
+ // Helpers contain predefined logic converting events into selection related events.
+ // Callbacks provide authors the ability to reponspond to other types of
+ // events (like "active" a tapped item). This is broken up into two main
+ // suites, one for "touch" and one for "mouse", though both can and should (usually)
+ // be configued to handle other types of input (to satisfy user expectation).);
+
+ // Provides high level glue for binding touch events
+ // and gestures to selection framework.
+ TouchInputHandler<K> touchHandler = new TouchInputHandler<K>(
+ selectionHelper,
+ mKeyProvider,
+ mDetailsLookup,
+ mSelectionPredicate,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectionPredicate.canSelectMultiple()) {
+ gestureHelper.start();
+ }
+ }
+ },
+ mTouchCallbacks,
+ mActivationCallbacks,
+ mFocusCallbacks,
+ new Runnable() {
+ @Override
+ public void run() {
+ mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
+ });
+
+ for (int toolType : mGestureToolTypes) {
+ gestureRouter.register(toolType, touchHandler);
+ eventRouter.register(toolType, gestureHelper);
+ }
+
+ // Provides high level glue for binding mouse/stylus events and gestures
+ // to selection framework.
+ MouseInputHandler<K> mouseHandler = new MouseInputHandler<>(
+ selectionHelper,
+ mKeyProvider,
+ mDetailsLookup,
+ mMouseCallbacks,
+ mActivationCallbacks,
+ mFocusCallbacks);
+
+ for (int toolType : mBandToolTypes) {
+ gestureRouter.register(toolType, mouseHandler);
+ }
+
+ // Band selection not supported in single select mode, or when key access
+ // is limited to anything less than the entire corpus.
+ // TODO: Since we cach grid info from laid out items, we could cache key too.
+ // Then we couldn't have to limit to CORPUS access.
+ if (mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED)
+ && mSelectionPredicate.canSelectMultiple()) {
+ // BandSelectionHelper provides support for band selection on-top of a RecyclerView
+ // instance. Given the recycling nature of RecyclerView BandSelectionController
+ // necessarily models and caches list/grid information as the user's pointer
+ // interacts with the item in the RecyclerView. Selectable items that intersect
+ // with the band, both on and off screen, are selected.
+ BandSelectionHelper bandHelper = BandSelectionHelper.create(
+ mRecView,
+ scroller,
+ mBandOverlayId,
+ mKeyProvider,
+ selectionHelper,
+ mSelectionPredicate,
+ mBandPredicate,
+ mFocusCallbacks,
+ mLock);
+
+ for (int toolType : mBandToolTypes) {
+ eventRouter.register(toolType, bandHelper);
+ }
+ }
+
+ return selectionHelper;
+ }
+}
diff --git a/androidx/recyclerview/selection/SelectionPredicates.java b/androidx/recyclerview/selection/SelectionPredicates.java
new file mode 100644
index 00000000..26253d98
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionPredicates.java
@@ -0,0 +1,84 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Utility class for creating SelectionPredicate instances.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionPredicates {
+
+ private SelectionPredicates() {}
+
+ /**
+ * Returns a selection predicate that allows multiples items to be selected, without
+ * any restrictions on which items can be selected.
+ * @param <K>
+ * @return
+ */
+ public static <K> SelectionPredicate<K> selectAnything() {
+ return new SelectionPredicate<K>() {
+ @Override
+ public boolean canSetStateForKey(K key, boolean nextState) {
+ return true;
+ }
+
+ @Override
+ public boolean canSetStateAtPosition(int position, boolean nextState) {
+ return true;
+ }
+
+ @Override
+ public boolean canSelectMultiple() {
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a selection predicate that allows a single item to be selected, without
+ * any restrictions on which item can be selected.
+ * @param <K>
+ * @return
+ */
+ public static <K> SelectionPredicate<K> selectSingleAnything() {
+ return new SelectionPredicate<K>() {
+ @Override
+ public boolean canSetStateForKey(K key, boolean nextState) {
+ return true;
+ }
+
+ @Override
+ public boolean canSetStateAtPosition(int position, boolean nextState) {
+ return true;
+ }
+
+ @Override
+ public boolean canSelectMultiple() {
+ return false;
+ }
+ };
+ }
+}
diff --git a/androidx/recyclerview/selection/SelectionStorage.java b/androidx/recyclerview/selection/SelectionStorage.java
new file mode 100644
index 00000000..81db30fd
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionStorage.java
@@ -0,0 +1,181 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Helper class binding SelectionHelper and Activity lifecycle events facilitating
+ * persistence of selection across activity lifecycle events.
+ *
+ * <p>Usage:<br><pre>
+ void onCreate() {
+ mLifecycleHelper = new SelectionStorage<>(SelectionStorage.TYPE_STRING, mSelectionHelper);
+ if (savedInstanceState != null) {
+ mSelectionStorage.onRestoreInstanceState(savedInstanceState);
+ }
+ }
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mSelectionStorage.onSaveInstanceState(outState);
+ }
+ </pre>
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionStorage<K> {
+
+ @VisibleForTesting
+ static final String EXTRA_SAVED_SELECTION_TYPE = "androidx.recyclerview.selection.type";
+
+ @VisibleForTesting
+ static final String EXTRA_SAVED_SELECTION_ENTRIES = "androidx.recyclerview.selection.entries";
+
+ public static final int TYPE_STRING = 0;
+ public static final int TYPE_LONG = 1;
+ @IntDef({
+ TYPE_STRING,
+ TYPE_LONG
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface KeyType {}
+
+ private final @KeyType int mKeyType;
+ private final SelectionHelper<K> mHelper;
+
+ /**
+ * Creates a new lifecycle helper. {@code keyType}.
+ *
+ * @param keyType
+ * @param helper
+ */
+ public SelectionStorage(@KeyType int keyType, SelectionHelper<K> helper) {
+ checkArgument(
+ keyType == TYPE_STRING || keyType == TYPE_LONG,
+ "Only String and Integer presistence are supported by default.");
+ checkArgument(helper != null);
+
+ mKeyType = keyType;
+ mHelper = helper;
+ }
+
+ /**
+ * Preserves selection, if any.
+ *
+ * @param state
+ */
+ @SuppressWarnings("unchecked")
+ public void onSaveInstanceState(Bundle state) {
+ MutableSelection<K> sel = new MutableSelection<>();
+ mHelper.copySelection(sel);
+
+ state.putInt(EXTRA_SAVED_SELECTION_TYPE, mKeyType);
+ switch (mKeyType) {
+ case TYPE_STRING:
+ writeStringSelection(state, ((Selection<String>) sel).mSelection);
+ break;
+ case TYPE_LONG:
+ writeLongSelection(state, ((Selection<Long>) sel).mSelection);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported key type: " + mKeyType);
+ }
+ }
+
+ /**
+ * Restores selection from previously saved state.
+ *
+ * @param state
+ */
+ public void onRestoreInstanceState(@Nullable Bundle state) {
+ if (state == null) {
+ return;
+ }
+
+ int keyType = state.getInt(EXTRA_SAVED_SELECTION_TYPE, -1);
+ switch(keyType) {
+ case TYPE_STRING:
+ Selection<String> stringSel = readStringSelection(state);
+ if (stringSel != null && !stringSel.isEmpty()) {
+ mHelper.restoreSelection(stringSel);
+ }
+ break;
+ case TYPE_LONG:
+ Selection<Long> longSel = readLongSelection(state);
+ if (longSel != null && !longSel.isEmpty()) {
+ mHelper.restoreSelection(longSel);
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported selection key type.");
+ }
+ }
+
+ private @Nullable Selection<String> readStringSelection(@Nullable Bundle state) {
+ @Nullable ArrayList<String> stored =
+ state.getStringArrayList(EXTRA_SAVED_SELECTION_ENTRIES);
+ if (stored == null) {
+ return null;
+ }
+
+ Selection<String> selection = new Selection<>();
+ selection.mSelection.addAll(stored);
+ return selection;
+ }
+
+ private @Nullable Selection<Long> readLongSelection(@Nullable Bundle state) {
+ @Nullable long[] stored = state.getLongArray(EXTRA_SAVED_SELECTION_ENTRIES);
+ if (stored == null) {
+ return null;
+ }
+
+ Selection<Long> selection = new Selection<>();
+ for (long key : stored) {
+ selection.mSelection.add(key);
+ }
+ return selection;
+ }
+
+ private void writeStringSelection(Bundle state, Set<String> selected) {
+ ArrayList<String> value = new ArrayList<>(selected.size());
+ value.addAll(selected);
+ state.putStringArrayList(EXTRA_SAVED_SELECTION_ENTRIES, value);
+ }
+
+ private void writeLongSelection(Bundle state, Set<Long> selected) {
+ long[] value = new long[selected.size()];
+ int i = 0;
+ for (Long key : selected) {
+ value[i++] = key;
+ }
+ state.putLongArray(EXTRA_SAVED_SELECTION_ENTRIES, value);
+ }
+}
diff --git a/androidx/recyclerview/selection/Shared.java b/androidx/recyclerview/selection/Shared.java
new file mode 100644
index 00000000..3b791205
--- /dev/null
+++ b/androidx/recyclerview/selection/Shared.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.recyclerview.selection;
+
+/**
+ * Shared constants used in this package.
+ */
+final class Shared {
+
+ static final boolean DEBUG = false;
+ static final boolean VERBOSE = true;
+
+ private Shared() {}
+}
diff --git a/androidx/recyclerview/selection/StableIdKeyProvider.java b/androidx/recyclerview/selection/StableIdKeyProvider.java
new file mode 100644
index 00000000..3dc78ca3
--- /dev/null
+++ b/androidx/recyclerview/selection/StableIdKeyProvider.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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnChildAttachStateChangeListener;
+import android.util.SparseArray;
+import android.view.View;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ItemKeyProvider that provides stable ids by way of cached RecyclerView.Adapter stable ids.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
+
+ private final SparseArray<Long> mPositionToKey = new SparseArray<>();
+ private final Map<Long, Integer> mKeyToPosition = new HashMap<Long, Integer>();
+ private final RecyclerView mRecView;
+
+ public StableIdKeyProvider(RecyclerView recView) {
+
+ // Since this provide is based on stable ids based on whats laid out in the window
+ // we can only satisfy "window" scope key access.
+ super(SCOPE_CACHED);
+
+ mRecView = recView;
+
+ mRecView.addOnChildAttachStateChangeListener(
+ new OnChildAttachStateChangeListener() {
+ @Override
+ public void onChildViewAttachedToWindow(View view) {
+ onAttached(view);
+ }
+
+ @Override
+ public void onChildViewDetachedFromWindow(View view) {
+ onDetached(view);
+ }
+ }
+ );
+
+ }
+
+ private void onAttached(View view) {
+ RecyclerView.ViewHolder holder = mRecView.findContainingViewHolder(view);
+ int position = holder.getAdapterPosition();
+ long id = holder.getItemId();
+ if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
+ mPositionToKey.put(position, id);
+ mKeyToPosition.put(id, position);
+ }
+ }
+
+ private void onDetached(View view) {
+ RecyclerView.ViewHolder holder = mRecView.findContainingViewHolder(view);
+ int position = holder.getAdapterPosition();
+ long id = holder.getItemId();
+ if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
+ mPositionToKey.delete(position);
+ mKeyToPosition.remove(id);
+ }
+ }
+
+ @Override
+ public @Nullable Long getKey(int position) {
+ return mPositionToKey.get(position, null);
+ }
+
+ @Override
+ public int getPosition(Long key) {
+ if (mKeyToPosition.containsKey(key)) {
+ return mKeyToPosition.get(key);
+ }
+ return RecyclerView.NO_POSITION;
+ }
+}
diff --git a/androidx/recyclerview/selection/ToolHandlerRegistry.java b/androidx/recyclerview/selection/ToolHandlerRegistry.java
new file mode 100644
index 00000000..c7355295
--- /dev/null
+++ b/androidx/recyclerview/selection/ToolHandlerRegistry.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.support.annotation.Nullable;
+import android.view.MotionEvent;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Registry for tool specific event handler.
+ */
+final class ToolHandlerRegistry<T> {
+
+ // Currently there are four known input types. ERASER is the last one, so has the
+ // highest value. UNKNOWN is zero, so we add one. This allows delegates to be
+ // registered by type, and avoid the auto-boxing that would be necessary were we
+ // to store delegates in a Map<Integer, Delegate>.
+ private static final int sNumInputTypes = MotionEvent.TOOL_TYPE_ERASER + 1;
+
+ private final List<T> mHandlers = Arrays.asList(null, null, null, null, null);
+ private final T mDefault;
+
+ ToolHandlerRegistry(T defaultDelegate) {
+ checkArgument(defaultDelegate != null);
+ mDefault = defaultDelegate;
+
+ // Initialize all values to null.
+ for (int i = 0; i < sNumInputTypes; i++) {
+ mHandlers.set(i, null);
+ }
+ }
+
+ /**
+ * @param toolType
+ * @param delegate the delegate, or null to unregister.
+ * @throws IllegalStateException if an tooltype handler is already registered.
+ */
+ void set(int toolType, @Nullable T delegate) {
+ checkArgument(toolType >= 0 && toolType <= MotionEvent.TOOL_TYPE_ERASER);
+ checkState(mHandlers.get(toolType) == null);
+
+ mHandlers.set(toolType, delegate);
+ }
+
+ T get(MotionEvent e) {
+ T d = mHandlers.get(e.getToolType(0));
+ return d != null ? d : mDefault;
+ }
+}
diff --git a/androidx/recyclerview/selection/TouchCallbacks.java b/androidx/recyclerview/selection/TouchCallbacks.java
new file mode 100644
index 00000000..59053927
--- /dev/null
+++ b/androidx/recyclerview/selection/TouchCallbacks.java
@@ -0,0 +1,55 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class TouchCallbacks {
+
+ static final TouchCallbacks DUMMY = new TouchCallbacks() {
+ @Override
+ public boolean onDragInitiated(MotionEvent e) {
+ return false;
+ }
+ };
+
+ /**
+ * Called when a drag is initiated. Touch input handler only considers
+ * a drag to be initiated on long press on an existing selection,
+ * as normal touch and drag events are strongly associated with scrolling of the view.
+ *
+ * <p>Drag will only be initiated when the item under the event is already selected.
+ *
+ * <p>The RecyclerView item at the coordinates of the MotionEvent is not supplied as a parameter
+ * to this method as there may be multiple items selected. Clients can obtain the current
+ * list of selected items from {@link SelectionHelper#copySelection(Selection)}.
+ *
+ * @param e the event associated with the drag.
+ * @return true if the event was handled.
+ */
+ public abstract boolean onDragInitiated(MotionEvent e);
+}
diff --git a/androidx/recyclerview/selection/TouchEventRouter.java b/androidx/recyclerview/selection/TouchEventRouter.java
new file mode 100644
index 00000000..fbbca238
--- /dev/null
+++ b/androidx/recyclerview/selection/TouchEventRouter.java
@@ -0,0 +1,105 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+/**
+ * A class responsible for routing MotionEvents to tool-type specific handlers,
+ * and if not handled by a handler, on to a {@link GestureDetector} for further
+ * processing.
+ *
+ * <p>TouchEventRouter takes its name from
+ * {@link RecyclerView#addOnItemTouchListener(OnItemTouchListener)}. Despite "Touch"
+ * being in the name, it receives MotionEvents for all types of tools.
+ */
+final class TouchEventRouter implements OnItemTouchListener {
+
+ private static final String TAG = "TouchEventRouter";
+
+ private final GestureDetector mDetector;
+ private final ToolHandlerRegistry<OnItemTouchListener> mDelegates;
+
+ TouchEventRouter(GestureDetector detector, OnItemTouchListener defaultDelegate) {
+ checkArgument(detector != null);
+ checkArgument(defaultDelegate != null);
+
+ mDetector = detector;
+ mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
+ }
+
+ TouchEventRouter(GestureDetector detector) {
+ this(
+ detector,
+ // Supply a fallback listener does nothing...because the caller
+ // didn't supply a fallback.
+ new OnItemTouchListener() {
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ }
+ });
+ }
+
+ /**
+ * @param toolType See MotionEvent for details on available types.
+ * @param delegate An {@link OnItemTouchListener} to receive events
+ * of {@code toolType}.
+ */
+ void register(int toolType, OnItemTouchListener delegate) {
+ checkArgument(delegate != null);
+ mDelegates.set(toolType, delegate);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+ boolean handled = mDelegates.get(e).onInterceptTouchEvent(rv, e);
+
+ // Forward all events to UserInputHandler.
+ // This is necessary since UserInputHandler needs to always see the first DOWN event. Or
+ // else all future UP events will be tossed.
+ handled |= mDetector.onTouchEvent(e);
+
+ return handled;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+ mDelegates.get(e).onTouchEvent(rv, e);
+
+ // Note: even though this event is being handled as part of gestures such as drag and band,
+ // continue forwarding to the GestureDetector. The detector needs to see the entire cluster
+ // of events in order to properly interpret other gestures, such as long press.
+ mDetector.onTouchEvent(e);
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+}
diff --git a/androidx/recyclerview/selection/TouchInputHandler.java b/androidx/recyclerview/selection/TouchInputHandler.java
new file mode 100644
index 00000000..e07aeb19
--- /dev/null
+++ b/androidx/recyclerview/selection/TouchInputHandler.java
@@ -0,0 +1,149 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * A MotionInputHandler that provides the high-level glue for touch driven selection. This class
+ * works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper} to
+ * provide robust user drive selection support.
+ */
+final class TouchInputHandler<K> extends MotionInputHandler<K> {
+
+ private static final String TAG = "TouchInputDelegate";
+ private static final boolean DEBUG = false;
+
+ private final ItemDetailsLookup<K> mDetailsLookup;
+ private final SelectionPredicate<K> mSelectionPredicate;
+ private final ActivationCallbacks<K> mActivationCallbacks;
+ private final TouchCallbacks mTouchCallbacks;
+ private final Runnable mGestureStarter;
+ private final Runnable mHapticPerformer;
+
+ TouchInputHandler(
+ SelectionHelper<K> selectionHelper,
+ ItemKeyProvider<K> keyProvider,
+ ItemDetailsLookup<K> detailsLookup,
+ SelectionPredicate<K> selectionPredicate,
+ Runnable gestureStarter,
+ TouchCallbacks touchCallbacks,
+ ActivationCallbacks<K> activationCallbacks,
+ FocusCallbacks<K> focusCallbacks,
+ Runnable hapticPerformer) {
+
+ super(selectionHelper, keyProvider, focusCallbacks);
+
+ checkArgument(detailsLookup != null);
+ checkArgument(selectionPredicate != null);
+ checkArgument(gestureStarter != null);
+ checkArgument(activationCallbacks != null);
+ checkArgument(touchCallbacks != null);
+ checkArgument(hapticPerformer != null);
+
+ mDetailsLookup = detailsLookup;
+ mSelectionPredicate = selectionPredicate;
+ mGestureStarter = gestureStarter;
+ mActivationCallbacks = activationCallbacks;
+ mTouchCallbacks = touchCallbacks;
+ mHapticPerformer = hapticPerformer;
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+ if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
+ mSelectionHelper.clearSelection();
+ return false;
+ }
+
+ ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+ // Should really not be null at this point, but...
+ if (item == null) {
+ return false;
+ }
+
+ if (mSelectionHelper.hasSelection()) {
+ if (isRangeExtension(e)) {
+ extendSelectionRange(item);
+ } else if (mSelectionHelper.isSelected(item.getSelectionKey())) {
+ mSelectionHelper.deselect(item.getSelectionKey());
+ } else {
+ selectItem(item);
+ }
+
+ return true;
+ }
+
+ // Touch events select if they occur in the selection hotspot,
+ // otherwise they activate.
+ return item.inSelectionHotspot(e)
+ ? selectItem(item)
+ : mActivationCallbacks.onItemActivated(item, e);
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+ if (DEBUG) Log.d(TAG, "Ignoring LongPress on non-model-backed item.");
+ return;
+ }
+
+ ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+ // Should really not be null at this point, but...
+ if (item == null) {
+ return;
+ }
+
+ boolean handled = false;
+
+ if (isRangeExtension(e)) {
+ extendSelectionRange(item);
+ handled = true;
+ } else {
+ if (!mSelectionHelper.isSelected(item.getSelectionKey())
+ && mSelectionPredicate.canSetStateForKey(item.getSelectionKey(), true)) {
+ // If we cannot select it, we didn't apply anchoring - therefore should not
+ // start gesture selection
+ if (selectItem(item)) {
+ // And finally if the item was selected && we can select multiple
+ // we kick off gesture selection.
+ if (mSelectionPredicate.canSelectMultiple()) {
+ mGestureStarter.run();
+ }
+ handled = true;
+ }
+ } else {
+ // We only initiate drag and drop on long press for touch to allow regular
+ // touch-based scrolling
+ mTouchCallbacks.onDragInitiated(e);
+ handled = true;
+ }
+ }
+
+ if (handled) {
+ mHapticPerformer.run();
+ }
+ }
+}
diff --git a/androidx/recyclerview/selection/ViewAutoScroller.java b/androidx/recyclerview/selection/ViewAutoScroller.java
new file mode 100644
index 00000000..d13b0f24
--- /dev/null
+++ b/androidx/recyclerview/selection/ViewAutoScroller.java
@@ -0,0 +1,271 @@
+/*
+ * 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.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.graphics.Point;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+/**
+ * Provides auto-scrolling upon request when user's interaction with the application
+ * introduces a natural intent to scroll. Used by BandSelectionHelper and GestureSelectionHelper,
+ * to provide auto scrolling when user is performing selection operations.
+ */
+final class ViewAutoScroller extends AutoScroller {
+
+ private static final String TAG = "ViewAutoScroller";
+
+ // ratio used to calculate the top/bottom hotspot region; used with view height
+ private static final float DEFAULT_SCROLL_THRESHOLD_RATIO = 0.125f;
+ private static final int MAX_SCROLL_STEP = 70;
+
+ private final float mScrollThresholdRatio;
+
+ private final ScrollHost mHost;
+ private final Runnable mRunner;
+
+ private @Nullable Point mOrigin;
+ private @Nullable Point mLastLocation;
+ private boolean mPassedInitialMotionThreshold;
+
+ ViewAutoScroller(ScrollHost scrollHost) {
+ this(scrollHost, DEFAULT_SCROLL_THRESHOLD_RATIO);
+ }
+
+ @VisibleForTesting
+ ViewAutoScroller(ScrollHost scrollHost, float scrollThresholdRatio) {
+
+ checkArgument(scrollHost != null);
+
+ mHost = scrollHost;
+ mScrollThresholdRatio = scrollThresholdRatio;
+
+ mRunner = new Runnable() {
+ @Override
+ public void run() {
+ runScroll();
+ }
+ };
+ }
+
+ @Override
+ protected void reset() {
+ mHost.removeCallback(mRunner);
+ mOrigin = null;
+ mLastLocation = null;
+ mPassedInitialMotionThreshold = false;
+ }
+
+ @Override
+ protected void scroll(Point location) {
+ mLastLocation = location;
+
+ // See #aboveMotionThreshold for details on how we track initial location.
+ if (mOrigin == null) {
+ mOrigin = location;
+ if (VERBOSE) Log.v(TAG, "Origin @ " + mOrigin);
+ }
+
+ if (VERBOSE) Log.v(TAG, "Current location @ " + mLastLocation);
+
+ mHost.runAtNextFrame(mRunner);
+ }
+
+ /**
+ * Attempts to smooth-scroll the view at the given UI frame. Application should be
+ * responsible to do any clean up (such as unsubscribing scrollListeners) after the run has
+ * finished, and re-run this method on the next UI frame if applicable.
+ */
+ private void runScroll() {
+ if (DEBUG) checkState(mLastLocation != null);
+
+ if (VERBOSE) Log.v(TAG, "Running in background using event location @ " + mLastLocation);
+
+ // Compute the number of pixels the pointer's y-coordinate is past the view.
+ // Negative values mean the pointer is at or before the top of the view, and
+ // positive values mean that the pointer is at or after the bottom of the view. Note
+ // that top/bottom threshold is added here so that the view still scrolls when the
+ // pointer are in these buffer pixels.
+ int pixelsPastView = 0;
+
+ final int verticalThreshold = (int) (mHost.getViewHeight()
+ * mScrollThresholdRatio);
+
+ if (mLastLocation.y <= verticalThreshold) {
+ pixelsPastView = mLastLocation.y - verticalThreshold;
+ } else if (mLastLocation.y >= mHost.getViewHeight()
+ - verticalThreshold) {
+ pixelsPastView = mLastLocation.y - mHost.getViewHeight()
+ + verticalThreshold;
+ }
+
+ if (pixelsPastView == 0) {
+ // If the operation that started the scrolling is no longer inactive, or if it is active
+ // but not at the edge of the view, no scrolling is necessary.
+ return;
+ }
+
+ // We're in one of the endzones. Now determine if there's enough of a difference
+ // from the orgin to take any action. Basically if a user has somehow initiated
+ // selection, but is hovering at or near their initial contact point, we don't
+ // scroll. This avoids a situation where the user initiates selection in an "endzone"
+ // only to have scrolling start automatically.
+ if (!mPassedInitialMotionThreshold && !aboveMotionThreshold(mLastLocation)) {
+ if (VERBOSE) Log.v(TAG, "Ignoring event below motion threshold.");
+ return;
+ }
+ mPassedInitialMotionThreshold = true;
+
+ if (pixelsPastView > verticalThreshold) {
+ pixelsPastView = verticalThreshold;
+ }
+
+ // Compute the number of pixels to scroll, and scroll that many pixels.
+ final int numPixels = computeScrollDistance(pixelsPastView);
+ mHost.scrollBy(numPixels);
+
+ // Replace any existing scheduled jobs with the latest and greatest..
+ mHost.removeCallback(mRunner);
+ mHost.runAtNextFrame(mRunner);
+ }
+
+ private boolean aboveMotionThreshold(Point location) {
+ // We reuse the scroll threshold to calculate a much smaller area
+ // in which we ignore motion initially.
+ int motionThreshold =
+ (int) ((mHost.getViewHeight() * mScrollThresholdRatio)
+ * (mScrollThresholdRatio * 2));
+ return Math.abs(mOrigin.y - location.y) >= motionThreshold;
+ }
+
+ /**
+ * Computes the number of pixels to scroll based on how far the pointer is past the end
+ * of the region. Roughly based on ItemTouchHelper's algorithm for computing the number of
+ * pixels to scroll when an item is dragged to the end of a view.
+ * @return
+ */
+ @VisibleForTesting
+ int computeScrollDistance(int pixelsPastView) {
+ final int topBottomThreshold =
+ (int) (mHost.getViewHeight() * mScrollThresholdRatio);
+
+ final int direction = (int) Math.signum(pixelsPastView);
+ final int absPastView = Math.abs(pixelsPastView);
+
+ // Calculate the ratio of how far out of the view the pointer currently resides to
+ // the top/bottom scrolling hotspot of the view.
+ final float outOfBoundsRatio = Math.min(
+ 1.0f, (float) absPastView / topBottomThreshold);
+ // Interpolate this ratio and use it to compute the maximum scroll that should be
+ // possible for this step.
+ final int cappedScrollStep =
+ (int) (direction * MAX_SCROLL_STEP * smoothOutOfBoundsRatio(outOfBoundsRatio));
+
+ // If the final number of pixels to scroll ends up being 0, the view should still
+ // scroll at least one pixel.
+ return cappedScrollStep != 0 ? cappedScrollStep : direction;
+ }
+
+ /**
+ * Interpolates the given out of bounds ratio on a curve which starts at (0,0) and ends
+ * at (1,1) and quickly approaches 1 near the start of that interval. This ensures that
+ * drags that are at the edge or barely past the edge of the threshold does little to no
+ * scrolling, while drags that are near the edge of the view does a lot of
+ * scrolling. The equation y=x^10 is used, but this could also be tweaked if
+ * needed.
+ * @param ratio A ratio which is in the range [0, 1].
+ * @return A "smoothed" value, also in the range [0, 1].
+ */
+ private float smoothOutOfBoundsRatio(float ratio) {
+ return (float) Math.pow(ratio, 10);
+ }
+
+ /**
+ * Used by to calculate the proper amount of pixels to scroll given time passed
+ * since scroll started, and to properly scroll / proper listener clean up if necessary.
+ *
+ * Callback used by scroller to perform UI tasks, such as scrolling and rerunning at next UI
+ * cycle.
+ */
+ abstract static class ScrollHost {
+ /**
+ * @return height of the view.
+ */
+ abstract int getViewHeight();
+
+ /**
+ * @param dy distance to scroll.
+ */
+ abstract void scrollBy(int dy);
+
+ /**
+ * @param r schedule runnable to be run at next convenient time.
+ */
+ abstract void runAtNextFrame(Runnable r);
+
+ /**
+ * @param r remove runnable from being run.
+ */
+ abstract void removeCallback(Runnable r);
+ }
+
+ public static ScrollHost createScrollHost(final RecyclerView view) {
+ return new RuntimeHost(view);
+ }
+
+ /**
+ * Tracks location of last surface contact as reported by RecyclerView.
+ */
+ private static final class RuntimeHost extends ScrollHost {
+
+ private final RecyclerView mRecView;
+
+ RuntimeHost(RecyclerView recView) {
+ mRecView = recView;
+ }
+
+ @Override
+ void runAtNextFrame(Runnable r) {
+ ViewCompat.postOnAnimation(mRecView, r);
+ }
+
+ @Override
+ void removeCallback(Runnable r) {
+ mRecView.removeCallbacks(r);
+ }
+
+ @Override
+ void scrollBy(int dy) {
+ if (VERBOSE) Log.v(TAG, "Scrolling view by: " + dy);
+ mRecView.scrollBy(0, dy);
+ }
+
+ @Override
+ int getViewHeight() {
+ return mRecView.getHeight();
+ }
+ }
+}
diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
index 0f5b84b1..d1838975 100644
--- a/com/android/car/setupwizardlib/CarSetupWizardLayout.java
+++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
@@ -23,7 +23,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
/**
@@ -32,9 +31,15 @@ import android.widget.RelativeLayout;
public class CarSetupWizardLayout extends LinearLayout {
private View mBackButton;
- private Button mContinueButton;
-
- private RelativeLayout mHeader;
+ /* The Primary Continue Button should always be used when there is only a single action that
+ * moves the wizard to the next screen (e.g. Only need a 'Skip' button).
+ *
+ * When there are two actions that can move the wizard to the next screen (e.g. either 'Skip'
+ * or 'Let's Go' are the two options), then the Primary is used for the positive action
+ * while the Secondary is used for the negative action.
+ */
+ private Button mPrimaryContinueButton;
+ private Button mSecondaryContinueButton;
public CarSetupWizardLayout(Context context) {
this(context, null);
@@ -47,6 +52,7 @@ public class CarSetupWizardLayout extends LinearLayout {
public CarSetupWizardLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
+
/**
* On initialization, the layout gets all of the custom attributes and initializes
* the custom views that can be set by the user (e.g. back button, continue button).
@@ -69,15 +75,30 @@ public class CarSetupWizardLayout extends LinearLayout {
*/
void init(TypedArray attrArray) {
boolean showBackButton;
- boolean showContinueButton;
- String continueButtonText;
+
+ boolean showPrimaryContinueButton;
+ String primaryContinueButtonText;
+ boolean primaryContinueButtonEnabled;
+
+ boolean showSecondaryContinueButton;
+ String secondaryContinueButtonText;
+ boolean secondaryContinueButtonEnabled;
+
try {
showBackButton = attrArray.getBoolean(
R.styleable.CarSetupWizardLayout_showBackButton, true);
- showContinueButton = attrArray.getBoolean(
- R.styleable.CarSetupWizardLayout_showContinueButton, true);
- continueButtonText = attrArray.getString(
- R.styleable.CarSetupWizardLayout_continueButtonText);
+ showPrimaryContinueButton = attrArray.getBoolean(
+ R.styleable.CarSetupWizardLayout_showPrimaryContinueButton, true);
+ primaryContinueButtonText = attrArray.getString(
+ R.styleable.CarSetupWizardLayout_primaryContinueButtonText);
+ primaryContinueButtonEnabled = attrArray.getBoolean(
+ R.styleable.CarSetupWizardLayout_primaryContinueButtonEnabled, true);
+ showSecondaryContinueButton = attrArray.getBoolean(
+ R.styleable.CarSetupWizardLayout_showSecondaryContinueButton, false);
+ secondaryContinueButtonText = attrArray.getString(
+ R.styleable.CarSetupWizardLayout_secondaryContinueButtonText);
+ secondaryContinueButtonEnabled = attrArray.getBoolean(
+ R.styleable.CarSetupWizardLayout_secondaryContinueButtonEnabled, true);
} finally {
attrArray.recycle();
}
@@ -85,59 +106,110 @@ public class CarSetupWizardLayout extends LinearLayout {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.car_setup_wizard_layout, this);
- mHeader = findViewById(R.id.header);
-
// Set the back button visibility based on the custom attribute.
mBackButton = findViewById(R.id.back_button);
if (!showBackButton) {
- setBackButtonVisibility(View.GONE);
+ setBackButtonVisible(false);
}
- // Set the continue button visibility and text based on the custom attributes.
- mContinueButton = findViewById(R.id.continue_button);
- if (showContinueButton) {
- setContinueButtonText(continueButtonText);
+ // Set the primary continue button visibility and text based on the custom attributes.
+ mPrimaryContinueButton = findViewById(R.id.primary_continue_button);
+ if (showPrimaryContinueButton) {
+ setPrimaryContinueButtonText(primaryContinueButtonText);
+ setPrimaryContinueButtonEnabled(primaryContinueButtonEnabled);
} else {
- setContinueButtonVisibility(View.GONE);
+ setPrimaryContinueButtonVisible(false);
+ }
+
+ // Set the secondary continue button visibility and text based on the custom attributes.
+ mSecondaryContinueButton = findViewById(R.id.secondary_continue_button);
+ if (showSecondaryContinueButton) {
+ setSecondaryContinueButtonText(secondaryContinueButtonText);
+ setSecondaryContinueButtonEnabled(secondaryContinueButtonEnabled);
+ } else {
+ setSecondaryContinueButtonVisible(false);
}
// TODO: Handle loading bar logic
}
/**
+ * Set a given button's visibility.
+ */
+ private void setViewVisible(View button, boolean visible) {
+ button.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Set the back button onClickListener to given listener. Can be null if the listener should
+ * be overridden so no callback is made.
+ */
+ public void setBackButtonListener(@Nullable View.OnClickListener listener) {
+ mBackButton.setOnClickListener(listener);
+ }
+
+ /**
* Set the back button visibility to the given visibility.
*/
- public void setBackButtonVisibility(int visibility) {
- mBackButton.setVisibility(visibility);
+ public void setBackButtonVisible(boolean visible) {
+ setViewVisible(mBackButton, visible);
}
/**
- * Set the continue button text to given text.
+ * Set whether the primary continue button is enabled.
*/
- public void setContinueButtonText(String text) {
- mContinueButton.setText(text);
+ public void setPrimaryContinueButtonEnabled(boolean enabled) {
+ mPrimaryContinueButton.setEnabled(enabled);
}
/**
- * Set the continue button visibility to given visibility.
+ * Set the primary continue button onClickListener to the given listener. Can be null if the
+ * listener should be overridden so no callback is made.
*/
- public void setContinueButtonVisibility(int visibility) {
- mContinueButton.setVisibility(visibility);
+ public void setPrimaryContinueButtonListener(@Nullable View.OnClickListener listener) {
+ mPrimaryContinueButton.setOnClickListener(listener);
}
/**
- * Set the back button onClickListener to given listener. Can be null if the listener should
- * be overridden so no callback is made.
+ * Set the primary continue button text to the given text.
*/
- public void setBackButtonListener(@Nullable View.OnClickListener listener) {
- mBackButton.setOnClickListener(listener);
+ public void setPrimaryContinueButtonText(String text) {
+ mPrimaryContinueButton.setText(text);
+ }
+
+ /**
+ * Set the primary continue button visibility to the given visibility.
+ */
+ public void setPrimaryContinueButtonVisible(boolean visible) {
+ setViewVisible(mPrimaryContinueButton, visible);
+ }
+
+ /**
+ * Set whether the secondary continue button is enabled.
+ */
+ public void setSecondaryContinueButtonEnabled(boolean enabled){
+ mSecondaryContinueButton.setEnabled(enabled);
+ }
+
+ /**
+ * Set the secondary continue button onClickListener to the given listener. Can be null if the
+ * listener should be overridden so no callback is made.
+ */
+ public void setSecondaryContinueButtonListener(@Nullable View.OnClickListener listener) {
+ mSecondaryContinueButton.setOnClickListener(listener);
+ }
+
+ /**
+ * Set the secondary continue button text to the given text.
+ */
+ public void setSecondaryContinueButtonText(String text) {
+ mSecondaryContinueButton.setText(text);
}
/**
- * Set the continue button onClickListener to then given listener. Can be null if the listener
- * should be overridden so no callback is made.
+ * Set the secondary continue button visibility to the given visibility.
*/
- public void setContinueButtonListener(@Nullable View.OnClickListener listener) {
- mContinueButton.setOnClickListener(listener);
+ public void setSecondaryContinueButtonVisible(boolean visible) {
+ setViewVisible(mSecondaryContinueButton, visible);
}
}
diff --git a/com/android/commands/am/Am.java b/com/android/commands/am/Am.java
index ab075ee0..813335a6 100644
--- a/com/android/commands/am/Am.java
+++ b/com/android/commands/am/Am.java
@@ -98,7 +98,8 @@ public class Am extends BaseCommand {
static final class MyShellCallback extends ShellCallback {
boolean mActive = true;
- @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+ String mode) {
if (!mActive) {
System.err.println("Open attempt after active for: " + path);
return null;
@@ -159,7 +160,11 @@ public class Am extends BaseCommand {
} else if (opt.equals("-r")) {
instrument.rawMode = true;
} else if (opt.equals("-m")) {
- instrument.proto = true;
+ instrument.protoStd = true;
+ } else if (opt.equals("-f")) {
+ instrument.protoFile = true;
+ if (peekNextArg() != null && !peekNextArg().startsWith("-"))
+ instrument.logPath = nextArg();
} else if (opt.equals("-e")) {
final String argKey = nextArgRequired();
final String argValue = nextArgRequired();
diff --git a/com/android/commands/am/Instrument.java b/com/android/commands/am/Instrument.java
index b69ef1c2..d79b1a61 100644
--- a/com/android/commands/am/Instrument.java
+++ b/com/android/commands/am/Instrument.java
@@ -25,23 +25,42 @@ import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.proto.ProtoOutputStream;
import android.view.IWindowManager;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
+import java.util.Locale;
/**
* Runs the am instrument command
+ *
+ * Test Result Code:
+ * 1 - Test running
+ * 0 - Test passed
+ * -2 - assertion failure
+ * -1 - other exceptions
+ *
+ * Session Result Code:
+ * -1: Success
+ * other: Failure
*/
public class Instrument {
+ public static final String DEFAULT_LOG_DIR = "instrument-logs";
+
private final IActivityManager mAm;
private final IPackageManager mPm;
private final IWindowManager mWm;
@@ -50,7 +69,9 @@ public class Instrument {
public String profileFile = null;
public boolean wait = false;
public boolean rawMode = false;
- public boolean proto = false;
+ boolean protoStd = false; // write proto to stdout
+ boolean protoFile = false; // write proto to a file
+ String logPath = null;
public boolean noWindowAnimation = false;
public String abi = null;
public int userId = UserHandle.USER_CURRENT;
@@ -178,18 +199,49 @@ public class Instrument {
* Printer for the protobuf based status reporting.
*/
private class ProtoStatusReporter implements StatusReporter {
+
+ private File mLog;
+
+ ProtoStatusReporter() {
+ if (protoFile) {
+ if (logPath == null) {
+ File logDir = new File(Environment.getLegacyExternalStorageDirectory(),
+ DEFAULT_LOG_DIR);
+ if (!logDir.exists() && !logDir.mkdirs()) {
+ System.err.format("Unable to create log directory: %s\n",
+ logDir.getAbsolutePath());
+ protoFile = false;
+ return;
+ }
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-hhmmss-SSS", Locale.US);
+ String fileName = String.format("log-%s.instrumentation_data_proto",
+ format.format(new Date()));
+ mLog = new File(logDir, fileName);
+ } else {
+ mLog = new File(Environment.getLegacyExternalStorageDirectory(), logPath);
+ File logDir = mLog.getParentFile();
+ if (!logDir.exists() && !logDir.mkdirs()) {
+ System.err.format("Unable to create log directory: %s\n",
+ logDir.getAbsolutePath());
+ protoFile = false;
+ return;
+ }
+ }
+ if (mLog.exists()) mLog.delete();
+ }
+ }
+
@Override
public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
Bundle results) {
final ProtoOutputStream proto = new ProtoOutputStream();
- final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
-
- proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
+ final long token = proto.start(InstrumentationData.Session.TEST_STATUS);
+ proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
+ proto.end(token);
- proto.endRepeatedObject(token);
- writeProtoToStdout(proto);
+ outputProto(proto);
}
@Override
@@ -197,80 +249,87 @@ public class Instrument {
Bundle results) {
final ProtoOutputStream proto = new ProtoOutputStream();
- final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
- proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+ proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
InstrumentationData.SESSION_FINISHED);
- proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
+ proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
+ proto.end(token);
- proto.endObject(token);
- writeProtoToStdout(proto);
+ outputProto(proto);
}
@Override
public void onError(String errorText, boolean commandError) {
final ProtoOutputStream proto = new ProtoOutputStream();
- final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
- proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+ proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
InstrumentationData.SESSION_ABORTED);
- proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+ proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+ proto.end(token);
- proto.endObject(token);
- writeProtoToStdout(proto);
+ outputProto(proto);
}
private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
- final long bundleToken = proto.startObject(fieldId);
+ final long bundleToken = proto.start(fieldId);
for (final String key: sorted(bundle.keySet())) {
final long entryToken = proto.startRepeatedObject(
InstrumentationData.ResultsBundle.ENTRIES);
- proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
+ proto.write(InstrumentationData.ResultsBundleEntry.KEY, key);
final Object val = bundle.get(key);
if (val instanceof String) {
- proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
(String)val);
} else if (val instanceof Byte) {
- proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT,
((Byte)val).intValue());
} else if (val instanceof Double) {
- proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
- ((Double)val).doubleValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, (double)val);
} else if (val instanceof Float) {
- proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
- ((Float)val).floatValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, (float)val);
} else if (val instanceof Integer) {
- proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
- ((Integer)val).intValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (int)val);
} else if (val instanceof Long) {
- proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
- ((Long)val).longValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_LONG, (long)val);
} else if (val instanceof Short) {
- proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
- ((Short)val).intValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (short)val);
} else if (val instanceof Bundle) {
writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
(Bundle)val);
+ } else if (val instanceof byte[]) {
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_BYTES, (byte[])val);
}
- proto.endRepeatedObject(entryToken);
+ proto.end(entryToken);
}
- proto.endObject(bundleToken);
+ proto.end(bundleToken);
}
- private void writeProtoToStdout(ProtoOutputStream proto) {
- try {
- System.out.write(proto.getBytes());
- System.out.flush();
- } catch (IOException ex) {
- System.err.println("Error writing finished response: ");
- ex.printStackTrace(System.err);
+ private void outputProto(ProtoOutputStream proto) {
+ byte[] out = proto.getBytes();
+ if (protoStd) {
+ try {
+ System.out.write(out);
+ System.out.flush();
+ } catch (IOException ex) {
+ System.err.println("Error writing finished response: ");
+ ex.printStackTrace(System.err);
+ }
+ }
+ if (protoFile) {
+ try (OutputStream os = new FileOutputStream(mLog, true)) {
+ os.write(proto.getBytes());
+ os.flush();
+ } catch (IOException ex) {
+ System.err.format("Cannot write to %s:\n", mLog.getAbsolutePath());
+ ex.printStackTrace();
+ }
}
}
}
@@ -374,7 +433,7 @@ public class Instrument {
try {
// Choose which output we will do.
- if (proto) {
+ if (protoFile || protoStd) {
reporter = new ProtoStatusReporter();
} else if (wait) {
reporter = new TextStatusReporter(rawMode);
@@ -396,7 +455,7 @@ public class Instrument {
mWm.setAnimationScale(2, 0.0f);
}
- // Figure out which component we are tring to do.
+ // Figure out which component we are trying to do.
final ComponentName cn = parseComponentName(componentNameArg);
// Choose an ABI if necessary
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index 29433f3f..9490880a 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -157,7 +157,8 @@ public final class Pm {
}
static final class MyShellCallback extends ShellCallback {
- @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+ String mode) {
File file = new File(path);
final ParcelFileDescriptor fd;
try {
diff --git a/com/android/commands/sm/Sm.java b/com/android/commands/sm/Sm.java
index a9a4118a..77e8efaf 100644
--- a/com/android/commands/sm/Sm.java
+++ b/com/android/commands/sm/Sm.java
@@ -20,6 +20,9 @@ import static android.os.storage.StorageManager.PROP_ADOPTABLE_FBE;
import static android.os.storage.StorageManager.PROP_HAS_ADOPTABLE;
import static android.os.storage.StorageManager.PROP_VIRTUAL_DISK;
+import android.os.IBinder;
+import android.os.IVoldTaskListener;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -29,6 +32,8 @@ import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.util.Log;
+import java.util.concurrent.CompletableFuture;
+
public final class Sm {
private static final String TAG = "Sm";
@@ -96,6 +101,8 @@ public final class Sm {
runSetEmulateFbe();
} else if ("get-fbe-mode".equals(op)) {
runGetFbeMode();
+ } else if ("idle-maint".equals(op)) {
+ runIdleMaint();
} else if ("fstrim".equals(op)) {
runFstrim();
} else if ("set-virtual-disk".equals(op)) {
@@ -221,9 +228,23 @@ public final class Sm {
mSm.format(volId);
}
- public void runBenchmark() throws RemoteException {
+ public void runBenchmark() throws Exception {
final String volId = nextArg();
- mSm.benchmark(volId);
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ mSm.benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ // Touch to unparcel
+ extras.size();
+ result.complete(extras);
+ }
+ });
+ System.out.println(result.get());
}
public void runForget() throws RemoteException {
@@ -235,8 +256,22 @@ public final class Sm {
}
}
- public void runFstrim() throws RemoteException {
- mSm.fstrim(0);
+ public void runFstrim() throws Exception {
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ mSm.fstrim(0, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ // Touch to unparcel
+ extras.size();
+ result.complete(extras);
+ }
+ });
+ System.out.println(result.get());
}
public void runSetVirtualDisk() throws RemoteException {
@@ -245,6 +280,15 @@ public final class Sm {
StorageManager.DEBUG_VIRTUAL_DISK);
}
+ public void runIdleMaint() throws RemoteException {
+ final boolean im_run = "run".equals(nextArg());
+ if (im_run) {
+ mSm.runIdleMaintenance();
+ } else {
+ mSm.abortIdleMaintenance();
+ }
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -267,6 +311,7 @@ public final class Sm {
System.err.println(" sm unmount VOLUME");
System.err.println(" sm format VOLUME");
System.err.println(" sm benchmark VOLUME");
+ System.err.println(" sm idle-maint [run|abort]");
System.err.println(" sm fstrim");
System.err.println("");
System.err.println(" sm forget [UUID|all]");
diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java
index 6d4d4d20..ae621979 100644
--- a/com/android/ex/photo/ActionBarWrapper.java
+++ b/com/android/ex/photo/ActionBarWrapper.java
@@ -1,7 +1,8 @@
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 7b53918f..a5c4a438 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.v4.app.FragmentActivity;
+import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
/**
* Activity to view the contents of an album.
*/
-public class PhotoViewActivity extends FragmentActivity
+public class PhotoViewActivity extends AppCompatActivity
implements PhotoViewController.ActivityInterface {
private PhotoViewController mController;
@@ -41,7 +41,7 @@ public class PhotoViewActivity extends FragmentActivity
mController.onCreate(savedInstanceState);
}
- public PhotoViewController createController() {
+ protected PhotoViewController createController() {
return new PhotoViewController(this);
}
@@ -122,7 +122,7 @@ public class PhotoViewActivity extends FragmentActivity
@Override
public ActionBarInterface getActionBarInterface() {
if (mActionBar == null) {
- mActionBar = new ActionBarWrapper(getActionBar());
+ mActionBar = new ActionBarWrapper(getSupportActionBar());
}
return mActionBar;
}
diff --git a/com/android/ims/ImsManager.java b/com/android/ims/ImsManager.java
index a77abcd6..813118ba 100644
--- a/com/android/ims/ImsManager.java
+++ b/com/android/ims/ImsManager.java
@@ -37,8 +37,6 @@ import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.telephony.ims.ImsServiceProxy;
-import android.telephony.ims.ImsServiceProxyCompat;
import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
@@ -183,7 +181,7 @@ public class ImsManager {
private CarrierConfigManager mConfigManager;
private int mPhoneId;
private final boolean mConfigDynamicBind;
- private ImsServiceProxyCompat mImsServiceProxy = null;
+ private ImsServiceProxy mImsServiceProxy = null;
private ImsServiceDeathRecipient mDeathRecipient = new ImsServiceDeathRecipient();
// Ut interface for the supplementary service configuration
private ImsUt mUt = null;
@@ -237,6 +235,8 @@ public class ImsManager {
private static final long BACKOFF_MAX_DELAY_MS = 300000;
// Multiplier for exponential delay
private static final int BACKOFF_MULTIPLIER = 2;
+ // -1 indicates a subscriptionProperty value that is never set.
+ private static final int SUB_PROPERTY_NOT_INITIALIZED = -1;
/**
@@ -281,20 +281,22 @@ public class ImsManager {
}
/**
- * Returns the user configuration of Enhanced 4G LTE Mode setting for slot.
+ * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If not set, it
+ * returns true as 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)) {
+ if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
return true;
}
- int enabled = android.provider.Settings.Global.getInt(
- mContext.getContentResolver(),
- android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED,
- ImsConfig.FeatureValueConstants.ON);
- return (enabled == 1);
+
+ int setting = SubscriptionManager.getIntegerSubscriptionProperty(
+ getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+ SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+ // If it's never set, by default we return true.
+ return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
}
/**
@@ -319,28 +321,23 @@ public class ImsManager {
*
*/
public void setEnhanced4gLteModeSetting(boolean enabled) {
- // If false, we must always keep advanced 4G mode set to true (1).
- int value = getBooleanCarrierConfig(
- CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL) ? (enabled ? 1: 0) : 1;
-
- try {
- int prevSetting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED);
- if (prevSetting == value) {
- // Don't trigger setAdvanced4GMode if the setting hasn't changed.
- return;
- }
- } catch (Settings.SettingNotFoundException e) {
- // Setting doesn't exist yet, so set it below.
- }
-
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED, value);
- if (isNonTtyOrTtyOnVolteEnabled()) {
- try {
- setAdvanced4GMode(enabled);
- } catch (ImsException ie) {
- // do nothing
+ // If false, we must always keep advanced 4G mode set to true.
+ enabled = getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
+ ? enabled : true;
+
+ int prevSetting = SubscriptionManager.getIntegerSubscriptionProperty(
+ getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+ SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+ if (prevSetting != (enabled ? 1 : 0)) {
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(enabled));
+ if (isNonTtyOrTtyOnVolteEnabled()) {
+ try {
+ setAdvanced4GMode(enabled);
+ } catch (ImsException ie) {
+ // do nothing
+ }
}
}
}
@@ -406,8 +403,7 @@ public class ImsManager {
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_device_volte_available)
- && getBooleanCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
+ && getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
&& isGbaValid();
}
@@ -541,8 +537,7 @@ public class ImsManager {
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_device_vt_available) &&
- getBooleanCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL) &&
+ getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL) &&
isGbaValid();
}
@@ -562,13 +557,16 @@ public class ImsManager {
}
/**
- * Returns the user configuration of VT setting per slot.
+ * Returns the user configuration of VT setting per slot. If not set, it
+ * returns true as default value.
*/
public boolean isVtEnabledByUser() {
- int enabled = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.VT_IMS_ENABLED,
- ImsConfig.FeatureValueConstants.ON);
- return (enabled == 1);
+ int setting = SubscriptionManager.getIntegerSubscriptionProperty(
+ getSubId(), SubscriptionManager.VT_IMS_ENABLED,
+ SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+ // If it's never set, by default we return true.
+ return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
}
/**
@@ -589,10 +587,9 @@ public class ImsManager {
* Change persistent VT enabled setting for slot.
*/
public void setVtSetting(boolean enabled) {
- int value = enabled ? 1 : 0;
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.VT_IMS_ENABLED, value);
-
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.VT_IMS_ENABLED,
+ booleanToPropertyString(enabled));
try {
ImsConfig config = getConfigInterface();
config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
@@ -662,15 +659,21 @@ public class ImsManager {
}
/**
- * Returns the user configuration of WFC setting for slot.
+ * Returns the user configuration of WFC setting for slot. If not set, it
+ * queries CarrierConfig value as default.
*/
public boolean isWfcEnabledByUser() {
- int enabled = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ENABLED,
- getBooleanCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL) ?
- ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
- return enabled == 1;
+ int setting = SubscriptionManager.getIntegerSubscriptionProperty(
+ getSubId(), SubscriptionManager.WFC_IMS_ENABLED,
+ SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+ // SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
+ if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+ return getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL);
+ } else {
+ return setting == 1;
+ }
}
/**
@@ -691,9 +694,8 @@ public class ImsManager {
* Change persistent WFC enabled setting for slot.
*/
public void setWfcSetting(boolean enabled) {
- int value = enabled ? 1 : 0;
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ENABLED, value);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_ENABLED, booleanToPropertyString(enabled));
setWfcNonPersistent(enabled, getWfcMode());
}
@@ -736,7 +738,7 @@ public class ImsManager {
/**
* Returns the user configuration of WFC preference setting.
*
- * @deprecated Doesn't support MSIM devices. Use {@link #getWfcMode()} instead.
+ * @deprecated Doesn't support MSIM devices. Use {@link #getWfcMode(boolean roaming)} instead.
*/
public static int getWfcMode(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
@@ -750,13 +752,10 @@ public class ImsManager {
/**
* Returns the user configuration of WFC preference setting
+ * @deprecated. Use {@link #getWfcMode(boolean roaming)} instead.
*/
public int getWfcMode() {
- int setting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_MODE, getIntCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
- if (DBG) log("getWfcMode - setting=" + setting);
- return setting;
+ return getWfcMode(false);
}
/**
@@ -778,8 +777,9 @@ public class ImsManager {
*/
public void setWfcMode(int wfcMode) {
if (DBG) log("setWfcMode(i) - setting=" + wfcMode);
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_MODE, wfcMode);
+
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_MODE, Integer.toString(wfcMode));
setWfcModeInternal(wfcMode);
}
@@ -813,22 +813,35 @@ public class ImsManager {
}
/**
- * Returns the user configuration of WFC preference setting for slot
+ * Returns the user configuration of WFC preference setting for slot. If not set, it
+ * queries CarrierConfig value as default.
*
* @param roaming {@code false} for home network setting, {@code true} for roaming setting
*/
public int getWfcMode(boolean roaming) {
int setting = 0;
if (!roaming) {
- setting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_MODE, getIntCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
+ setting = SubscriptionManager.getIntegerSubscriptionProperty(
+ getSubId(), SubscriptionManager.WFC_IMS_MODE,
+ SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+ // SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
+ if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+ setting = getIntCarrierConfig(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT);
+ }
if (DBG) log("getWfcMode - setting=" + setting);
} else {
- setting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ROAMING_MODE,
- getIntCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT));
+ setting = SubscriptionManager.getIntegerSubscriptionProperty(
+ getSubId(), SubscriptionManager.WFC_IMS_ROAMING_MODE,
+ SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+ // SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
+ if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+ setting = getIntCarrierConfig(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT);
+ }
+
if (DBG) log("getWfcMode (roaming) - setting=" + setting);
}
return setting;
@@ -859,15 +872,14 @@ public class ImsManager {
public void setWfcMode(int wfcMode, boolean roaming) {
if (!roaming) {
if (DBG) log("setWfcMode(i,b) - setting=" + wfcMode);
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_MODE, wfcMode);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_MODE, Integer.toString(wfcMode));
} else {
if (DBG) log("setWfcMode(i,b) (roaming) - setting=" + wfcMode);
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ROAMING_MODE, wfcMode);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_ROAMING_MODE, Integer.toString(wfcMode));
}
-
TelephonyManager tm = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (roaming == tm.isNetworkRoaming(getSubId())) {
@@ -907,13 +919,12 @@ public class ImsManager {
private void setWfcModeInternal(int wfcMode) {
final int value = wfcMode;
Thread thread = new Thread(() -> {
- try {
- getConfigInterface().setProvisionedValue(
- ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE,
- value);
- } catch (ImsException e) {
- // do nothing
- }
+ try {
+ getConfigInterface().setProvisionedValue(
+ ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE, value);
+ } catch (ImsException e) {
+ // do nothing
+ }
});
thread.start();
}
@@ -935,15 +946,19 @@ public class ImsManager {
}
/**
- * Returns the user configuration of WFC roaming setting for slot
+ * Returns the user configuration of WFC roaming setting for slot. If not set, it
+ * queries CarrierConfig value as default.
*/
public boolean isWfcRoamingEnabledByUser() {
- int enabled = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
- getBooleanCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL) ?
- ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
- return (enabled == 1);
+ int setting = SubscriptionManager.getIntegerSubscriptionProperty(
+ getSubId(), SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
+ SUB_PROPERTY_NOT_INITIALIZED, mContext);
+ if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+ return getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL);
+ } else {
+ return (setting == 1);
+ }
}
/**
@@ -962,10 +977,9 @@ public class ImsManager {
* Change persistent WFC roaming enabled setting
*/
public void setWfcRoamingSetting(boolean enabled) {
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
- enabled ? ImsConfig.FeatureValueConstants.ON
- : ImsConfig.FeatureValueConstants.OFF);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_ROAMING_ENABLED, booleanToPropertyString(enabled)
+ );
setWfcRoamingSettingInternal(enabled);
}
@@ -975,13 +989,12 @@ public class ImsManager {
? ImsConfig.FeatureValueConstants.ON
: ImsConfig.FeatureValueConstants.OFF;
Thread thread = new Thread(() -> {
- try {
- getConfigInterface().setProvisionedValue(
- ImsConfig.ConfigConstants.VOICE_OVER_WIFI_ROAMING,
- value);
- } catch (ImsException e) {
- // do nothing
- }
+ try {
+ getConfigInterface().setProvisionedValue(
+ ImsConfig.ConfigConstants.VOICE_OVER_WIFI_ROAMING, value);
+ } catch (ImsException e) {
+ // do nothing
+ }
});
thread.start();
}
@@ -1361,6 +1374,14 @@ public class ImsManager {
return mImsServiceProxy.isBinderAlive();
}
+ /*
+ * Returns a flag indicating whether the IMS service is ready to send requests to lower layers.
+ */
+ public boolean isServiceReady() {
+ connectIfServiceIsAvailable();
+ return mImsServiceProxy.isBinderReady();
+ }
+
/**
* If the service is available, try to reconnect.
*/
@@ -2383,34 +2404,30 @@ public class ImsManager {
*/
public void factoryReset() {
// Set VoLTE to default
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED,
- ImsConfig.FeatureValueConstants.ON);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(true));
// Set VoWiFi to default
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ENABLED,
- getBooleanCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL) ?
- ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_ENABLED,
+ booleanToPropertyString(getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL)));
// Set VoWiFi mode to default
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_MODE,
- getIntCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_MODE,
+ Integer.toString(getIntCarrierConfig(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)));
// Set VoWiFi roaming to default
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
- getBooleanCarrierConfig(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL) ?
- ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
+ booleanToPropertyString(getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL)));
// Set VT to default
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.VT_IMS_ENABLED,
- ImsConfig.FeatureValueConstants.ON);
+ SubscriptionManager.setSubscriptionProperty(getSubId(),
+ SubscriptionManager.VT_IMS_ENABLED, booleanToPropertyString(true));
// Push settings to ImsConfig
updateImsServiceConfig(true);
@@ -2453,6 +2470,11 @@ public class ImsManager {
SystemProperties.set(VT_PROVISIONED_PROP, provisioned ? TRUE : FALSE);
}
+ private static String booleanToPropertyString(boolean bool) {
+ return bool ? "1" : "0";
+ }
+
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("ImsManager:");
pw.println(" mPhoneId = " + mPhoneId);
diff --git a/android/telephony/ims/ImsServiceProxy.java b/com/android/ims/ImsServiceProxy.java
index 038e295d..8c51202f 100644
--- a/android/telephony/ims/ImsServiceProxy.java
+++ b/com/android/ims/ImsServiceProxy.java
@@ -14,17 +14,15 @@
* limitations under the License
*/
-package android.telephony.ims;
+package com.android.ims;
import android.app.PendingIntent;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
-import android.telephony.ims.feature.IRcsFeature;
import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
-import com.android.ims.ImsCallProfile;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsCallSessionListener;
import com.android.ims.internal.IImsConfig;
@@ -41,9 +39,11 @@ import com.android.ims.internal.IImsUt;
* @hide
*/
-public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeature {
+public class ImsServiceProxy {
protected String LOG_TAG = "ImsServiceProxy";
+ protected final int mSlotId;
+ protected IBinder mBinder;
private final int mSupportedFeature;
// Start by assuming the proxy is available for usage.
@@ -99,13 +99,13 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
};
public ImsServiceProxy(int slotId, IBinder binder, int featureType) {
- super(slotId, binder);
+ mSlotId = slotId;
+ mBinder = binder;
mSupportedFeature = featureType;
}
public ImsServiceProxy(int slotId, int featureType) {
- super(slotId, null /*IBinder*/);
- mSupportedFeature = featureType;
+ this(slotId, null, featureType);
}
public IImsServiceFeatureListener getListener() {
@@ -116,7 +116,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
mBinder = binder;
}
- @Override
public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
@@ -126,7 +125,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public void endSession(int sessionId) throws RemoteException {
synchronized (mLock) {
// Only check to make sure the binder connection still exists. This method should
@@ -136,7 +134,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public boolean isConnected(int callServiceType, int callType)
throws RemoteException {
synchronized (mLock) {
@@ -146,7 +143,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public boolean isOpened() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -154,7 +150,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
@@ -164,7 +159,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
@@ -174,7 +168,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
throws RemoteException {
synchronized (mLock) {
@@ -184,7 +177,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
IImsCallSessionListener listener) throws RemoteException {
synchronized (mLock) {
@@ -194,7 +186,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public IImsCallSession getPendingCallSession(int sessionId, String callId)
throws RemoteException {
synchronized (mLock) {
@@ -204,7 +195,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public IImsUt getUtInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -212,7 +202,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public IImsConfig getConfigInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -220,7 +209,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public void turnOnIms() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -228,7 +216,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public void turnOffIms() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -236,7 +223,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public IImsEcbm getEcbmInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -244,7 +230,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public void setUiTTYMode(int uiTtyMode, Message onComplete)
throws RemoteException {
synchronized (mLock) {
@@ -254,7 +239,6 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -263,7 +247,10 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
}
}
- @Override
+ /**
+ * @return an integer describing the current Feature Status, defined in
+ * {@link ImsFeature.ImsState}.
+ */
public int getFeatureStatus() {
synchronized (mLock) {
if (isBinderAlive() && mFeatureStatusCached != null) {
@@ -318,7 +305,9 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
}
- @Override
+ /**
+ * @return false if the binder connection is no longer alive.
+ */
public boolean isBinderAlive() {
return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
}
@@ -332,4 +321,10 @@ public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeatur
private IImsServiceController getServiceInterface(IBinder b) {
return IImsServiceController.Stub.asInterface(b);
}
+
+ protected void checkBinderConnection() throws RemoteException {
+ if (!isBinderAlive()) {
+ throw new RemoteException("ImsServiceProxy is not available for that feature.");
+ }
+ }
}
diff --git a/android/telephony/ims/ImsServiceProxyCompat.java b/com/android/ims/ImsServiceProxyCompat.java
index bbd5f027..5ba1f351 100644
--- a/android/telephony/ims/ImsServiceProxyCompat.java
+++ b/com/android/ims/ImsServiceProxyCompat.java
@@ -14,16 +14,14 @@
* limitations under the License
*/
-package android.telephony.ims;
+package com.android.ims;
import android.app.PendingIntent;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
-import android.telephony.ims.feature.IMMTelFeature;
import android.telephony.ims.feature.ImsFeature;
-import com.android.ims.ImsCallProfile;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsCallSessionListener;
import com.android.ims.internal.IImsConfig;
@@ -34,22 +32,18 @@ import com.android.ims.internal.IImsService;
import com.android.ims.internal.IImsUt;
/**
- * Compatibility class that implements the new ImsService IMMTelFeature interface, but
+ * Compatibility class that implements the new ImsService MMTelFeature interface, but
* uses the old IImsService interface to support older devices that implement the deprecated
* opt/net/ims interface.
* @hide
*/
-public class ImsServiceProxyCompat implements IMMTelFeature {
+public class ImsServiceProxyCompat extends ImsServiceProxy {
private static final int SERVICE_ID = ImsFeature.MMTEL;
- protected final int mSlotId;
- protected IBinder mBinder;
-
public ImsServiceProxyCompat(int slotId, IBinder binder) {
- mSlotId = slotId;
- mBinder = binder;
+ super(slotId, binder, SERVICE_ID);
}
@Override
@@ -156,17 +150,12 @@ public class ImsServiceProxyCompat implements IMMTelFeature {
checkBinderConnection();
return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID);
}
-
- /**
- * Base implementation, always returns READY for compatibility with old ImsService.
- */
+ @Override
public int getFeatureStatus() {
return ImsFeature.STATE_READY;
}
- /**
- * @return false if the binder connection is no longer alive.
- */
+ @Override
public boolean isBinderAlive() {
return mBinder != null && mBinder.isBinderAlive();
}
@@ -174,10 +163,4 @@ public class ImsServiceProxyCompat implements IMMTelFeature {
private IImsService getServiceInterface(IBinder b) {
return IImsService.Stub.asInterface(b);
}
-
- protected void checkBinderConnection() throws RemoteException {
- if (!isBinderAlive()) {
- throw new RemoteException("ImsServiceProxy is not available for that feature.");
- }
- }
}
diff --git a/com/android/internal/app/NightDisplayController.java b/com/android/internal/app/ColorDisplayController.java
index b2053c00..b8682a89 100644
--- a/com/android/internal/app/NightDisplayController.java
+++ b/com/android/internal/app/ColorDisplayController.java
@@ -25,6 +25,7 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemProperties;
import android.provider.Settings.Secure;
import android.provider.Settings.System;
import android.util.Slog;
@@ -41,14 +42,14 @@ import java.time.ZoneId;
import java.time.format.DateTimeParseException;
/**
- * Controller for managing Night display settings.
+ * Controller for managing night display and color mode settings.
* <p/>
* Night display tints your screen red at night. This makes it easier to look at your screen in
* dim light and may help you fall asleep more easily.
*/
-public final class NightDisplayController {
+public final class ColorDisplayController {
- private static final String TAG = "NightDisplayController";
+ private static final String TAG = "ColorDisplayController";
private static final boolean DEBUG = false;
@Retention(RetentionPolicy.SOURCE)
@@ -100,6 +101,12 @@ public final class NightDisplayController {
*/
public static final int COLOR_MODE_SATURATED = 2;
+ /**
+ * See com.android.server.display.DisplayTransformManager.
+ */
+ private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
+ private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+
private final Context mContext;
private final int mUserId;
@@ -107,11 +114,11 @@ public final class NightDisplayController {
private Callback mCallback;
- public NightDisplayController(@NonNull Context context) {
+ public ColorDisplayController(@NonNull Context context) {
this(context, ActivityManager.getCurrentUser());
}
- public NightDisplayController(@NonNull Context context, int userId) {
+ public ColorDisplayController(@NonNull Context context, int userId) {
mContext = context.getApplicationContext();
mUserId = userId;
@@ -334,9 +341,15 @@ public final class NightDisplayController {
*/
public int getColorMode() {
final int colorMode = System.getIntForUser(mContext.getContentResolver(),
- System.DISPLAY_COLOR_MODE, COLOR_MODE_BOOSTED, mUserId);
+ System.DISPLAY_COLOR_MODE, -1, mUserId);
if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
- return COLOR_MODE_BOOSTED;
+ // There still might be a legacy system property controlling color mode that we need to
+ // respect.
+ if ("1".equals(SystemProperties.get(PERSISTENT_PROPERTY_NATIVE_MODE))) {
+ return COLOR_MODE_SATURATED;
+ }
+ return "1.0".equals(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
}
return colorMode;
}
diff --git a/com/android/internal/app/LocaleHelper.java b/com/android/internal/app/LocaleHelper.java
index 386aa84b..0a230a90 100644
--- a/com/android/internal/app/LocaleHelper.java
+++ b/com/android/internal/app/LocaleHelper.java
@@ -136,7 +136,16 @@ public class LocaleHelper {
* @return the localized country name.
*/
public static String getDisplayCountry(Locale locale, Locale displayLocale) {
- return ULocale.getDisplayCountry(locale.toLanguageTag(), ULocale.forLocale(displayLocale));
+ final String languageTag = locale.toLanguageTag();
+ final ULocale uDisplayLocale = ULocale.forLocale(displayLocale);
+ final String country = ULocale.getDisplayCountry(languageTag, uDisplayLocale);
+ final String numberingSystem = locale.getUnicodeLocaleType("nu");
+ if (numberingSystem != null) {
+ return String.format("%s (%s)", country,
+ ULocale.getDisplayKeywordValue(languageTag, "numbers", uDisplayLocale));
+ } else {
+ return country;
+ }
}
/**
diff --git a/com/android/internal/app/LocalePicker.java b/com/android/internal/app/LocalePicker.java
index 9936ed5c..c8c2fcf6 100644
--- a/com/android/internal/app/LocalePicker.java
+++ b/com/android/internal/app/LocalePicker.java
@@ -93,10 +93,6 @@ public class LocalePicker extends ListFragment {
return context.getResources().getStringArray(R.array.supported_locales);
}
- public static String[] getPseudoLocales() {
- return pseudoLocales;
- }
-
public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
final Resources resources = context.getResources();
@@ -104,13 +100,6 @@ public class LocalePicker extends ListFragment {
List<String> localeList = new ArrayList<String>(locales.length);
Collections.addAll(localeList, locales);
- // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
- if (!isInDeveloperMode) {
- for (String locale : pseudoLocales) {
- localeList.remove(locale);
- }
- }
-
Collections.sort(localeList);
final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
@@ -122,6 +111,10 @@ public class LocalePicker extends ListFragment {
|| l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
continue;
}
+ // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
+ if (!isInDeveloperMode && LocaleList.isPseudoLocale(l)) {
+ continue;
+ }
if (localeInfos.isEmpty()) {
if (DEBUG) {
diff --git a/com/android/internal/app/LocaleStore.java b/com/android/internal/app/LocaleStore.java
index e3fce519..2b0b5eec 100644
--- a/com/android/internal/app/LocaleStore.java
+++ b/com/android/internal/app/LocaleStore.java
@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.content.Context;
+import android.os.LocaleList;
import android.provider.Settings;
import android.telephony.TelephonyManager;
@@ -68,7 +69,9 @@ public class LocaleStore {
return null;
}
return new Locale.Builder()
- .setLocale(locale).setRegion("")
+ .setLocale(locale)
+ .setRegion("")
+ .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "")
.build();
}
@@ -253,11 +256,25 @@ public class LocaleStore {
Set<String> simCountries = getSimCountries(context);
+ final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
for (String localeId : LocalePicker.getSupportedLocales(context)) {
if (localeId.isEmpty()) {
throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
}
LocaleInfo li = new LocaleInfo(localeId);
+
+ if (LocaleList.isPseudoLocale(li.getLocale())) {
+ if (isInDeveloperMode) {
+ li.setTranslated(true);
+ li.mIsPseudo = true;
+ li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
+ } else {
+ // Do not display pseudolocales unless in development mode.
+ continue;
+ }
+ }
+
if (simCountries.contains(li.getLocale().getCountry())) {
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
}
@@ -271,19 +288,6 @@ public class LocaleStore {
}
}
- boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
- for (String localeId : LocalePicker.getPseudoLocales()) {
- LocaleInfo li = getLocaleInfo(Locale.forLanguageTag(localeId));
- if (isInDeveloperMode) {
- li.setTranslated(true);
- li.mIsPseudo = true;
- li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
- } else {
- sLocaleCache.remove(li.getId());
- }
- }
-
// TODO: See if we can reuse what LocaleList.matchScore does
final HashSet<String> localizedLocales = new HashSet<>();
for (String localeId : LocalePicker.getSystemAssetLocales()) {
diff --git a/com/android/internal/colorextraction/types/Tonal.java b/com/android/internal/colorextraction/types/Tonal.java
index e6ef10b3..71baaf17 100644
--- a/com/android/internal/colorextraction/types/Tonal.java
+++ b/com/android/internal/colorextraction/types/Tonal.java
@@ -51,9 +51,11 @@ public class Tonal implements ExtractionType {
private static final boolean DEBUG = true;
+ public static final int THRESHOLD_COLOR_LIGHT = 0xffe0e0e0;
public static final int MAIN_COLOR_LIGHT = 0xffe0e0e0;
public static final int SECONDARY_COLOR_LIGHT = 0xff9e9e9e;
- public static final int MAIN_COLOR_DARK = 0xff212121;
+ public static final int THRESHOLD_COLOR_DARK = 0xff212121;
+ public static final int MAIN_COLOR_DARK = 0xff000000;
public static final int SECONDARY_COLOR_DARK = 0xff000000;
private final TonalPalette mGreyPalette;
@@ -197,12 +199,12 @@ public class Tonal implements ExtractionType {
// light fallback or darker than our dark fallback.
ColorUtils.colorToHSL(mainColor, mTmpHSL);
final float mainLuminosity = mTmpHSL[2];
- ColorUtils.colorToHSL(MAIN_COLOR_LIGHT, mTmpHSL);
+ ColorUtils.colorToHSL(THRESHOLD_COLOR_LIGHT, mTmpHSL);
final float lightLuminosity = mTmpHSL[2];
if (mainLuminosity > lightLuminosity) {
return false;
}
- ColorUtils.colorToHSL(MAIN_COLOR_DARK, mTmpHSL);
+ ColorUtils.colorToHSL(THRESHOLD_COLOR_DARK, mTmpHSL);
final float darkLuminosity = mTmpHSL[2];
if (mainLuminosity < darkLuminosity) {
return false;
diff --git a/com/android/internal/net/NetworkStatsFactory.java b/com/android/internal/net/NetworkStatsFactory.java
index 3d3e148f..5eda81ba 100644
--- a/com/android/internal/net/NetworkStatsFactory.java
+++ b/com/android/internal/net/NetworkStatsFactory.java
@@ -43,6 +43,8 @@ import java.util.Objects;
/**
* Creates {@link NetworkStats} instances by parsing various {@code /proc/}
* files as needed.
+ *
+ * @hide
*/
public class NetworkStatsFactory {
private static final String TAG = "NetworkStatsFactory";
diff --git a/com/android/internal/os/BaseCommand.java b/com/android/internal/os/BaseCommand.java
index 3baccee0..05ec9e90 100644
--- a/com/android/internal/os/BaseCommand.java
+++ b/com/android/internal/os/BaseCommand.java
@@ -106,6 +106,14 @@ public abstract class BaseCommand {
}
/**
+ * Peek the next argument on the command line, whatever it is; if there are
+ * no arguments left, return null.
+ */
+ public String peekNextArg() {
+ return mArgs.peekNextArg();
+ }
+
+ /**
* Return the next argument on the command line, whatever it is; if there are
* no arguments left, throws an IllegalArgumentException to report this to the user.
*/
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index f0d05da2..f2483c0a 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -3639,6 +3639,7 @@ public class BatteryStatsImpl extends BatteryStats {
public void addIsolatedUidLocked(int isolatedUid, int appUid) {
mIsolatedUids.put(isolatedUid, appUid);
+ StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
}
/**
@@ -3659,9 +3660,11 @@ public class BatteryStatsImpl extends BatteryStats {
* @see #scheduleRemoveIsolatedUidLocked(int, int)
*/
public void removeIsolatedUidLocked(int isolatedUid) {
- mIsolatedUids.delete(isolatedUid);
- mKernelUidCpuTimeReader.removeUid(isolatedUid);
- mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+ StatsLog.write(
+ StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+ mIsolatedUids.delete(isolatedUid);
+ mKernelUidCpuTimeReader.removeUid(isolatedUid);
+ mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
}
public int mapUid(int uid) {
@@ -5412,6 +5415,18 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ public String[] getWifiIfaces() {
+ synchronized (mWifiNetworkLock) {
+ return mWifiIfaces;
+ }
+ }
+
+ public String[] getMobileIfaces() {
+ synchronized (mModemNetworkLock) {
+ return mModemIfaces;
+ }
+ }
+
@Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -9614,7 +9629,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
public boolean isScreenOn(int state) {
- return state == Display.STATE_ON || state == Display.STATE_VR;
+ return state == Display.STATE_ON || state == Display.STATE_VR
+ || state == Display.STATE_ON_SUSPEND;
}
public boolean isScreenOff(int state) {
diff --git a/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index 8884d24f..a39997d3 100644
--- a/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.util.IntArray;
import android.util.Slog;
@@ -82,6 +83,7 @@ public class KernelUidCpuFreqTimeReader {
if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
return null;
}
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
mProcFileAvailable = true;
return readFreqs(reader, powerProfile);
@@ -89,6 +91,8 @@ public class KernelUidCpuFreqTimeReader {
mReadErrorCounter++;
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
return null;
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
}
}
@@ -106,12 +110,15 @@ public class KernelUidCpuFreqTimeReader {
if (!mProcFileAvailable) {
return;
}
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
mNowTimeMs = SystemClock.elapsedRealtime();
readDelta(reader, callback);
mLastTimeReadMs = mNowTimeMs;
} catch (IOException e) {
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
}
}
diff --git a/com/android/internal/os/KernelUidCpuTimeReader.java b/com/android/internal/os/KernelUidCpuTimeReader.java
index 37d9d1d4..65615c0f 100644
--- a/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
import android.annotation.Nullable;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Slog;
@@ -65,6 +66,7 @@ public class KernelUidCpuTimeReader {
* a fresh delta.
*/
public void readDelta(@Nullable Callback callback) {
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
long nowUs = SystemClock.elapsedRealtime() * 1000;
try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
@@ -121,6 +123,8 @@ public class KernelUidCpuTimeReader {
}
} catch (IOException e) {
Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
}
mLastTimeReadUs = nowUs;
}
@@ -160,12 +164,15 @@ public class KernelUidCpuTimeReader {
private void removeUidsFromKernelModule(int startUid, int endUid) {
Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
writer.write(startUid + "-" + endUid);
writer.flush();
} catch (IOException e) {
Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
+ " from uid_cputime module", e);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
}
}
}
diff --git a/com/android/internal/policy/DividerSnapAlgorithm.java b/com/android/internal/policy/DividerSnapAlgorithm.java
index fb6b8b0b..3af3e2ad 100644
--- a/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -16,6 +16,10 @@
package com.android.internal.policy;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -99,11 +103,12 @@ public class DividerSnapAlgorithm {
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isHorizontalDivision, Rect insets) {
- this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, false);
+ this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+ DOCKED_INVALID, false);
}
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
- boolean isHorizontalDivision, Rect insets, boolean isMinimizedMode) {
+ boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode) {
mMinFlingVelocityPxPerSecond =
MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
mMinDismissVelocityPxPerSecond =
@@ -121,7 +126,7 @@ public class DividerSnapAlgorithm {
com.android.internal.R.dimen.default_minimal_size_resizable_task);
mTaskHeightInMinimizedMode = res.getDimensionPixelSize(
com.android.internal.R.dimen.task_height_of_minimized_mode);
- calculateTargets(isHorizontalDivision);
+ calculateTargets(isHorizontalDivision, dockSide);
mFirstSplitTarget = mTargets.get(1);
mLastSplitTarget = mTargets.get(mTargets.size() - 2);
mDismissStartTarget = mTargets.get(0);
@@ -254,7 +259,7 @@ public class DividerSnapAlgorithm {
return mTargets.get(minIndex);
}
- private void calculateTargets(boolean isHorizontalDivision) {
+ private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
mTargets.clear();
int dividerMax = isHorizontalDivision
? mDisplayHeight
@@ -273,7 +278,7 @@ public class DividerSnapAlgorithm {
addMiddleTarget(isHorizontalDivision);
break;
case SNAP_MODE_MINIMIZED:
- addMinimizedTarget(isHorizontalDivision);
+ addMinimizedTarget(isHorizontalDivision, dockedSide);
break;
}
mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
@@ -331,12 +336,16 @@ public class DividerSnapAlgorithm {
mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
}
- private void addMinimizedTarget(boolean isHorizontalDivision) {
+ private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
// In portrait offset the position by the statusbar height, in landscape add the statusbar
// height as well to match portrait offset
int position = mTaskHeightInMinimizedMode + mInsets.top;
if (!isHorizontalDivision) {
- position += mInsets.left;
+ if (dockedSide == DOCKED_LEFT) {
+ position += mInsets.left;
+ } else if (dockedSide == DOCKED_RIGHT) {
+ position = mDisplayWidth - position - mInsets.right;
+ }
}
mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
}
diff --git a/com/android/internal/telephony/BaseCommands.java b/com/android/internal/telephony/BaseCommands.java
index 137b2a73..9304f910 100644
--- a/com/android/internal/telephony/BaseCommands.java
+++ b/com/android/internal/telephony/BaseCommands.java
@@ -47,7 +47,6 @@ public abstract class BaseCommands implements CommandsInterface {
protected RegistrantList mIccStatusChangedRegistrants = new RegistrantList();
protected RegistrantList mVoicePrivacyOnRegistrants = new RegistrantList();
protected RegistrantList mVoicePrivacyOffRegistrants = new RegistrantList();
- protected Registrant mUnsolOemHookRawRegistrant;
protected RegistrantList mOtaProvisionRegistrants = new RegistrantList();
protected RegistrantList mCallWaitingInfoRegistrants = new RegistrantList();
protected RegistrantList mDisplayInfoRegistrants = new RegistrantList();
@@ -584,17 +583,6 @@ public abstract class BaseCommands implements CommandsInterface {
mSignalInfoRegistrants.add(r);
}
- public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
- mUnsolOemHookRawRegistrant = new Registrant (h, what, obj);
- }
-
- public void unSetOnUnsolOemHookRaw(Handler h) {
- if (mUnsolOemHookRawRegistrant != null && mUnsolOemHookRawRegistrant.getHandler() == h) {
- mUnsolOemHookRawRegistrant.clear();
- mUnsolOemHookRawRegistrant = null;
- }
- }
-
@Override
public void unregisterForSignalInfo(Handler h) {
mSignalInfoRegistrants.remove(h);
diff --git a/com/android/internal/telephony/CommandsInterface.java b/com/android/internal/telephony/CommandsInterface.java
index f339693d..7026ff22 100644
--- a/com/android/internal/telephony/CommandsInterface.java
+++ b/com/android/internal/telephony/CommandsInterface.java
@@ -1450,8 +1450,6 @@ public interface CommandsInterface {
*/
void reportStkServiceIsRunning(Message result);
- void invokeOemRilRequestRaw(byte[] data, Message response);
-
/**
* Sends carrier specific information to the vendor ril that can be used to
* encrypt the IMSI and IMPI.
@@ -1464,14 +1462,6 @@ public interface CommandsInterface {
void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
Message response);
- void invokeOemRilRequestStrings(String[] strings, Message response);
-
- /**
- * Fires when RIL_UNSOL_OEM_HOOK_RAW is received from the RIL.
- */
- void setOnUnsolOemHookRaw(Handler h, int what, Object obj);
- void unSetOnUnsolOemHookRaw(Handler h);
-
/**
* Send TERMINAL RESPONSE to the SIM, after processing a proactive command
* sent by the SIM.
diff --git a/com/android/internal/telephony/DefaultPhoneNotifier.java b/com/android/internal/telephony/DefaultPhoneNotifier.java
index 98c0a32e..6a4dee7d 100644
--- a/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -309,15 +309,6 @@ public class DefaultPhoneNotifier implements PhoneNotifier {
}
}
- @Override
- public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
- try {
- mRegistry.notifyOemHookRawEventForSubscriber(subId, rawData);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
/**
* Convert the {@link Phone.DataActivityState} enum into the TelephonyManager.DATA_* constants
* for the public API.
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index ad078d67..47289e57 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -1406,8 +1406,11 @@ public class GsmCdmaPhone extends Phone {
if (!isPhoneTypeGsm() && TextUtils.isEmpty(number)) {
// Read platform settings for dynamic voicemail number
- if (getContext().getResources().getBoolean(com.android.internal
- .R.bool.config_telephony_use_own_number_for_voicemail)) {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ PersistableBundle b = configManager.getConfig();
+ if (b != null && b.getBoolean(
+ CarrierConfigManager.KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL)) {
number = getLine1Number();
} else {
number = "*86";
@@ -2236,14 +2239,14 @@ public class GsmCdmaPhone extends Phone {
int current_cdma_roaming_mode =
Settings.Global.getInt(getContext().getContentResolver(),
Settings.Global.CDMA_ROAMING_MODE,
- CarrierConfigManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
+ TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
switch (config_cdma_roaming_mode) {
// Carrier's cdma_roaming_mode will overwrite the user's previous settings
// Keep the user's previous setting in global variable which will be used
// when carrier's setting is turn off.
- case CarrierConfigManager.CDMA_ROAMING_MODE_HOME:
- case CarrierConfigManager.CDMA_ROAMING_MODE_AFFILIATED:
- case CarrierConfigManager.CDMA_ROAMING_MODE_ANY:
+ case TelephonyManager.CDMA_ROAMING_MODE_HOME:
+ case TelephonyManager.CDMA_ROAMING_MODE_AFFILIATED:
+ case TelephonyManager.CDMA_ROAMING_MODE_ANY:
logd("cdma_roaming_mode is going to changed to "
+ config_cdma_roaming_mode);
setCdmaRoamingPreference(config_cdma_roaming_mode,
@@ -2252,7 +2255,7 @@ public class GsmCdmaPhone extends Phone {
// When carrier's setting is turn off, change the cdma_roaming_mode to the
// previous user's setting
- case CarrierConfigManager.CDMA_ROAMING_MODE_RADIO_DEFAULT:
+ case TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT:
if (current_cdma_roaming_mode != config_cdma_roaming_mode) {
logd("cdma_roaming_mode is going to changed to "
+ current_cdma_roaming_mode);
diff --git a/com/android/internal/telephony/IccSmsInterfaceManager.java b/com/android/internal/telephony/IccSmsInterfaceManager.java
index 0fc08c65..997ccea3 100644
--- a/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -78,6 +78,8 @@ public class IccSmsInterfaceManager {
protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
private static final int SMS_CB_CODE_SCHEME_MIN = 0;
private static final int SMS_CB_CODE_SCHEME_MAX = 255;
+ public static final int SMS_MESSAGE_PRIORITY_NOT_SPECIFIED = -1;
+ public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
protected Phone mPhone;
final protected Context mContext;
@@ -393,7 +395,8 @@ public class IccSmsInterfaceManager {
Manifest.permission.SEND_SMS,
"Sending SMS message");
sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
- persistMessageForNonDefaultSmsApp);
+ persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+ false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
}
/**
@@ -407,7 +410,8 @@ public class IccSmsInterfaceManager {
Manifest.permission.SEND_SMS,
"Sending SMS message");
sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
- persistMessage);
+ persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
+ SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
}
/**
@@ -433,15 +437,39 @@ public class IccSmsInterfaceManager {
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
* raw pdu of the status report is in the extended data ("pdu").
+ * @param persistMessageForNonDefaultSmsApp whether the sent message should
+ * be automatically persisted in the SMS db. It only affects messages sent
+ * by a non-default SMS app. Currently only the carrier app can set this
+ * parameter to false to skip auto message persistence.
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values including negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values including negative considered as Invalid Validity Period of the message.
*/
private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
- boolean persistMessageForNonDefaultSmsApp) {
+ boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
+ int validityPeriod) {
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
" text='"+ text + "' sentIntent=" +
- sentIntent + " deliveryIntent=" + deliveryIntent);
+ sentIntent + " deliveryIntent=" + deliveryIntent
+ + " priority=" + priority + " expectMore=" + expectMore
+ + " validityPeriod=" + validityPeriod);
}
if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
callingPackage) != AppOpsManager.MODE_ALLOWED) {
@@ -452,7 +480,65 @@ public class IccSmsInterfaceManager {
}
destAddr = filterDestAddress(destAddr);
mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
- null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
+ null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
+ priority, expectMore, validityPeriod);
+ }
+
+ /**
+ * Send a text based SMS with Messaging Options.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr is the service center address or null to use
+ * the current default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param persistMessageForNonDefaultSmsApp whether the sent message should
+ * be automatically persisted in the SMS db. It only affects messages sent
+ * by a non-default SMS app. Currently only the carrier app can set this
+ * parameter to false to skip auto message persistence.
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values including negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values including negative considered as Invalid Validity Period of the message.
+ */
+
+ public void sendTextWithOptions(String callingPackage, String destAddr, String scAddr,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+ boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
+ int validityPeriod) {
+ mPhone.getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.SEND_SMS,
+ "Sending SMS message");
+ sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
+ persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod);
}
/**
@@ -504,6 +590,63 @@ public class IccSmsInterfaceManager {
public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
List<String> parts, List<PendingIntent> sentIntents,
List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) {
+ sendMultipartTextWithOptions(callingPackage, destAddr, destAddr, parts, sentIntents,
+ deliveryIntents, persistMessageForNonDefaultSmsApp,
+ SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
+ SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
+ }
+
+ /**
+ * Send a multi-part text based SMS with Messaging Options.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ * @param persistMessageForNonDefaultSmsApp whether the sent message should
+ * be automatically persisted in the SMS db. It only affects messages sent
+ * by a non-default SMS app. Currently only the carrier app can set this
+ * parameter to false to skip auto message persistence.
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values including negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values including negative considered as Invalid Validity Period of the message.
+ */
+
+ public void sendMultipartTextWithOptions(String callingPackage, String destAddr,
+ String scAddr, List<String> parts, List<PendingIntent> sentIntents,
+ List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
+ int priority, boolean expectMore, int validityPeriod) {
mPhone.getContext().enforceCallingPermission(
Manifest.permission.SEND_SMS,
"Sending SMS message");
@@ -514,7 +657,7 @@ public class IccSmsInterfaceManager {
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
int i = 0;
for (String part : parts) {
- log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr +
+ log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr +
", part[" + (i++) + "]=" + part);
}
}
@@ -549,17 +692,21 @@ public class IccSmsInterfaceManager {
mDispatcher.sendText(destAddr, scAddr, singlePart,
singleSentIntent, singleDeliveryIntent,
null/*messageUri*/, callingPackage,
- persistMessageForNonDefaultSmsApp);
+ persistMessageForNonDefaultSmsApp,
+ priority, expectMore, validityPeriod);
}
return;
}
- mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
- (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents,
- null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
+ mDispatcher.sendMultipartText(destAddr,
+ scAddr,
+ (ArrayList<String>) parts,
+ (ArrayList<PendingIntent>) sentIntents,
+ (ArrayList<PendingIntent>) deliveryIntents,
+ null, callingPackage, persistMessageForNonDefaultSmsApp,
+ priority, expectMore, validityPeriod);
}
-
public int getPremiumSmsPermission(String packageName) {
return mDispatcher.getPremiumSmsPermission(packageName);
}
@@ -953,7 +1100,8 @@ public class IccSmsInterfaceManager {
textAndAddress[1] = filterDestAddress(textAndAddress[1]);
mDispatcher.sendText(textAndAddress[1], scAddress, textAndAddress[0],
sentIntent, deliveryIntent, messageUri, callingPkg,
- true /* persistMessageForNonDefaultSmsApp */);
+ true /* persistMessageForNonDefaultSmsApp */, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+ false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
}
public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
@@ -1009,7 +1157,9 @@ public class IccSmsInterfaceManager {
mDispatcher.sendText(textAndAddress[1], scAddress, singlePart,
singleSentIntent, singleDeliveryIntent, messageUri, callingPkg,
- true /* persistMessageForNonDefaultSmsApp */);
+ true /* persistMessageForNonDefaultSmsApp */,
+ SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+ false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
}
return;
}
@@ -1022,7 +1172,10 @@ public class IccSmsInterfaceManager {
(ArrayList<PendingIntent>) deliveryIntents,
messageUri,
callingPkg,
- true /* persistMessageForNonDefaultSmsApp */);
+ true /* persistMessageForNonDefaultSmsApp */,
+ SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+ false /* expectMore */,
+ SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
}
private boolean isFailedOrDraft(ContentResolver resolver, Uri messageUri) {
diff --git a/com/android/internal/telephony/ImsSMSDispatcher.java b/com/android/internal/telephony/ImsSMSDispatcher.java
index 4d8f62c9..bc829d61 100644
--- a/com/android/internal/telephony/ImsSMSDispatcher.java
+++ b/com/android/internal/telephony/ImsSMSDispatcher.java
@@ -173,13 +173,15 @@ public class ImsSMSDispatcher extends SMSDispatcher {
public void sendMultipartText(String destAddr, String scAddr,
ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
- boolean persistMessage) {
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
if (isCdmaMo()) {
mCdmaDispatcher.sendMultipartText(destAddr, scAddr,
- parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage);
+ parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage,
+ priority, expectMore, validityPeriod);
} else {
mGsmDispatcher.sendMultipartText(destAddr, scAddr,
- parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage);
+ parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage,
+ priority, expectMore, validityPeriod);
}
}
@@ -199,14 +201,16 @@ public class ImsSMSDispatcher extends SMSDispatcher {
@Override
public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
- boolean persistMessage) {
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
Rlog.d(TAG, "sendText");
if (isCdmaMo()) {
mCdmaDispatcher.sendText(destAddr, scAddr,
- text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage);
+ text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
+ priority, expectMore, validityPeriod);
} else {
mGsmDispatcher.sendText(destAddr, scAddr,
- text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage);
+ text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
+ priority, expectMore, validityPeriod);
}
}
@@ -365,7 +369,7 @@ public class ImsSMSDispatcher extends SMSDispatcher {
String message, SmsHeader smsHeader, int format, PendingIntent sentIntent,
PendingIntent deliveryIntent, boolean lastPart,
AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
- String fullMessageText) {
+ String fullMessageText, int priority, boolean expectMore, int validityPeriod) {
Rlog.e(TAG, "Error! Not implemented for IMS.");
return null;
}
diff --git a/com/android/internal/telephony/InboundSmsHandler.java b/com/android/internal/telephony/InboundSmsHandler.java
index 391de500..2d663cd7 100644
--- a/com/android/internal/telephony/InboundSmsHandler.java
+++ b/com/android/internal/telephony/InboundSmsHandler.java
@@ -165,9 +165,9 @@ public abstract class InboundSmsHandler extends StateMachine {
* state */
private static final int EVENT_STATE_TIMEOUT = 10;
- /** Timeout duration for EVENT_STATE_TIMEOUT */
+ /** Timeout duration for EVENT_STATE_TIMEOUT (5 minutes) */
@VisibleForTesting
- public static final int STATE_TIMEOUT = 30000;
+ public static final int STATE_TIMEOUT = 5 * 60 * 1000;
/** Wakelock release delay when returning to idle state. */
private static final int WAKELOCK_TIMEOUT = 3000;
diff --git a/com/android/internal/telephony/MccTable.java b/com/android/internal/telephony/MccTable.java
index 5714b298..3220078b 100644
--- a/com/android/internal/telephony/MccTable.java
+++ b/com/android/internal/telephony/MccTable.java
@@ -32,7 +32,7 @@ import com.android.internal.app.LocaleStore;
import com.android.internal.app.LocaleStore.LocaleInfo;
import libcore.icu.ICU;
-import libcore.icu.TimeZoneNames;
+import libcore.util.TimeZoneFinder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -94,24 +94,8 @@ public final class MccTable {
if (entry == null) {
return null;
}
- Locale locale = new Locale("", entry.mIso);
- String[] tz = TimeZoneNames.forLocale(locale);
- if (tz.length == 0) return null;
-
- String zoneName = tz[0];
-
- /* Use Australia/Sydney instead of Australia/Lord_Howe for Australia.
- * http://b/33228250
- * Todo: remove the code, see b/62418027
- */
- if (mcc == 505 /* Australia / Norfolk Island */) {
- for (String zone : tz) {
- if (zone.contains("Sydney")) {
- zoneName = zone;
- }
- }
- }
- return zoneName;
+ final String lowerCaseCountryCode = entry.mIso;
+ return TimeZoneFinder.getInstance().lookupDefaultTimeZoneIdByCountry(lowerCaseCountryCode);
}
/**
@@ -434,7 +418,7 @@ public final class MccTable {
String country = MccTable.countryCodeForMcc(mcc);
Slog.d(LOG_TAG, "WIFI_COUNTRY_CODE set to " + country);
WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- wM.setCountryCode(country, false);
+ wM.setCountryCode(country);
}
static {
diff --git a/com/android/internal/telephony/NitzData.java b/com/android/internal/telephony/NitzData.java
new file mode 100644
index 00000000..6775639d
--- /dev/null
+++ b/com/android/internal/telephony/NitzData.java
@@ -0,0 +1,228 @@
+/*
+ * 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 static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.telephony.Rlog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Represents NITZ data. Various static methods are provided to help with parsing and intepretation
+ * of NITZ data.
+ *
+ * {@hide}
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class NitzData {
+ private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
+ private static final int MS_PER_HOUR = 60 * 60 * 1000;
+ private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000;
+
+ /* Time stamp after 19 January 2038 is not supported under 32 bit */
+ private static final int MAX_NITZ_YEAR = 2037;
+
+ // Stored For logging / debugging only.
+ private final String mOriginalString;
+
+ private final int mZoneOffset;
+
+ private final Integer mDstOffset;
+
+ private final long mCurrentTimeMillis;
+
+ private final TimeZone mEmulatorHostTimeZone;
+
+ private NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis,
+ long utcTimeMillis, TimeZone timeZone) {
+ this.mOriginalString = originalString;
+ this.mZoneOffset = zoneOffsetMillis;
+ this.mDstOffset = dstOffsetMillis;
+ this.mCurrentTimeMillis = utcTimeMillis;
+ this.mEmulatorHostTimeZone = timeZone;
+ }
+
+ /**
+ * Parses the supplied NITZ string, returning the encoded data.
+ */
+ public static NitzData parse(String nitz) {
+ // "yy/mm/dd,hh:mm:ss(+/-)tz[,dt[,tzid]]"
+ // tz, dt are in number of quarter-hours
+
+ try {
+ /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
+ * offset as well (which we won't worry about until later) */
+ Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ c.clear();
+ c.set(Calendar.DST_OFFSET, 0);
+
+ String[] nitzSubs = nitz.split("[/:,+-]");
+
+ int year = 2000 + Integer.parseInt(nitzSubs[0]);
+ if (year > MAX_NITZ_YEAR) {
+ if (ServiceStateTracker.DBG) {
+ Rlog.e(LOG_TAG, "NITZ year: " + year + " exceeds limit, skip NITZ time update");
+ }
+ return null;
+ }
+ c.set(Calendar.YEAR, year);
+
+ // month is 0 based!
+ int month = Integer.parseInt(nitzSubs[1]) - 1;
+ c.set(Calendar.MONTH, month);
+
+ int date = Integer.parseInt(nitzSubs[2]);
+ c.set(Calendar.DATE, date);
+
+ int hour = Integer.parseInt(nitzSubs[3]);
+ c.set(Calendar.HOUR, hour);
+
+ int minute = Integer.parseInt(nitzSubs[4]);
+ c.set(Calendar.MINUTE, minute);
+
+ int second = Integer.parseInt(nitzSubs[5]);
+ c.set(Calendar.SECOND, second);
+
+ // The offset received from NITZ is the offset to add to get current local time.
+ boolean sign = (nitz.indexOf('-') == -1);
+ int totalUtcOffsetQuarterHours = Integer.parseInt(nitzSubs[6]);
+ int totalUtcOffsetMillis =
+ (sign ? 1 : -1) * totalUtcOffsetQuarterHours * MS_PER_QUARTER_HOUR;
+
+ // DST correction is already applied to the UTC offset. We could subtract it if we
+ // wanted the raw offset.
+ Integer dstAdjustmentQuarterHours =
+ (nitzSubs.length >= 8) ? Integer.parseInt(nitzSubs[7]) : null;
+ Integer dstAdjustmentMillis = null;
+ if (dstAdjustmentQuarterHours != null) {
+ dstAdjustmentMillis = dstAdjustmentQuarterHours * MS_PER_QUARTER_HOUR;
+ }
+
+ // As a special extension, the Android emulator appends the name of
+ // the host computer's timezone to the nitz string. this is zoneinfo
+ // timezone name of the form Area!Location or Area!Location!SubLocation
+ // so we need to convert the ! into /
+ TimeZone zone = null;
+ if (nitzSubs.length >= 9) {
+ String tzname = nitzSubs[8].replace('!', '/');
+ zone = TimeZone.getTimeZone(tzname);
+ }
+ return new NitzData(nitz, totalUtcOffsetMillis, dstAdjustmentMillis,
+ c.getTimeInMillis(), zone);
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz + " ex=" + ex);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the current time as the number of milliseconds since the beginning of the Unix epoch
+ * (1/1/1970 00:00:00 UTC).
+ */
+ public long getCurrentTimeInMillis() {
+ return mCurrentTimeMillis;
+ }
+
+ /**
+ * Returns the total offset to apply to the {@link #getCurrentTimeInMillis()} to arrive at a
+ * local time.
+ */
+ public int getLocalOffsetMillis() {
+ return mZoneOffset;
+ }
+
+ /**
+ * Returns the offset (already included in {@link #getLocalOffsetMillis()}) associated with
+ * Daylight Savings Time (DST). This field is optional: {@code null} means the DST offset is
+ * unknown.
+ */
+ public Integer getDstAdjustmentMillis() {
+ return mDstOffset;
+ }
+
+ /**
+ * Returns {@link true} if the time is in Daylight Savings Time (DST), {@link false} if it is
+ * unknown or not in DST. See {@link #getDstAdjustmentMillis()}.
+ */
+ public boolean isDst() {
+ return mDstOffset != null && mDstOffset != 0;
+ }
+
+
+ /**
+ * Returns the time zone of the host computer when Android is running in an emulator. It is
+ * {@code null} for real devices. This information is communicated via a non-standard Android
+ * extension to NITZ.
+ */
+ public TimeZone getEmulatorHostTimeZone() {
+ return mEmulatorHostTimeZone;
+ }
+
+ /**
+ * Using information present in the supplied {@link NitzData} object, guess the time zone.
+ * Because multiple time zones can have the same offset / DST state at a given time this process
+ * is error prone; an arbitrary match is returned when there are multiple candidates. The
+ * algorithm can also return a non-exact match by assuming that the DST information provided by
+ * NITZ is incorrect. This method can return {@code null} if no time zones are found.
+ */
+ public static TimeZone guessTimeZone(NitzData nitzData) {
+ int offset = nitzData.getLocalOffsetMillis();
+ boolean dst = nitzData.isDst();
+ long when = nitzData.getCurrentTimeInMillis();
+ TimeZone guess = findTimeZone(offset, dst, when);
+ if (guess == null) {
+ // Couldn't find a proper timezone. Perhaps the DST data is wrong.
+ guess = findTimeZone(offset, !dst, when);
+ }
+ return guess;
+ }
+
+ private static TimeZone findTimeZone(int offset, boolean dst, long when) {
+ int rawOffset = offset;
+ if (dst) {
+ rawOffset -= MS_PER_HOUR;
+ }
+ String[] zones = TimeZone.getAvailableIDs(rawOffset);
+ TimeZone guess = null;
+ Date d = new Date(when);
+ for (String zone : zones) {
+ TimeZone tz = TimeZone.getTimeZone(zone);
+ if (tz.getOffset(when) == offset && tz.inDaylightTime(d) == dst) {
+ guess = tz;
+ break;
+ }
+ }
+
+ return guess;
+ }
+
+ @Override
+ public String toString() {
+ return "NitzData{"
+ + "mOriginalString=" + mOriginalString
+ + ", mZoneOffset=" + mZoneOffset
+ + ", mDstOffset=" + mDstOffset
+ + ", mCurrentTimeMillis=" + mCurrentTimeMillis
+ + ", mEmulatorHostTimeZone=" + mEmulatorHostTimeZone
+ + '}';
+ }
+}
diff --git a/com/android/internal/telephony/OemHookIndication.java b/com/android/internal/telephony/OemHookIndication.java
deleted file mode 100644
index 122a70e8..00000000
--- a/com/android/internal/telephony/OemHookIndication.java
+++ /dev/null
@@ -1,53 +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.telephony;
-
-import android.hardware.radio.deprecated.V1_0.IOemHookIndication;
-import android.os.AsyncResult;
-
-import java.util.ArrayList;
-
-import static com.android.internal.telephony.RILConstants.RIL_UNSOL_OEM_HOOK_RAW;
-
-/**
- * Class containing oem hook indication callbacks
- */
-public class OemHookIndication extends IOemHookIndication.Stub {
- RIL mRil;
-
- public OemHookIndication(RIL ril) {
- mRil = ril;
- }
-
- /**
- * @param indicationType RadioIndicationType
- * @param data Data sent by oem
- */
- public void oemHookRaw(int indicationType, ArrayList<Byte> data) {
- mRil.processIndication(indicationType);
-
- byte[] response = RIL.arrayListToPrimitiveArray(data);
- if (RIL.RILJ_LOGD) {
- mRil.unsljLogvRet(RIL_UNSOL_OEM_HOOK_RAW,
- com.android.internal.telephony.uicc.IccUtils.bytesToHexString(response));
- }
-
- if (mRil.mUnsolOemHookRawRegistrant != null) {
- mRil.mUnsolOemHookRawRegistrant.notifyRegistrant(new AsyncResult(null, response, null));
- }
- }
-}
diff --git a/com/android/internal/telephony/OemHookResponse.java b/com/android/internal/telephony/OemHookResponse.java
deleted file mode 100644
index 0afeac8d..00000000
--- a/com/android/internal/telephony/OemHookResponse.java
+++ /dev/null
@@ -1,59 +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.telephony;
-
-import android.hardware.radio.deprecated.V1_0.IOemHookResponse;
-import android.hardware.radio.V1_0.RadioError;
-import android.hardware.radio.V1_0.RadioResponseInfo;
-
-import java.util.ArrayList;
-
-/**
- * Class containing oem hook response callbacks
- */
-public class OemHookResponse extends IOemHookResponse.Stub {
- RIL mRil;
-
- public OemHookResponse(RIL ril) {
- mRil = ril;
- }
-
- /**
- * @param responseInfo Response info struct containing response type, serial no. and error
- * @param data Data returned by oem
- */
- public void sendRequestRawResponse(RadioResponseInfo responseInfo, ArrayList<Byte> data) {
- RILRequest rr = mRil.processResponse(responseInfo);
-
- if (rr != null) {
- byte[] ret = null;
- if (responseInfo.error == RadioError.NONE) {
- ret = RIL.arrayListToPrimitiveArray(data);
- RadioResponse.sendMessageResponse(rr.mResult, ret);
- }
- mRil.processResponseDone(rr, responseInfo, ret);
- }
- }
-
- /**
- * @param responseInfo Response info struct containing response type, serial no. and error
- * @param data Data returned by oem
- */
- public void sendRequestStringsResponse(RadioResponseInfo responseInfo, ArrayList<String> data) {
- RadioResponse.responseStringArrayList(mRil, responseInfo, data);
- }
-}
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index cf520973..c51a5fe4 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -188,7 +188,6 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
private static final int EVENT_SRVCC_STATE_CHANGED = 31;
private static final int EVENT_INITIATE_SILENT_REDIAL = 32;
private static final int EVENT_RADIO_NOT_AVAILABLE = 33;
- private static final int EVENT_UNSOL_OEM_HOOK_RAW = 34;
protected static final int EVENT_GET_RADIO_CAPABILITY = 35;
protected static final int EVENT_SS = 36;
private static final int EVENT_CONFIG_LCE = 37;
@@ -527,7 +526,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
// note this is not persisting
WifiManager wM = (WifiManager)
mContext.getSystemService(Context.WIFI_SERVICE);
- wM.setCountryCode(country, false);
+ wM.setCountryCode(country);
}
}
@@ -541,7 +540,6 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
}
- mCi.setOnUnsolOemHookRaw(this, EVENT_UNSOL_OEM_HOOK_RAW, null);
mCi.startLceService(DEFAULT_REPORT_INTERVAL_MS, LCE_PULL_MODE,
obtainMessage(EVENT_CONFIG_LCE));
}
@@ -677,16 +675,6 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
}
break;
- case EVENT_UNSOL_OEM_HOOK_RAW:
- ar = (AsyncResult)msg.obj;
- if (ar.exception == null) {
- byte[] data = (byte[])ar.result;
- mNotifier.notifyOemHookRawEventForSubscriber(getSubId(), data);
- } else {
- Rlog.e(LOG_TAG, "OEM hook raw exception: " + ar.exception);
- }
- break;
-
case EVENT_CONFIG_LCE:
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
@@ -2050,45 +2038,6 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
}
/**
- * Invokes RIL_REQUEST_OEM_HOOK_RAW on RIL implementation.
- *
- * @param data The data for the request.
- * @param response <strong>On success</strong>,
- * (byte[])(((AsyncResult)response.obj).result)
- * <strong>On failure</strong>,
- * (((AsyncResult)response.obj).result) == null and
- * (((AsyncResult)response.obj).exception) being an instance of
- * com.android.internal.telephony.gsm.CommandException
- *
- * @see #invokeOemRilRequestRaw(byte[], android.os.Message)
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public void invokeOemRilRequestRaw(byte[] data, Message response) {
- mCi.invokeOemRilRequestRaw(data, response);
- }
-
- /**
- * Invokes RIL_REQUEST_OEM_HOOK_Strings on RIL implementation.
- *
- * @param strings The strings to make available as the request data.
- * @param response <strong>On success</strong>, "response" bytes is
- * made available as:
- * (String[])(((AsyncResult)response.obj).result).
- * <strong>On failure</strong>,
- * (((AsyncResult)response.obj).result) == null and
- * (((AsyncResult)response.obj).exception) being an instance of
- * com.android.internal.telephony.gsm.CommandException
- *
- * @see #invokeOemRilRequestStrings(java.lang.String[], android.os.Message)
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public void invokeOemRilRequestStrings(String[] strings, Message response) {
- mCi.invokeOemRilRequestStrings(strings, response);
- }
-
- /**
* Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
* Used for device configuration by some CDMA operators.
*
diff --git a/com/android/internal/telephony/PhoneInternalInterface.java b/com/android/internal/telephony/PhoneInternalInterface.java
index 7dbd8d34..4c39a654 100644
--- a/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/com/android/internal/telephony/PhoneInternalInterface.java
@@ -21,13 +21,13 @@ import android.os.Handler;
import android.os.Message;
import android.os.ResultReceiver;
import android.os.WorkSource;
-import android.telephony.CarrierConfigManager;
import android.telephony.CellLocation;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
-import com.android.internal.telephony.PhoneConstants.*; // ????
+import com.android.internal.telephony.PhoneConstants.DataState;
import java.util.List;
@@ -165,11 +165,11 @@ public interface PhoneInternalInterface {
// Used for CDMA roaming mode
// Home Networks only, as defined in PRL
- static final int CDMA_RM_HOME = CarrierConfigManager.CDMA_ROAMING_MODE_HOME;
+ int CDMA_RM_HOME = TelephonyManager.CDMA_ROAMING_MODE_HOME;
// Roaming an Affiliated networks, as defined in PRL
- static final int CDMA_RM_AFFILIATED = CarrierConfigManager.CDMA_ROAMING_MODE_AFFILIATED;
+ int CDMA_RM_AFFILIATED = TelephonyManager.CDMA_ROAMING_MODE_AFFILIATED;
// Roaming on Any Network, as defined in PRL
- static final int CDMA_RM_ANY = CarrierConfigManager.CDMA_ROAMING_MODE_ANY;
+ int CDMA_RM_ANY = TelephonyManager.CDMA_ROAMING_MODE_ANY;
// Used for CDMA subscription mode
static final int CDMA_SUBSCRIPTION_UNKNOWN =-1; // Unknown
diff --git a/com/android/internal/telephony/PhoneNotifier.java b/com/android/internal/telephony/PhoneNotifier.java
index 2caf5e23..3b29a060 100644
--- a/com/android/internal/telephony/PhoneNotifier.java
+++ b/com/android/internal/telephony/PhoneNotifier.java
@@ -62,6 +62,4 @@ public interface PhoneNotifier {
public void notifyVoiceActivationStateChanged(Phone sender, int activationState);
public void notifyDataActivationStateChanged(Phone sender, int activationState);
-
- public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData);
}
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 9007f14d..3ddbf121 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -52,7 +52,6 @@ import android.hardware.radio.V1_0.SetupDataCallResult;
import android.hardware.radio.V1_0.SimApdu;
import android.hardware.radio.V1_0.SmsWriteArgs;
import android.hardware.radio.V1_0.UusInfo;
-import android.hardware.radio.deprecated.V1_0.IOemHook;
import android.net.ConnectivityManager;
import android.os.AsyncResult;
import android.os.Handler;
@@ -174,9 +173,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
RadioResponse mRadioResponse;
RadioIndication mRadioIndication;
volatile IRadio mRadioProxy = null;
- OemHookResponse mOemHookResponse;
- OemHookIndication mOemHookIndication;
- volatile IOemHook mOemHookProxy = null;
final AtomicLong mRadioProxyCookie = new AtomicLong(0);
final RadioProxyDeathRecipient mRadioProxyDeathRecipient;
final RilHandler mRilHandler;
@@ -280,7 +276,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
// todo: rild should be back up since message was sent with a delay. this is
// a hack.
getRadioProxy(null);
- getOemHookProxy(null);
}
break;
}
@@ -325,7 +320,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
private void resetProxyAndRequestList() {
mRadioProxy = null;
- mOemHookProxy = null;
// increment the cookie so that death notification can be ignored
mRadioProxyCookie.incrementAndGet();
@@ -389,55 +383,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
return mRadioProxy;
}
- /** Returns an {@link IOemHook} instance or null if the service is not available. */
- @VisibleForTesting
- public IOemHook getOemHookProxy(Message result) {
- if (!mIsMobileNetworkSupported) {
- if (RILJ_LOGV) riljLog("getOemHookProxy: Not calling getService(): wifi-only");
- if (result != null) {
- AsyncResult.forMessage(result, null,
- CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
- result.sendToTarget();
- }
- return null;
- }
-
- if (mOemHookProxy != null) {
- return mOemHookProxy;
- }
-
- try {
- mOemHookProxy = IOemHook.getService(
- HIDL_SERVICE_NAME[mPhoneId == null ? 0 : mPhoneId]);
- if (mOemHookProxy != null) {
- // not calling linkToDeath() as ril service runs in the same process and death
- // notification for that should be sufficient
- mOemHookProxy.setResponseFunctions(mOemHookResponse, mOemHookIndication);
- } else {
- riljLoge("getOemHookProxy: mOemHookProxy == null");
- }
- } catch (RemoteException | RuntimeException e) {
- mOemHookProxy = null;
- riljLoge("OemHookProxy getService/setResponseFunctions: " + e);
- }
-
- if (mOemHookProxy == null) {
- if (result != null) {
- AsyncResult.forMessage(result, null,
- CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
- result.sendToTarget();
- }
-
- // if service is not up, treat it like death notification to try to get service again
- mRilHandler.sendMessageDelayed(
- mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD,
- mRadioProxyCookie.incrementAndGet()),
- IRADIO_GET_SERVICE_DELAY_MILLIS);
- }
-
- return mOemHookProxy;
- }
-
//***** Constructors
public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {
@@ -464,8 +409,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
mRadioResponse = new RadioResponse(this);
mRadioIndication = new RadioIndication(this);
- mOemHookResponse = new OemHookResponse(this);
- mOemHookIndication = new OemHookIndication(this);
mRilHandler = new RilHandler();
mRadioProxyDeathRecipient = new RadioProxyDeathRecipient();
@@ -488,7 +431,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
// set radio callback; needed to set RadioIndication callback (should be done after
// wakelock stuff is initialized above as callbacks are received on separate binder threads)
getRadioProxy(null);
- getOemHookProxy(null);
}
@Override
@@ -1953,51 +1895,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
}
@Override
- public void invokeOemRilRequestRaw(byte[] data, Message response) {
- IOemHook oemHookProxy = getOemHookProxy(response);
- if (oemHookProxy != null) {
- RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_RAW, response,
- mRILDefaultWorkSource);
-
- if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
- + "[" + IccUtils.bytesToHexString(data) + "]");
- }
-
- try {
- oemHookProxy.sendRequestRaw(rr.mSerial, primitiveArrayToArrayList(data));
- } catch (RemoteException | RuntimeException e) {
- handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestRaw", e);
- }
- }
- }
-
- @Override
- public void invokeOemRilRequestStrings(String[] strings, Message result) {
- IOemHook oemHookProxy = getOemHookProxy(result);
- if (oemHookProxy != null) {
- RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_STRINGS, result,
- mRILDefaultWorkSource);
-
- String logStr = "";
- for (int i = 0; i < strings.length; i++) {
- logStr = logStr + strings[i] + " ";
- }
- if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " strings = "
- + logStr);
- }
-
- try {
- oemHookProxy.sendRequestStrings(rr.mSerial,
- new ArrayList<String>(Arrays.asList(strings)));
- } catch (RemoteException | RuntimeException e) {
- handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestStrings", e);
- }
- }
- }
-
- @Override
public void setSuppServiceNotifications(boolean enable, Message result) {
IRadio radioProxy = getRadioProxy(result);
if (radioProxy != null) {
@@ -4480,10 +4377,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
return "DATA_CALL_LIST";
case RIL_REQUEST_RESET_RADIO:
return "RESET_RADIO";
- case RIL_REQUEST_OEM_HOOK_RAW:
- return "OEM_HOOK_RAW";
- case RIL_REQUEST_OEM_HOOK_STRINGS:
- return "OEM_HOOK_STRINGS";
case RIL_REQUEST_SCREEN_STATE:
return "SCREEN_STATE";
case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION:
@@ -4709,8 +4602,6 @@ public class RIL extends BaseCommands implements CommandsInterface {
return "UNSOL_CDMA_OTA_PROVISION_STATUS";
case RIL_UNSOL_CDMA_INFO_REC:
return "UNSOL_CDMA_INFO_REC";
- case RIL_UNSOL_OEM_HOOK_RAW:
- return "UNSOL_OEM_HOOK_RAW";
case RIL_UNSOL_RINGBACK_TONE:
return "UNSOL_RINGBACK_TONE";
case RIL_UNSOL_RESEND_INCALL_MUTE:
diff --git a/com/android/internal/telephony/RadioResponse.java b/com/android/internal/telephony/RadioResponse.java
index caf44774..3990d241 100644
--- a/com/android/internal/telephony/RadioResponse.java
+++ b/com/android/internal/telephony/RadioResponse.java
@@ -576,9 +576,6 @@ public class RadioResponse extends IRadioResponse.Stub {
responseDataCallList(responseInfo, dataCallResultList);
}
- public void sendOemRilRequestRawResponse(RadioResponseInfo responseInfo,
- ArrayList<Byte> var2) {}
-
/**
* @param responseInfo Response info struct containing response type, serial no. and error
*/
diff --git a/com/android/internal/telephony/SMSDispatcher.java b/com/android/internal/telephony/SMSDispatcher.java
index 2ec51010..dc08ec02 100644
--- a/com/android/internal/telephony/SMSDispatcher.java
+++ b/com/android/internal/telephony/SMSDispatcher.java
@@ -17,6 +17,8 @@
package com.android.internal.telephony;
import static android.Manifest.permission.SEND_SMS_NO_CONFIRMATION;
+import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
+import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
@@ -797,10 +799,29 @@ public abstract class SMSDispatcher extends Handler {
* @param callingPkg the calling package name
* @param persistMessage whether to save the sent message into SMS DB for a
* non-default SMS app.
+ *
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
*/
protected abstract void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
- String callingPkg, boolean persistMessage);
+ String callingPkg, boolean persistMessage, int priority, boolean expectMore,
+ int validityPeriod);
/**
* Inject an SMS PDU into the android platform.
@@ -852,11 +873,28 @@ public abstract class SMSDispatcher extends Handler {
* @param callingPkg the calling package name
* @param persistMessage whether to save the sent message into SMS DB for a
* non-default SMS app.
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
*/
protected void sendMultipartText(String destAddr, String scAddr,
ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
- boolean persistMessage) {
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
final String fullMessageText = getMultipartMessageText(parts);
int refNumber = getNextConcatenatedRef() & 0x00FF;
int msgCount = parts.size();
@@ -913,7 +951,8 @@ public abstract class SMSDispatcher extends Handler {
trackers[i] =
getNewSubmitPduTracker(destAddr, scAddr, parts.get(i), smsHeader, encoding,
sentIntent, deliveryIntent, (i == (msgCount - 1)),
- unsentPartCount, anyPartFailed, messageUri, fullMessageText);
+ unsentPartCount, anyPartFailed, messageUri,
+ fullMessageText, priority, expectMore, validityPeriod);
trackers[i].mPersistMessage = persistMessage;
}
@@ -943,11 +982,12 @@ public abstract class SMSDispatcher extends Handler {
/**
* Create a new SubmitPdu and return the SMS tracker.
*/
- protected abstract SmsTracker getNewSubmitPduTracker(String destinationAddress, String scAddress,
- String message, SmsHeader smsHeader, int encoding,
+ protected abstract SmsTracker getNewSubmitPduTracker(String destinationAddress,
+ String scAddress, String message, SmsHeader smsHeader, int encoding,
PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
- AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
- String fullMessageText);
+ AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed,
+ Uri messageUri, String fullMessageText,
+ int priority, boolean expectMore, int validityPeriod);
/**
* Send an SMS
@@ -1320,7 +1360,8 @@ public abstract class SMSDispatcher extends Handler {
}
sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents,
- null/*messageUri*/, null/*callingPkg*/, tracker.mPersistMessage);
+ null/*messageUri*/, null/*callingPkg*/, tracker.mPersistMessage, tracker.mPriority,
+ tracker.mExpectMore, tracker.mValidityPeriod);
}
/**
@@ -1334,6 +1375,8 @@ public abstract class SMSDispatcher extends Handler {
public int mImsRetry; // nonzero indicates initial message was sent over Ims
public int mMessageRef;
public boolean mExpectMore;
+ public int mValidityPeriod;
+ public int mPriority;
String mFormat;
public final PendingIntent mSentIntent;
@@ -1367,8 +1410,9 @@ public abstract class SMSDispatcher extends Handler {
private SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr, String format,
AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
- SmsHeader smsHeader, boolean isExpectMore, String fullMessageText, int subId,
- boolean isText, boolean persistMessage, int userId) {
+ SmsHeader smsHeader, boolean expectMore, String fullMessageText, int subId,
+ boolean isText, boolean persistMessage, int userId, int priority,
+ int validityPeriod) {
mData = data;
mSentIntent = sentIntent;
mDeliveryIntent = deliveryIntent;
@@ -1376,7 +1420,7 @@ public abstract class SMSDispatcher extends Handler {
mAppInfo = appInfo;
mDestAddress = destAddr;
mFormat = format;
- mExpectMore = isExpectMore;
+ mExpectMore = expectMore;
mImsRetry = 0;
mMessageRef = 0;
mUnsentPartCount = unsentPartCount;
@@ -1388,6 +1432,8 @@ public abstract class SMSDispatcher extends Handler {
mIsText = isText;
mPersistMessage = persistMessage;
mUserId = userId;
+ mPriority = priority;
+ mValidityPeriod = validityPeriod;
}
/**
@@ -1603,7 +1649,8 @@ public abstract class SMSDispatcher extends Handler {
protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
PendingIntent deliveryIntent, String format, AtomicInteger unsentPartCount,
AtomicBoolean anyPartFailed, Uri messageUri, SmsHeader smsHeader,
- boolean isExpectMore, String fullMessageText, boolean isText, boolean persistMessage) {
+ boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage,
+ int priority, int validityPeriod) {
// Get calling app package name via UID from Binder call
PackageManager pm = mContext.getPackageManager();
String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
@@ -1624,16 +1671,27 @@ public abstract class SMSDispatcher extends Handler {
// and before displaying the number to the user if confirmation is required.
String destAddr = PhoneNumberUtils.extractNetworkPortion((String) data.get("destAddr"));
return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
- unsentPartCount, anyPartFailed, messageUri, smsHeader, isExpectMore,
- fullMessageText, getSubId(), isText, persistMessage, userId);
+ unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore,
+ fullMessageText, getSubId(), isText, persistMessage, userId, priority,
+ validityPeriod);
}
protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
- PendingIntent deliveryIntent, String format, Uri messageUri, boolean isExpectMore,
+ PendingIntent deliveryIntent, String format, Uri messageUri, boolean expectMore,
String fullMessageText, boolean isText, boolean persistMessage) {
return getSmsTracker(data, sentIntent, deliveryIntent, format, null/*unsentPartCount*/,
- null/*anyPartFailed*/, messageUri, null/*smsHeader*/, isExpectMore,
- fullMessageText, isText, persistMessage);
+ null/*anyPartFailed*/, messageUri, null/*smsHeader*/, expectMore,
+ fullMessageText, isText, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+ SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
+ }
+
+ protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
+ PendingIntent deliveryIntent, String format, Uri messageUri, boolean expectMore,
+ String fullMessageText, boolean isText, boolean persistMessage, int priority,
+ int validityPeriod) {
+ return getSmsTracker(data, sentIntent, deliveryIntent, format, null/*unsentPartCount*/,
+ null/*anyPartFailed*/, messageUri, null/*smsHeader*/, expectMore, fullMessageText,
+ isText, persistMessage, priority, validityPeriod);
}
protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr,
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index e0f94de5..0f4c9e4a 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -94,8 +94,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
@@ -105,8 +103,8 @@ import java.util.regex.PatternSyntaxException;
* {@hide}
*/
public class ServiceStateTracker extends Handler {
- private static final String LOG_TAG = "SST";
- private static final boolean DBG = true;
+ static final String LOG_TAG = "SST";
+ static final boolean DBG = true;
private static final boolean VDBG = false; // STOPSHIP if true
private static final String PROP_FORCE_ROAMING = "telephony.test.forceRoaming";
@@ -381,9 +379,7 @@ public class ServiceStateTracker extends Handler {
* we can fix the time zone once know the country.
*/
private boolean mNeedFixZoneAfterNitz = false;
- private int mZoneOffset;
- private boolean mZoneDst;
- private long mZoneTime;
+ private NitzData mNitzData;
private boolean mGotCountryCode = false;
private String mSavedTimeZone;
private long mSavedTime;
@@ -2254,7 +2250,7 @@ public class ServiceStateTracker extends Handler {
IccRecords iccRecords = mIccRecords;
String plmn = null;
boolean showPlmn = false;
- int rule = (iccRecords != null) ? iccRecords.getDisplayRule(mSS.getOperatorNumeric()) : 0;
+ int rule = (iccRecords != null) ? iccRecords.getDisplayRule(mSS) : 0;
if (combinedRegState == ServiceState.STATE_OUT_OF_SERVICE
|| combinedRegState == ServiceState.STATE_EMERGENCY_ONLY) {
showPlmn = true;
@@ -2530,11 +2526,11 @@ public class ServiceStateTracker extends Handler {
mRatLog.log(mSS.toString());
}
- protected void log(String s) {
+ protected final void log(String s) {
Rlog.d(LOG_TAG, s);
}
- protected void loge(String s) {
+ protected final void loge(String s) {
Rlog.e(LOG_TAG, s);
}
@@ -3132,12 +3128,12 @@ public class ServiceStateTracker extends Handler {
}
}
- protected boolean isInvalidOperatorNumeric(String operatorNumeric) {
+ private boolean isInvalidOperatorNumeric(String operatorNumeric) {
return operatorNumeric == null || operatorNumeric.length() < 5 ||
operatorNumeric.startsWith(INVALID_MCC);
}
- protected String fixUnknownMcc(String operatorNumeric, int sid) {
+ private String fixUnknownMcc(String operatorNumeric, int sid) {
if (sid <= 0) {
// no cdma information is available, do nothing
return operatorNumeric;
@@ -3147,45 +3143,53 @@ public class ServiceStateTracker extends Handler {
// if mSavedTimeZone is null, TimeZone would get the default timeZone,
// and the fixTimeZone couldn't help, because it depends on operator Numeric;
// if the sid is conflict and timezone is unavailable, the mcc may be not right.
- boolean isNitzTimeZone = false;
- int timeZone = 0;
- TimeZone tzone = null;
+ boolean isNitzTimeZone;
+ int rawUtcOffsetMillis;
if (mSavedTimeZone != null) {
- timeZone =
- TimeZone.getTimeZone(mSavedTimeZone).getRawOffset()/MS_PER_HOUR;
+ rawUtcOffsetMillis = TimeZone.getTimeZone(mSavedTimeZone).getRawOffset();
isNitzTimeZone = true;
} else {
- tzone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
- if (tzone != null)
- timeZone = tzone.getRawOffset()/MS_PER_HOUR;
+ rawUtcOffsetMillis = 0;
+ if (mNitzData != null) {
+ TimeZone tzone = NitzData.guessTimeZone(mNitzData);
+ if (tzone != null) {
+ rawUtcOffsetMillis = tzone.getRawOffset();
+ }
+ }
+ isNitzTimeZone = false;
}
+ int rawUtcOffsetHours = rawUtcOffsetMillis / MS_PER_HOUR;
- int mcc = mHbpcdUtils.getMcc(sid,
- timeZone, (mZoneDst ? 1 : 0), isNitzTimeZone);
+ boolean isDst = mNitzData != null && mNitzData.isDst();
+ int mcc = mHbpcdUtils.getMcc(sid, rawUtcOffsetHours, (isDst ? 1 : 0), isNitzTimeZone);
if (mcc > 0) {
operatorNumeric = Integer.toString(mcc) + DEFAULT_MNC;
}
return operatorNumeric;
}
- protected void fixTimeZone(String isoCountryCode) {
+ private void fixTimeZone(String isoCountryCode) {
TimeZone zone = null;
// If the offset is (0, false) and the time zone property
// is set, use the time zone property rather than GMT.
final String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
if (DBG) {
- log("fixTimeZone zoneName='" + zoneName +
- "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
- " iso-cc='" + isoCountryCode +
- "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
+ log("fixTimeZone zoneName='" + zoneName
+ + "' mNitzData=" + mNitzData
+ + " iso-cc='" + isoCountryCode
+ + "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
}
if ("".equals(isoCountryCode) && mNeedFixZoneAfterNitz) {
// Country code not found. This is likely a test network.
// Get a TimeZone based only on the NITZ parameters (best guess).
- zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
+
+ // mNeedFixZoneAfterNitz is only set to true when mNitzData is set so there's no need to
+ // check mNitzData == null.
+ zone = NitzData.guessTimeZone(mNitzData);
if (DBG) log("pollStateDone: using NITZ TimeZone");
- } else if ((mZoneOffset == 0) && (mZoneDst == false) && (zoneName != null)
- && (zoneName.length() > 0)
+ } else if ((mNitzData == null
+ || (mNitzData.getLocalOffsetMillis() == 0 && !mNitzData.isDst()))
+ && (zoneName != null) && (zoneName.length() > 0)
&& (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
// For NITZ string without time zone,
// need adjust time to reflect default time zone setting
@@ -3209,12 +3213,20 @@ public class ServiceStateTracker extends Handler {
}
if (DBG) log("fixTimeZone: using default TimeZone");
} else {
- zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, isoCountryCode);
+ if (mNitzData == null) {
+ // This behavior is unusual but consistent with historical behavior when it wasn't
+ // possible to detect whether a previous NITZ signal had been saved.
+ zone = TimeUtils.getTimeZone(0 /* offset */, false /* dst */, 0 /* when */,
+ isoCountryCode);
+ } else {
+ zone = TimeUtils.getTimeZone(mNitzData.getLocalOffsetMillis(), mNitzData.isDst(),
+ mNitzData.getCurrentTimeInMillis(), isoCountryCode);
+ }
if (DBG) log("fixTimeZone: using getTimeZone(off, dst, time, iso)");
}
- final String tmpLog = "fixTimeZone zoneName=" + zoneName + " mZoneOffset=" + mZoneOffset
- + " mZoneDst=" + mZoneDst + " iso-cc=" + isoCountryCode + " mNeedFixZoneAfterNitz="
+ final String tmpLog = "fixTimeZone zoneName=" + zoneName + " mNitzData=" + mNitzData
+ + " iso-cc=" + isoCountryCode + " mNeedFixZoneAfterNitz="
+ mNeedFixZoneAfterNitz + " zone=" + (zone != null ? zone.getID() : "NULL");
mTimeZoneLog.log(tmpLog);
@@ -3246,39 +3258,6 @@ public class ServiceStateTracker extends Handler {
(dataRegState != ServiceState.STATE_IN_SERVICE));
}
- /**
- * Returns a TimeZone object based only on parameters from the NITZ string.
- */
- private TimeZone getNitzTimeZone(int offset, boolean dst, long when) {
- TimeZone guess = findTimeZone(offset, dst, when);
- if (guess == null) {
- // Couldn't find a proper timezone. Perhaps the DST data is wrong.
- guess = findTimeZone(offset, !dst, when);
- }
- if (DBG) log("getNitzTimeZone returning " + (guess == null ? guess : guess.getID()));
- return guess;
- }
-
- private TimeZone findTimeZone(int offset, boolean dst, long when) {
- int rawOffset = offset;
- if (dst) {
- rawOffset -= MS_PER_HOUR;
- }
- String[] zones = TimeZone.getAvailableIDs(rawOffset);
- TimeZone guess = null;
- Date d = new Date(when);
- for (String zone : zones) {
- TimeZone tz = TimeZone.getTimeZone(zone);
- if (tz.getOffset(when) == offset &&
- tz.inDaylightTime(d) == dst) {
- guess = tz;
- break;
- }
- }
-
- return guess;
- }
-
/** convert ServiceState registration code
* to service state */
private int regCodeToServiceState(int code) {
@@ -3586,100 +3565,59 @@ public class ServiceStateTracker extends Handler {
+ " start=" + start + " delay=" + (start - nitzReceiveTime));
}
- try {
- /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
- * offset as well (which we won't worry about until later) */
- Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
-
- c.clear();
- c.set(Calendar.DST_OFFSET, 0);
-
- String[] nitzSubs = nitz.split("[/:,+-]");
-
- int year = 2000 + Integer.parseInt(nitzSubs[0]);
- if (year > MAX_NITZ_YEAR) {
- if (DBG) loge("NITZ year: " + year + " exceeds limit, skip NITZ time update");
- return;
- }
- c.set(Calendar.YEAR, year);
-
- // month is 0 based!
- int month = Integer.parseInt(nitzSubs[1]) - 1;
- c.set(Calendar.MONTH, month);
-
- int date = Integer.parseInt(nitzSubs[2]);
- c.set(Calendar.DATE, date);
-
- int hour = Integer.parseInt(nitzSubs[3]);
- c.set(Calendar.HOUR, hour);
-
- int minute = Integer.parseInt(nitzSubs[4]);
- c.set(Calendar.MINUTE, minute);
-
- int second = Integer.parseInt(nitzSubs[5]);
- c.set(Calendar.SECOND, second);
-
- boolean sign = (nitz.indexOf('-') == -1);
-
- int tzOffset = Integer.parseInt(nitzSubs[6]);
-
- int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7]) : 0;
-
- // The zone offset received from NITZ is for current local time,
- // so DST correction is already applied. Don't add it again.
- //
- // tzOffset += dst * 4;
- //
- // We could unapply it if we wanted the raw offset.
-
- tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
-
- TimeZone zone = null;
-
- // As a special extension, the Android emulator appends the name of
- // the host computer's timezone to the nitz string. this is zoneinfo
- // timezone name of the form Area!Location or Area!Location!SubLocation
- // so we need to convert the ! into /
- if (nitzSubs.length >= 9) {
- String tzname = nitzSubs[8].replace('!','/');
- zone = TimeZone.getTimeZone( tzname );
- }
+ NitzData nitzData = NitzData.parse(nitz);
+ if (nitzData == null) {
+ return;
+ }
+ try {
String iso = ((TelephonyManager) mPhone.getContext().
getSystemService(Context.TELEPHONY_SERVICE)).
getNetworkCountryIsoForPhone(mPhone.getPhoneId());
+ TimeZone zone = nitzData.getEmulatorHostTimeZone();
if (zone == null) {
-
if (mGotCountryCode) {
if (iso != null && iso.length() > 0) {
- zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
- c.getTimeInMillis(),
+ zone = TimeUtils.getTimeZone(
+ nitzData.getLocalOffsetMillis(),
+ nitzData.isDst(),
+ nitzData.getCurrentTimeInMillis(),
iso);
} else {
// We don't have a valid iso country code. This is
// most likely because we're on a test network that's
// using a bogus MCC (eg, "001"), so get a TimeZone
// based only on the NITZ parameters.
- zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
+ zone = NitzData.guessTimeZone(nitzData);
}
}
}
- if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+ int previousUtcOffset;
+ boolean previousIsDst;
+ if (mNitzData == null) {
+ // No previously saved NITZ data. Use the same defaults as Android would have done
+ // before it was possible to detect this case.
+ previousUtcOffset = 0;
+ previousIsDst = false;
+ } else {
+ previousUtcOffset = mNitzData.getLocalOffsetMillis();
+ previousIsDst = mNitzData.isDst();
+ }
+ if ((zone == null)
+ || (nitzData.getLocalOffsetMillis() != previousUtcOffset)
+ || (nitzData.isDst() != previousIsDst)) {
// We got the time before the country or the zone has changed
// so we don't know how to identify the DST rules yet. Save
// the information and hope to fix it up later.
mNeedFixZoneAfterNitz = true;
- mZoneOffset = tzOffset;
- mZoneDst = dst != 0;
- mZoneTime = c.getTimeInMillis();
+ mNitzData = nitzData;
}
- String tmpLog = "NITZ: nitz=" + nitz + " nitzReceiveTime=" + nitzReceiveTime
- + " tzOffset=" + tzOffset + " dst=" + dst + " zone="
- + (zone != null ? zone.getID() : "NULL")
+ String tmpLog = "NITZ: nitzData=" + nitzData + " nitzReceiveTime=" + nitzReceiveTime
+ + " zone=" + (zone != null ? zone.getID() : "NULL")
+ " iso=" + iso + " mGotCountryCode=" + mGotCountryCode
+ " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz
+ " getAutoTimeZone()=" + getAutoTimeZone();
@@ -3704,52 +3642,51 @@ public class ServiceStateTracker extends Handler {
try {
mWakeLock.acquire();
- if (!mPhone.isPhoneTypeGsm() || getAutoTime()) {
- long millisSinceNitzReceived
- = SystemClock.elapsedRealtime() - nitzReceiveTime;
-
- if (millisSinceNitzReceived < 0) {
- // Sanity check: something is wrong
- if (DBG) {
- log("NITZ: not setting time, clock has rolled "
- + "backwards since NITZ time was received, "
- + nitz);
- }
- return;
+ long millisSinceNitzReceived = SystemClock.elapsedRealtime() - nitzReceiveTime;
+ if (millisSinceNitzReceived < 0) {
+ // Sanity check: something is wrong
+ if (DBG) {
+ log("NITZ: not setting time, clock has rolled "
+ + "backwards since NITZ time was received, "
+ + nitz);
}
+ return;
+ }
- if (millisSinceNitzReceived > Integer.MAX_VALUE) {
- // If the time is this far off, something is wrong > 24 days!
- if (DBG) {
- log("NITZ: not setting time, processing has taken "
- + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
- + " days");
- }
- return;
+ if (millisSinceNitzReceived > Integer.MAX_VALUE) {
+ // If the time is this far off, something is wrong > 24 days!
+ if (DBG) {
+ log("NITZ: not setting time, processing has taken "
+ + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
+ + " days");
}
+ return;
+ }
- // Note: with range checks above, cast to int is safe
- c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
+ // Adjust the NITZ time by the delay since it was received.
+ long adjustedCurrentTimeMillis = nitzData.getCurrentTimeInMillis();
+ adjustedCurrentTimeMillis += millisSinceNitzReceived;
+ if (!mPhone.isPhoneTypeGsm() || getAutoTime()) {
tmpLog = "NITZ: nitz=" + nitz + " nitzReceiveTime=" + nitzReceiveTime
- + " Setting time of day to " + c.getTime()
+ + " Setting time of day to " + adjustedCurrentTimeMillis
+ " NITZ receive delay(ms): " + millisSinceNitzReceived
+ " gained(ms): "
- + (c.getTimeInMillis() - System.currentTimeMillis())
+ + (adjustedCurrentTimeMillis - System.currentTimeMillis())
+ " from " + nitz;
if (DBG) {
log(tmpLog);
}
mTimeLog.log(tmpLog);
if (mPhone.isPhoneTypeGsm()) {
- setAndBroadcastNetworkSetTime(c.getTimeInMillis());
+ setAndBroadcastNetworkSetTime(adjustedCurrentTimeMillis);
Rlog.i(LOG_TAG, "NITZ: after Setting time of day");
} else {
if (getAutoTime()) {
/**
* Update system time automatically
*/
- long gained = c.getTimeInMillis() - System.currentTimeMillis();
+ long gained = adjustedCurrentTimeMillis - System.currentTimeMillis();
long timeSinceLastUpdate = SystemClock.elapsedRealtime() - mSavedAtTime;
int nitzUpdateSpacing = Settings.Global.getInt(mCr,
Settings.Global.NITZ_UPDATE_SPACING, mNitzUpdateSpacing);
@@ -3759,12 +3696,13 @@ public class ServiceStateTracker extends Handler {
if ((mSavedAtTime == 0) || (timeSinceLastUpdate > nitzUpdateSpacing)
|| (Math.abs(gained) > nitzUpdateDiff)) {
if (DBG) {
- log("NITZ: Auto updating time of day to " + c.getTime()
+ log("NITZ: Auto updating time of day to "
+ + adjustedCurrentTimeMillis
+ " NITZ receive delay=" + millisSinceNitzReceived
+ "ms gained=" + gained + "ms from " + nitz);
}
- setAndBroadcastNetworkSetTime(c.getTimeInMillis());
+ setAndBroadcastNetworkSetTime(adjustedCurrentTimeMillis);
} else {
if (DBG) {
log("NITZ: ignore, a previous update was "
@@ -3775,8 +3713,8 @@ public class ServiceStateTracker extends Handler {
}
}
}
- SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
- saveNitzTime(c.getTimeInMillis());
+ SystemProperties.set("gsm.nitz.time", String.valueOf(adjustedCurrentTimeMillis));
+ saveNitzTime(adjustedCurrentTimeMillis);
mNitzUpdatedTime = true;
} finally {
if (DBG) {
@@ -4443,6 +4381,7 @@ public class ServiceStateTracker extends Handler {
mSignalStrength.setGsm(isGsm);
}
mSignalStrength.setLteRsrpBoost(mSS.getLteEarfcnRsrpBoost());
+ mSignalStrength.setUseOnlyRsrpForLteLevel(isUseOnlyRsrpForLteLevel());
} else {
log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
mSignalStrength = new SignalStrength(isGsm);
@@ -4483,7 +4422,7 @@ public class ServiceStateTracker extends Handler {
* @param needToFixTimeZone
* @return true if time zone needs to be fixed
*/
- protected boolean shouldFixTimeZoneNow(Phone phone, String operatorNumeric,
+ private boolean shouldFixTimeZoneNow(Phone phone, String operatorNumeric,
String prevOperatorNumeric, boolean needToFixTimeZone) {
// Return false if the mcc isn't valid as we don't know where we are.
// Return true if we have an IccCard and the mcc changed or we
@@ -4702,9 +4641,7 @@ public class ServiceStateTracker extends Handler {
pw.println(" mEmergencyOnly=" + mEmergencyOnly);
pw.println(" mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz);
pw.flush();
- pw.println(" mZoneOffset=" + mZoneOffset);
- pw.println(" mZoneDst=" + mZoneDst);
- pw.println(" mZoneTime=" + mZoneTime);
+ pw.println(" mNitzData=" + mNitzData);
pw.println(" mGotCountryCode=" + mGotCountryCode);
pw.println(" mNitzUpdatedTime=" + mNitzUpdatedTime);
pw.println(" mSavedTimeZone=" + mSavedTimeZone);
@@ -5085,4 +5022,25 @@ public class ServiceStateTracker extends Handler {
}
}
}
+
+ /**
+ * Check whether to use only RSRP for the number of LTE signal bar.
+ *
+ * @return true if it should use only RSRP for the number of LTE signal bar.
+ */
+ private boolean isUseOnlyRsrpForLteLevel() {
+ CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ // If an invalid subId is used, this bundle will contain default values.
+ PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
+ if (config != null) {
+ return config.getBoolean(
+ CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL);
+ }
+ }
+ // Return static default defined in CarrierConfigManager.
+ return CarrierConfigManager.getDefaultConfig().getBoolean(
+ CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL);
+ }
}
diff --git a/com/android/internal/telephony/SubscriptionController.java b/com/android/internal/telephony/SubscriptionController.java
index f122cc0b..2629b42d 100644
--- a/com/android/internal/telephony/SubscriptionController.java
+++ b/com/android/internal/telephony/SubscriptionController.java
@@ -45,6 +45,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.uicc.IccUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -87,6 +88,7 @@ public class SubscriptionController extends ISub.Stub {
static final boolean VDBG = false;
static final boolean DBG_CACHE = false;
static final int MAX_LOCAL_LOG_LINES = 500; // TODO: Reduce to 100 when 17678050 is fixed
+ private static final int DEPRECATED_SETTING = -1;
private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
/* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
@@ -193,6 +195,7 @@ public class SubscriptionController extends ISub.Stub {
protected SubscriptionController(Context c) {
init(c);
+ migrateImsSettings();
}
protected void init(Context c) {
@@ -222,6 +225,8 @@ public class SubscriptionController extends ISub.Stub {
ServiceManager.addService("isub", this);
}
+ migrateImsSettings();
+
if (DBG) logdl("[SubscriptionController] init by Phone");
}
@@ -905,8 +910,10 @@ public class SubscriptionController extends ISub.Stub {
ContentResolver resolver = mContext.getContentResolver();
Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
- SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE},
- SubscriptionManager.ICC_ID + "=?", new String[]{iccId}, null);
+ SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE,
+ SubscriptionManager.ICC_ID},
+ SubscriptionManager.ICC_ID + "=?" + " OR " + SubscriptionManager.ICC_ID + "=?",
+ new String[]{iccId, IccUtils.getDecimalSubstring(iccId)}, null);
boolean setDisplayName = false;
try {
@@ -918,6 +925,7 @@ public class SubscriptionController extends ISub.Stub {
int subId = cursor.getInt(0);
int oldSimInfoId = cursor.getInt(1);
int nameSource = cursor.getInt(2);
+ String oldIccId = cursor.getString(3);
ContentValues value = new ContentValues();
if (slotIndex != oldSimInfoId) {
@@ -928,6 +936,11 @@ public class SubscriptionController extends ISub.Stub {
setDisplayName = true;
}
+ if (oldIccId != null && oldIccId.length() < iccId.length()
+ && (oldIccId.equals(IccUtils.getDecimalSubstring(iccId)))) {
+ value.put(SubscriptionManager.ICC_ID, iccId);
+ }
+
if (value.size() > 0) {
resolver.update(SubscriptionManager.CONTENT_URI, value,
SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
@@ -1645,7 +1658,12 @@ public class SubscriptionController extends ISub.Stub {
broadcastDefaultVoiceSubIdChanged(subId);
}
- private void broadcastDefaultVoiceSubIdChanged(int subId) {
+ /**
+ * Broadcast intent of ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void broadcastDefaultVoiceSubIdChanged(int subId) {
// Broadcast an Intent for default voice sub change
if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
@@ -1660,7 +1678,7 @@ public class SubscriptionController extends ISub.Stub {
int subId = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId);
+ if (VDBG) slogd("[getDefaultVoiceSubId] subId=" + subId);
return subId;
}
@@ -1980,6 +1998,17 @@ public class SubscriptionController extends ISub.Stub {
enforceModifyPhoneState("setSubscriptionProperty");
final long token = Binder.clearCallingIdentity();
ContentResolver resolver = mContext.getContentResolver();
+
+ setSubscriptionPropertyIntoContentResolver(subId, propKey, propValue, resolver);
+
+ // Refresh the Cache of Active Subscription Info List
+ refreshCachedActiveSubscriptionInfoList();
+
+ Binder.restoreCallingIdentity(token);
+ }
+
+ private static void setSubscriptionPropertyIntoContentResolver(
+ int subId, String propKey, String propValue, ContentResolver resolver) {
ContentValues value = new ContentValues();
switch (propKey) {
case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
@@ -1994,21 +2023,22 @@ public class SubscriptionController extends ISub.Stub {
case SubscriptionManager.CB_CHANNEL_50_ALERT:
case SubscriptionManager.CB_CMAS_TEST_ALERT:
case SubscriptionManager.CB_OPT_OUT_DIALOG:
+ case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
+ case SubscriptionManager.VT_IMS_ENABLED:
+ case SubscriptionManager.WFC_IMS_ENABLED:
+ case SubscriptionManager.WFC_IMS_MODE:
+ case SubscriptionManager.WFC_IMS_ROAMING_MODE:
+ case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
value.put(propKey, Integer.parseInt(propValue));
break;
default:
- if(DBG) logd("Invalid column name");
+ if (DBG) slogd("Invalid column name");
break;
}
resolver.update(SubscriptionManager.CONTENT_URI, value,
SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
"=" + Integer.toString(subId), null);
-
- // Refresh the Cache of Active Subscription Info List
- refreshCachedActiveSubscriptionInfoList();
-
- Binder.restoreCallingIdentity(token);
}
/**
@@ -2046,6 +2076,12 @@ public class SubscriptionController extends ISub.Stub {
case SubscriptionManager.CB_CHANNEL_50_ALERT:
case SubscriptionManager.CB_CMAS_TEST_ALERT:
case SubscriptionManager.CB_OPT_OUT_DIALOG:
+ case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
+ case SubscriptionManager.VT_IMS_ENABLED:
+ case SubscriptionManager.WFC_IMS_ENABLED:
+ case SubscriptionManager.WFC_IMS_MODE:
+ case SubscriptionManager.WFC_IMS_ROAMING_MODE:
+ case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
resultValue = cursor.getInt(0) + "";
break;
default:
@@ -2139,4 +2175,47 @@ public class SubscriptionController extends ISub.Stub {
Binder.restoreCallingIdentity(token);
}
}
+
+ /**
+ * Migrating Ims settings from global setting to subscription DB, if not already done.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void migrateImsSettings() {
+ migrateImsSettingHelper(
+ Settings.Global.ENHANCED_4G_MODE_ENABLED,
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED);
+ migrateImsSettingHelper(
+ Settings.Global.VT_IMS_ENABLED,
+ SubscriptionManager.VT_IMS_ENABLED);
+ migrateImsSettingHelper(
+ Settings.Global.WFC_IMS_ENABLED,
+ SubscriptionManager.WFC_IMS_ENABLED);
+ migrateImsSettingHelper(
+ Settings.Global.WFC_IMS_MODE,
+ SubscriptionManager.WFC_IMS_MODE);
+ migrateImsSettingHelper(
+ Settings.Global.WFC_IMS_ROAMING_MODE,
+ SubscriptionManager.WFC_IMS_ROAMING_MODE);
+ migrateImsSettingHelper(
+ Settings.Global.WFC_IMS_ROAMING_ENABLED,
+ SubscriptionManager.WFC_IMS_ROAMING_ENABLED);
+ }
+
+ private void migrateImsSettingHelper(String settingGlobal, String subscriptionProperty) {
+ ContentResolver resolver = mContext.getContentResolver();
+ int defaultSubId = getDefaultVoiceSubId();
+ try {
+ int prevSetting = Settings.Global.getInt(resolver, settingGlobal);
+
+ if (prevSetting != DEPRECATED_SETTING) {
+ // Write previous setting into Subscription DB.
+ setSubscriptionPropertyIntoContentResolver(defaultSubId, subscriptionProperty,
+ Integer.toString(prevSetting), resolver);
+ // Write global setting value with DEPRECATED_SETTING making sure
+ // migration only happen once.
+ Settings.Global.putInt(resolver, settingGlobal, DEPRECATED_SETTING);
+ }
+ } catch (Settings.SettingNotFoundException e) {
+ }
+ }
}
diff --git a/com/android/internal/telephony/SubscriptionInfoUpdater.java b/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 2c819c33..1a2e09c7 100644
--- a/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -273,7 +273,7 @@ public class SubscriptionInfoUpdater extends Handler {
if (ar.exception == null) {
if (ar.result != null) {
byte[] data = (byte[])ar.result;
- mIccId[slotId] = IccUtils.bcdToString(data, 0, data.length);
+ mIccId[slotId] = IccUtils.bchToString(data, 0, data.length);
} else {
logd("Null ar");
mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
@@ -399,11 +399,11 @@ public class SubscriptionInfoUpdater extends Handler {
logd("handleSimLoaded: IccRecords null");
return;
}
- if (records.getIccId() == null) {
- logd("handleSimLoaded: IccID null");
+ if (records.getFullIccId() == null) {
+ logd("onRecieve: IccID null");
return;
}
- mIccId[slotId] = records.getIccId();
+ mIccId[slotId] = records.getFullIccId();
if (isAllIccIdQueryDone()) {
updateSubscriptionInfoByIccId();
@@ -427,20 +427,12 @@ public class SubscriptionInfoUpdater extends Handler {
ContentResolver contentResolver = mContext.getContentResolver();
if (msisdn != null) {
- ContentValues number = new ContentValues(1);
- number.put(SubscriptionManager.NUMBER, msisdn);
- contentResolver.update(SubscriptionManager.CONTENT_URI, number,
- SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
- + Long.toString(subId), null);
-
- // refresh Cached Active Subscription Info List
- SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
+ SubscriptionController.getInstance().setDisplayNumber(msisdn, subId);
}
SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
String nameToSet;
String simCarrierName = tm.getSimOperatorName(subId);
- ContentValues name = new ContentValues(1);
if (subInfo != null && subInfo.getNameSource() !=
SubscriptionManager.NAME_SOURCE_USER_INPUT) {
@@ -449,14 +441,8 @@ public class SubscriptionInfoUpdater extends Handler {
} else {
nameToSet = "CARD " + Integer.toString(slotId + 1);
}
- name.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
logd("sim name = " + nameToSet);
- contentResolver.update(SubscriptionManager.CONTENT_URI, name,
- SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
- + "=" + Long.toString(subId), null);
-
- // refresh Cached Active Subscription Info List
- SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
+ SubscriptionController.getInstance().setDisplayName(nameToSet, subId);
}
/* Update preferred network type and network selection mode on SIM change.
@@ -569,16 +555,19 @@ public class SubscriptionInfoUpdater extends Handler {
ContentResolver contentResolver = mContext.getContentResolver();
String[] oldIccId = new String[PROJECT_SIM_NUM];
+ String[] decIccId = new String[PROJECT_SIM_NUM];
for (int i = 0; i < PROJECT_SIM_NUM; i++) {
oldIccId[i] = null;
List<SubscriptionInfo> oldSubInfo =
SubscriptionController.getInstance().getSubInfoUsingSlotIndexWithCheck(i, false,
mContext.getOpPackageName());
+ decIccId[i] = IccUtils.getDecimalSubstring(mIccId[i]);
if (oldSubInfo != null && oldSubInfo.size() > 0) {
oldIccId[i] = oldSubInfo.get(0).getIccId();
logd("updateSubscriptionInfoByIccId: oldSubId = "
+ oldSubInfo.get(0).getSubscriptionId());
- if (mInsertSimState[i] == SIM_NOT_CHANGE && !mIccId[i].equals(oldIccId[i])) {
+ if (mInsertSimState[i] == SIM_NOT_CHANGE && !(mIccId[i].equals(oldIccId[i])
+ || (decIccId[i] != null && decIccId[i].equals(oldIccId[i])))) {
mInsertSimState[i] = SIM_CHANGED;
}
if (mInsertSimState[i] != SIM_NOT_CHANGE) {
@@ -623,7 +612,7 @@ public class SubscriptionInfoUpdater extends Handler {
} else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ {
mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i);
}
- if (isNewSim(mIccId[i], oldIccId)) {
+ if (isNewSim(mIccId[i], decIccId[i], oldIccId)) {
nNewCardCount++;
switch (i) {
case PhoneConstants.SUB1:
@@ -798,12 +787,15 @@ public class SubscriptionInfoUpdater extends Handler {
return -1;
}
- private boolean isNewSim(String iccId, String[] oldIccId) {
+ private boolean isNewSim(String iccId, String decIccId, String[] oldIccId) {
boolean newSim = true;
for(int i = 0; i < PROJECT_SIM_NUM; i++) {
if(iccId.equals(oldIccId[i])) {
newSim = false;
break;
+ } else if (decIccId != null && decIccId.equals(oldIccId[i])) {
+ newSim = false;
+ break;
}
}
logd("newSim = " + newSim);
diff --git a/com/android/internal/telephony/UiccSmsController.java b/com/android/internal/telephony/UiccSmsController.java
index e6cb972d..59449b83 100644
--- a/com/android/internal/telephony/UiccSmsController.java
+++ b/com/android/internal/telephony/UiccSmsController.java
@@ -159,6 +159,21 @@ public class UiccSmsController extends ISms.Stub {
}
}
+ @Override
+ public void sendTextForSubscriberWithOptions(int subId, String callingPackage,
+ String destAddr, String scAddr, String parts, PendingIntent sentIntents,
+ PendingIntent deliveryIntents, boolean persistMessage, int priority,
+ boolean expectMore, int validityPeriod) {
+ IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+ if (iccSmsIntMgr != null ) {
+ iccSmsIntMgr.sendTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntents,
+ deliveryIntents, persistMessage, priority, expectMore, validityPeriod);
+ } else {
+ Rlog.e(LOG_TAG,"sendTextWithOptions iccSmsIntMgr is null for" +
+ " Subscription: " + subId);
+ }
+ }
+
public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
List<String> parts, List<PendingIntent> sentIntents,
List<PendingIntent> deliveryIntents) throws android.os.RemoteException {
@@ -184,6 +199,22 @@ public class UiccSmsController extends ISms.Stub {
}
@Override
+ public void sendMultipartTextForSubscriberWithOptions(int subId, String callingPackage,
+ String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
+ List<PendingIntent> deliveryIntents, boolean persistMessage, int priority,
+ boolean expectMore, int validityPeriod) {
+ IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+ if (iccSmsIntMgr != null ) {
+ iccSmsIntMgr.sendMultipartTextWithOptions(callingPackage, destAddr, scAddr, parts,
+ sentIntents, deliveryIntents, persistMessage, priority, expectMore,
+ validityPeriod);
+ } else {
+ Rlog.e(LOG_TAG,"sendMultipartTextWithOptions iccSmsIntMgr is null for" +
+ " Subscription: " + subId);
+ }
+ }
+
+ @Override
public boolean enableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType)
throws android.os.RemoteException {
return enableCellBroadcastRangeForSubscriber(subId, messageIdentifier, messageIdentifier,
diff --git a/com/android/internal/telephony/cat/CommandParamsFactory.java b/com/android/internal/telephony/cat/CommandParamsFactory.java
index eb928885..256f541e 100644
--- a/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -28,16 +28,16 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import static com.android.internal.telephony.cat.CatCmdMessage.
- SetupEventListConstants.USER_ACTIVITY_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
- SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
- SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
- SetupEventListConstants.BROWSER_TERMINATION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
- SetupEventListConstants.BROWSING_STATUS_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+ .SetupEventListConstants.BROWSER_TERMINATION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+ .SetupEventListConstants.BROWSING_STATUS_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+ .SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+ .SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+ .SetupEventListConstants.USER_ACTIVITY_EVENT;
/**
* Factory class, used for decoding raw byte arrays, received from baseband,
* into a CommandParams object.
@@ -917,6 +917,10 @@ class CommandParamsFactory extends Handler {
ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
if (ctlv != null) {
textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+ // Assign the tone message text to empty string, if alpha identifier
+ // data is null. If no alpha identifier tlv is present, then tone
+ // message text will be null.
+ if (textMsg.text == null) textMsg.text = "";
}
// parse tone duration
ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
diff --git a/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 1cfdc334..080ca1c3 100644
--- a/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -113,7 +113,7 @@ public class CdmaSMSDispatcher extends SMSDispatcher {
if (pdu != null) {
HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
- null /*messageUri*/, false /*isExpectMore*/, null /*fullMessageText*/,
+ null /*messageUri*/, false /*expectMore*/, null /*fullMessageText*/,
false /*isText*/, true /*persistMessage*/);
String carrierPackage = getCarrierAppPackageName();
@@ -141,13 +141,14 @@ public class CdmaSMSDispatcher extends SMSDispatcher {
@Override
public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
- boolean persistMessage) {
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
- scAddr, destAddr, text, (deliveryIntent != null), null);
+ scAddr, destAddr, text, (deliveryIntent != null), null, priority);
if (pdu != null) {
HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
- messageUri, false /*isExpectMore*/, text, true /*isText*/, persistMessage);
+ messageUri, expectMore, text, true /*isText*/, persistMessage,
+ priority, validityPeriod);
String carrierPackage = getCarrierAppPackageName();
if (carrierPackage != null) {
@@ -189,7 +190,7 @@ public class CdmaSMSDispatcher extends SMSDispatcher {
String message, SmsHeader smsHeader, int encoding,
PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
- String fullMessageText) {
+ String fullMessageText, int priority, boolean expectMore, int validityPeriod) {
UserData uData = new UserData();
uData.payloadStr = message;
uData.userDataHeader = smsHeader;
@@ -205,14 +206,14 @@ public class CdmaSMSDispatcher extends SMSDispatcher {
* callback to the sender when that last fragment delivery
* has been acknowledged. */
SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
- uData, (deliveryIntent != null) && lastPart);
+ uData, (deliveryIntent != null) && lastPart, priority);
HashMap map = getSmsTrackerMap(destinationAddress, scAddress,
message, submitPdu);
return getSmsTracker(map, sentIntent, deliveryIntent,
getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
- false /*isExpextMore*/, fullMessageText, true /*isText*/,
- true /*persistMessage*/);
+ (!lastPart || expectMore), fullMessageText, true /*isText*/,
+ true /*persistMessage*/, priority, validityPeriod);
}
@Override
diff --git a/com/android/internal/telephony/cdma/SmsMessage.java b/com/android/internal/telephony/cdma/SmsMessage.java
index 7a53ef63..14c5f4be 100644
--- a/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/com/android/internal/telephony/cdma/SmsMessage.java
@@ -99,6 +99,15 @@ public class SmsMessage extends SmsMessageBase {
private static final int RETURN_NO_ACK = 0;
private static final int RETURN_ACK = 1;
+ /**
+ * Supported priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+ */
+ private static final int PRIORITY_NORMAL = 0x0;
+ private static final int PRIORITY_INTERACTIVE = 0x1;
+ private static final int PRIORITY_URGENT = 0x2;
+ private static final int PRIORITY_EMERGENCY = 0x3;
+
private SmsEnvelope mEnvelope;
private BearerData mBearerData;
@@ -211,6 +220,26 @@ public class SmsMessage extends SmsMessageBase {
*/
public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
boolean statusReportRequested, SmsHeader smsHeader) {
+ return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddr Service Centre address. Null means use default.
+ * @param destAddr Address of the recipient.
+ * @param message String representation of the message payload.
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param smsHeader Array containing the data for the User Data Header, preceded
+ * by the Element Identifiers.
+ * @param priority Priority level of the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+ boolean statusReportRequested, SmsHeader smsHeader, int priority) {
/**
* TODO(cleanup): Do we really want silent failure like this?
@@ -224,7 +253,7 @@ public class SmsMessage extends SmsMessageBase {
UserData uData = new UserData();
uData.payloadStr = message;
uData.userDataHeader = smsHeader;
- return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority);
}
/**
@@ -282,6 +311,22 @@ public class SmsMessage extends SmsMessageBase {
}
/**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+ *
+ * @param destAddr the address of the destination for the message
+ * @param userData the data for the message
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param priority Priority level of the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+ boolean statusReportRequested, int priority) {
+ return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority);
+ }
+
+ /**
* Note: This function is a GSM specific functionality which is not supported in CDMA mode.
*/
@Override
@@ -764,6 +809,15 @@ public class SmsMessage extends SmsMessageBase {
*/
private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
UserData userData) {
+ return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1);
+ }
+
+ /**
+ * Creates BearerData and Envelope from parameters for a Submit SMS.
+ * @return byte stream for SubmitPdu.
+ */
+ private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+ UserData userData, int priority) {
/**
* TODO(cleanup): give this function a more meaningful name.
@@ -792,6 +846,10 @@ public class SmsMessage extends SmsMessageBase {
bearerData.userAckReq = false;
bearerData.readAckReq = false;
bearerData.reportReq = false;
+ if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) {
+ bearerData.priorityIndicatorSet = true;
+ bearerData.priority = priority;
+ }
bearerData.userData = userData;
diff --git a/com/android/internal/telephony/dataconnection/ApnContext.java b/com/android/internal/telephony/dataconnection/ApnContext.java
index 3cd804de..1b42d4a5 100644
--- a/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -35,7 +35,6 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -272,7 +271,10 @@ public class ApnContext {
log("setState: " + s + ", previous state:" + mState);
}
- mState = s;
+ if (mState != s) {
+ mStateLocalLog.log("State changed from " + mState + " to " + s);
+ mState = s;
+ }
if (mState == DctConstants.State.FAILED) {
if (mRetryManager.getWaitingApns() != null) {
@@ -399,8 +401,7 @@ public class ApnContext {
private final ArrayList<LocalLog> mLocalLogs = new ArrayList<>();
private final ArrayList<NetworkRequest> mNetworkRequests = new ArrayList<>();
- private final ArrayDeque<LocalLog> mHistoryLogs = new ArrayDeque<>();
- private final static int MAX_HISTORY_LOG_COUNT = 4;
+ private final LocalLog mStateLocalLog = new LocalLog(50);
public void requestLog(String str) {
synchronized (mRefCountLock) {
@@ -719,13 +720,15 @@ public class ApnContext {
pw.increaseIndent();
for (LocalLog l : mLocalLogs) {
l.dump(fd, pw, args);
+ pw.println("-----");
}
- if (mHistoryLogs.size() > 0) pw.println("Historical Logs:");
- for (LocalLog l : mHistoryLogs) {
- l.dump(fd, pw, args);
- }
+ pw.decreaseIndent();
+ pw.println("Historical APN state:");
+ pw.increaseIndent();
+ mStateLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
pw.println(mRetryManager);
+ pw.println("--------------------------");
}
}
}
diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java
index 14b2c625..9e4b29cd 100644
--- a/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -170,7 +170,7 @@ public class DataConnection extends StateMachine {
private int mDataRegState = Integer.MAX_VALUE;
private NetworkInfo mNetworkInfo;
private NetworkAgent mNetworkAgent;
- private LocalLog mLocalLog = new LocalLog(50);
+ private LocalLog mNetCapsLocalLog = new LocalLog(50);
int mTag;
public int mCid;
@@ -976,6 +976,12 @@ public class DataConnection extends StateMachine {
result.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(mPhone.getSubId())));
+ if (!mPhone.getServiceState().getDataRoaming()) {
+ result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+ } else {
+ result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+ }
+
return result;
}
@@ -1213,26 +1219,22 @@ public class DataConnection extends StateMachine {
+ " drs=" + mDataRegState
+ " mRilRat=" + mRilRat);
}
- ServiceState ss = mPhone.getServiceState();
- int networkType = ss.getDataNetworkType();
- mNetworkInfo.setSubtype(networkType,
- TelephonyManager.getNetworkTypeName(networkType));
+ updateNetworkInfo();
+ updateNetworkInfoSuspendState();
if (mNetworkAgent != null) {
- updateNetworkInfoSuspendState();
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
mNetworkAgent.sendLinkProperties(mLinkProperties);
}
break;
-
case EVENT_DATA_CONNECTION_ROAM_ON:
- mNetworkInfo.setRoaming(true);
- break;
-
case EVENT_DATA_CONNECTION_ROAM_OFF:
- mNetworkInfo.setRoaming(false);
+ updateNetworkInfo();
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
+ mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ }
break;
-
default:
if (DBG) {
log("DcDefaultState: shouldn't happen but ignore msg.what="
@@ -1245,9 +1247,14 @@ public class DataConnection extends StateMachine {
}
}
- private boolean updateNetworkInfoSuspendState() {
- final NetworkInfo.DetailedState oldState = mNetworkInfo.getDetailedState();
+ private void updateNetworkInfo() {
+ final ServiceState state = mPhone.getServiceState();
+ final int subtype = state.getDataNetworkType();
+ mNetworkInfo.setSubtype(subtype, TelephonyManager.getNetworkTypeName(subtype));
+ mNetworkInfo.setRoaming(state.getDataRoaming());
+ }
+ private void updateNetworkInfoSuspendState() {
// this is only called when we are either connected or suspended. Decide which.
if (mNetworkAgent == null) {
Rlog.e(getName(), "Setting suspend state without a NetworkAgent");
@@ -1265,13 +1272,12 @@ public class DataConnection extends StateMachine {
if (ct.getState() != PhoneConstants.State.IDLE) {
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null,
mNetworkInfo.getExtraInfo());
- return (oldState != NetworkInfo.DetailedState.SUSPENDED);
+ return;
}
}
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null,
mNetworkInfo.getExtraInfo());
}
- return (oldState != mNetworkInfo.getDetailedState());
}
private DcDefaultState mDefaultState = new DcDefaultState();
@@ -1542,22 +1548,7 @@ public class DataConnection extends StateMachine {
@Override public void enter() {
if (DBG) log("DcActiveState: enter dc=" + DataConnection.this);
- // verify and get updated information in case these things
- // are obsolete
- ServiceState ss = mPhone.getServiceState();
- final int networkType = ss.getDataNetworkType();
- if (mNetworkInfo.getSubtype() != networkType) {
- log("DcActiveState with incorrect subtype (" + mNetworkInfo.getSubtype()
- + ", " + networkType + "), updating.");
- }
- mNetworkInfo.setSubtype(networkType, TelephonyManager.getNetworkTypeName(networkType));
- final boolean roaming = ss.getDataRoaming();
- if (roaming != mNetworkInfo.isRoaming()) {
- log("DcActiveState with incorrect roaming (" + mNetworkInfo.isRoaming()
- + ", " + roaming + "), updating.");
- }
-
- mNetworkInfo.setRoaming(roaming);
+ updateNetworkInfo();
// If we were retrying there maybe more than one, otherwise they'll only be one.
notifyAllOfConnected(Phone.REASON_CONNECTED);
@@ -1685,17 +1676,11 @@ public class DataConnection extends StateMachine {
retVal = HANDLED;
break;
}
- case EVENT_DATA_CONNECTION_ROAM_ON: {
- mNetworkInfo.setRoaming(true);
- if (mNetworkAgent != null) {
- mNetworkAgent.sendNetworkInfo(mNetworkInfo);
- }
- retVal = HANDLED;
- break;
- }
+ case EVENT_DATA_CONNECTION_ROAM_ON:
case EVENT_DATA_CONNECTION_ROAM_OFF: {
- mNetworkInfo.setRoaming(false);
+ updateNetworkInfo();
if (mNetworkAgent != null) {
+ mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
retVal = HANDLED;
@@ -1721,8 +1706,10 @@ public class DataConnection extends StateMachine {
}
case EVENT_DATA_CONNECTION_VOICE_CALL_STARTED:
case EVENT_DATA_CONNECTION_VOICE_CALL_ENDED: {
- if (updateNetworkInfoSuspendState() && mNetworkAgent != null) {
- // state changed
+ updateNetworkInfo();
+ updateNetworkInfoSuspendState();
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
retVal = HANDLED;
@@ -1844,7 +1831,7 @@ public class DataConnection extends StateMachine {
public DcNetworkAgent(Looper l, Context c, String TAG, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
super(l, c, TAG, ni, nc, lp, score, misc);
- mLocalLog.log("New network agent created. capabilities=" + nc);
+ mNetCapsLocalLog.log("New network agent created. capabilities=" + nc);
mNetworkCapabilities = nc;
}
@@ -1897,7 +1884,7 @@ public class DataConnection extends StateMachine {
+ mPhone.getServiceState().getRilDataRadioTechnology()
+ ", DUN APN=\"" + mDct.fetchDunApn() + "\""
+ ", mApnSetting=" + mApnSetting;
- mLocalLog.log(logStr);
+ mNetCapsLocalLog.log(logStr);
log(logStr);
mNetworkCapabilities = networkCapabilities;
}
@@ -2140,7 +2127,7 @@ public class DataConnection extends StateMachine {
pw.println("mAc=" + mAc);
pw.println("Network capabilities changed history:");
pw.increaseIndent();
- mLocalLog.dump(fd, pw, args);
+ mNetCapsLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
pw.decreaseIndent();
pw.println();
diff --git a/com/android/internal/telephony/euicc/EuiccController.java b/com/android/internal/telephony/euicc/EuiccController.java
index 0d58d802..ac2a0392 100644
--- a/com/android/internal/telephony/euicc/EuiccController.java
+++ b/com/android/internal/telephony/euicc/EuiccController.java
@@ -411,6 +411,15 @@ public class EuiccController extends IEuiccController.Stub {
callingToken, subscription, switchAfterDownload,
callingPackage));
break;
+ case EuiccService.RESULT_NEED_CONFIRMATION_CODE:
+ resultCode = RESOLVABLE_ERROR;
+ addResolutionIntent(extrasIntent,
+ EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE,
+ callingPackage,
+ EuiccOperation.forDownloadConfirmationCode(
+ callingToken, subscription, switchAfterDownload,
+ callingPackage));
+ break;
default:
resultCode = ERROR;
extrasIntent.putExtra(
diff --git a/com/android/internal/telephony/euicc/EuiccOperation.java b/com/android/internal/telephony/euicc/EuiccOperation.java
index 3b0dbc57..84df52e4 100644
--- a/com/android/internal/telephony/euicc/EuiccOperation.java
+++ b/com/android/internal/telephony/euicc/EuiccOperation.java
@@ -25,6 +25,7 @@ import android.os.Parcelable;
import android.service.euicc.EuiccService;
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -60,6 +61,7 @@ public class EuiccOperation implements Parcelable {
ACTION_GET_METADATA_DEACTIVATE_SIM,
ACTION_DOWNLOAD_DEACTIVATE_SIM,
ACTION_DOWNLOAD_NO_PRIVILEGES,
+ ACTION_DOWNLOAD_CONFIRMATION_CODE,
})
@interface Action {}
@@ -75,6 +77,8 @@ public class EuiccOperation implements Parcelable {
static final int ACTION_SWITCH_DEACTIVATE_SIM = 5;
@VisibleForTesting
static final int ACTION_SWITCH_NO_PRIVILEGES = 6;
+ @VisibleForTesting
+ static final int ACTION_DOWNLOAD_CONFIRMATION_CODE = 7;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final @Action int mAction;
@@ -123,6 +127,17 @@ public class EuiccOperation implements Parcelable {
subscription, 0 /* subscriptionId */, switchAfterDownload, callingPackage);
}
+ /**
+ * {@link EuiccManager#downloadSubscription} failed with
+ * {@link EuiccService#RESULT_NEED_CONFIRMATION_CODE} error.
+ */
+ public static EuiccOperation forDownloadConfirmationCode(long callingToken,
+ DownloadableSubscription subscription, boolean switchAfterDownload,
+ String callingPackage) {
+ return new EuiccOperation(ACTION_DOWNLOAD_CONFIRMATION_CODE, callingToken,
+ subscription, 0 /* subscriptionId */, switchAfterDownload, callingPackage);
+ }
+
static EuiccOperation forGetDefaultListDeactivateSim(long callingToken, String callingPackage) {
return new EuiccOperation(ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM, callingToken,
null /* downloadableSubscription */, 0 /* subscriptionId */,
@@ -205,6 +220,11 @@ public class EuiccOperation implements Parcelable {
resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT),
callbackIntent);
break;
+ case ACTION_DOWNLOAD_CONFIRMATION_CODE:
+ resolvedDownloadConfirmationCode(
+ resolutionExtras.getString(EuiccService.RESOLUTION_EXTRA_CONFIRMATION_CODE),
+ callbackIntent);
+ break;
case ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM:
resolvedGetDefaultListDeactivateSim(
resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT),
@@ -284,6 +304,24 @@ public class EuiccOperation implements Parcelable {
}
}
+ private void resolvedDownloadConfirmationCode(String confirmationCode,
+ PendingIntent callbackIntent) {
+ if (TextUtils.isEmpty(confirmationCode)) {
+ fail(callbackIntent);
+ } else {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ mDownloadableSubscription.setConfirmationCode(confirmationCode);
+ }
+ EuiccController.get()
+ .downloadSubscription(
+ mDownloadableSubscription,
+ mSwitchAfterDownload,
+ mCallingPackage,
+ true /* forceDeactivateSim */,
+ callbackIntent);
+ }
+ }
+
private void resolvedGetDefaultListDeactivateSim(
boolean consent, PendingIntent callbackIntent) {
if (consent) {
diff --git a/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 8f18c61d..6e4a79f8 100644
--- a/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -157,7 +157,7 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
if (pdu != null) {
HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
- null /*messageUri*/, false /*isExpectMore*/, null /*fullMessageText*/,
+ null /*messageUri*/, false /*expectMore*/, null /*fullMessageText*/,
false /*isText*/, true /*persistMessage*/);
String carrierPackage = getCarrierAppPackageName();
@@ -179,14 +179,14 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
@Override
public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
- boolean persistMessage) {
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
- scAddr, destAddr, text, (deliveryIntent != null));
+ scAddr, destAddr, text, (deliveryIntent != null), validityPeriod);
if (pdu != null) {
HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
- messageUri, false /*isExpectMore*/, text /*fullMessageText*/, true /*isText*/,
- persistMessage);
+ messageUri, false /*expectMore*/, text /*fullMessageText*/, true /*isText*/,
+ persistMessage, priority, validityPeriod);
String carrierPackage = getCarrierAppPackageName();
if (carrierPackage != null) {
@@ -221,17 +221,17 @@ public final class GsmSMSDispatcher extends SMSDispatcher {
String message, SmsHeader smsHeader, int encoding,
PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
- String fullMessageText) {
+ String fullMessageText, int priority, boolean expectMore, int validityPeriod) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
- encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
+ encoding, smsHeader.languageTable, smsHeader.languageShiftTable, validityPeriod);
if (pdu != null) {
HashMap map = getSmsTrackerMap(destinationAddress, scAddress,
message, pdu);
return getSmsTracker(map, sentIntent,
deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
- smsHeader, !lastPart, fullMessageText, true /*isText*/,
- false /*persistMessage*/);
+ smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/,
+ false /*persistMessage*/, priority, validityPeriod);
} else {
Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
return null;
diff --git a/com/android/internal/telephony/gsm/SmsMessage.java b/com/android/internal/telephony/gsm/SmsMessage.java
index 1ca19e01..4f5bfa91 100644
--- a/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/com/android/internal/telephony/gsm/SmsMessage.java
@@ -89,6 +89,18 @@ public class SmsMessage extends SmsMessageBase {
private int mVoiceMailCount = 0;
+ private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00;
+ private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01;
+ private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02;
+ private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03;
+
+ //Validity Period min - 5 mins
+ private static final int VALIDITY_PERIOD_MIN = 5;
+ //Validity Period max - 63 weeks
+ private static final int VALIDITY_PERIOD_MAX = 635040;
+
+ private static final int INVALID_VALIDITY_PERIOD = -1;
+
public static class SubmitPdu extends SubmitPduBase {
}
@@ -202,6 +214,45 @@ public class SmsMessage extends SmsMessageBase {
}
/**
+ * Get Encoded Relative Validty Period Value from Validity period in mins.
+ *
+ * @param validityPeriod Validity period in mins.
+ *
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * ||relValidityPeriod (TP-VP) || || validityPeriod ||
+ *
+ * 0 to 143 ---> (TP-VP + 1) x 5 minutes
+ *
+ * 144 to 167 ---> 12 hours + ((TP-VP -143) x 30 minutes)
+ *
+ * 168 to 196 ---> (TP-VP - 166) x 1 day
+ *
+ * 197 to 255 ---> (TP-VP - 192) x 1 week
+ *
+ * @return relValidityPeriod Encoded Relative Validity Period Value.
+ * @hide
+ */
+ public static int getRelativeValidityPeriod(int validityPeriod) {
+ int relValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+ if (validityPeriod < VALIDITY_PERIOD_MIN || validityPeriod > VALIDITY_PERIOD_MAX) {
+ Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod);
+ return relValidityPeriod;
+ }
+
+ if (validityPeriod <= 720) {
+ relValidityPeriod = (validityPeriod / 5) - 1;
+ } else if (validityPeriod <= 1440) {
+ relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
+ } else if (validityPeriod <= 43200) {
+ relValidityPeriod = (validityPeriod / 1440) + 166;
+ } else if (validityPeriod <= 635040) {
+ relValidityPeriod = (validityPeriod / 10080) + 192;
+ }
+ return relValidityPeriod;
+ }
+
+ /**
* Get an SMS-SUBMIT PDU for a destination address and a message
*
* @param scAddress Service Centre address. Null means use default.
@@ -236,6 +287,29 @@ public class SmsMessage extends SmsMessageBase {
String destinationAddress, String message,
boolean statusReportRequested, byte[] header, int encoding,
int languageTable, int languageShiftTable) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+ header, encoding, languageTable, languageShiftTable, -1);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message using the
+ * specified encoding.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param encoding Encoding defined by constants in
+ * com.android.internal.telephony.SmsConstants.ENCODING_*
+ * @param languageTable
+ * @param languageShiftTable
+ * @param validityPeriod Validity Period of the message in Minutes.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header, int encoding,
+ int languageTable, int languageShiftTable, int validityPeriod) {
// Perform null parameter checks.
if (message == null || destinationAddress == null) {
@@ -272,8 +346,19 @@ public class SmsMessage extends SmsMessageBase {
}
SubmitPdu ret = new SubmitPdu();
- // MTI = SMS-SUBMIT, UDHI = header != null
- byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+
+ int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE;
+ int relativeValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+ // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3
+ //bit 4:3 = 10 - TP-VP field present - relative format
+ if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) {
+ validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE;
+ }
+
+ byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) |
+ (header != null ? 0x40 : 0x00));
+
ByteArrayOutputStream bo = getSubmitPduHead(
scAddress, destinationAddress, mtiByte,
statusReportRequested, ret);
@@ -338,7 +423,11 @@ public class SmsMessage extends SmsMessageBase {
bo.write(0x08);
}
- // (no TP-Validity-Period)
+ if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
+ // ( TP-Validity-Period - relative format)
+ bo.write(relativeValidityPeriod);
+ }
+
bo.write(userData, 0, userData.length);
ret.encodedMessage = bo.toByteArray();
return ret;
@@ -388,6 +477,24 @@ public class SmsMessage extends SmsMessageBase {
}
/**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param destinationAddress the address of the destination for the message
+ * @param statusReportRequested staus report of the message Requested
+ * @param validityPeriod Validity Period of the message in Minutes.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, int validityPeriod) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+ null, ENCODING_UNKNOWN, 0, 0, validityPeriod);
+ }
+
+ /**
* Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
*
* @param scAddress Service Centre address. null == use default
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index f837b563..408dca53 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -50,7 +50,6 @@ import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.telephony.ims.ImsServiceProxy;
import android.telephony.ims.feature.ImsFeature;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -69,6 +68,7 @@ import com.android.ims.ImsManager;
import com.android.ims.ImsMultiEndpoint;
import com.android.ims.ImsReasonInfo;
import com.android.ims.ImsServiceClass;
+import com.android.ims.ImsServiceProxy;
import com.android.ims.ImsSuppServiceNotification;
import com.android.ims.ImsUtInterface;
import com.android.ims.internal.IImsVideoCallProvider;
@@ -972,7 +972,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
return false;
}
- return mImsManager.isServiceAvailable();
+ return mImsManager.isServiceReady();
}
/**
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 60a50b90..cea8b217 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -393,14 +393,6 @@ class ImsPhoneCommandInterface extends BaseCommands implements CommandsInterface
}
@Override
- public void invokeOemRilRequestRaw(byte[] data, Message response) {
- }
-
- @Override
- public void invokeOemRilRequestStrings(String[] strings, Message response) {
- }
-
- @Override
public void setBandMode (int bandMode, Message response) {
}
diff --git a/com/android/internal/telephony/sip/SipCommandInterface.java b/com/android/internal/telephony/sip/SipCommandInterface.java
index fe1d7c54..1264053c 100644
--- a/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -394,14 +394,6 @@ class SipCommandInterface extends BaseCommands implements CommandsInterface {
}
@Override
- public void invokeOemRilRequestRaw(byte[] data, Message response) {
- }
-
- @Override
- public void invokeOemRilRequestStrings(String[] strings, Message response) {
- }
-
- @Override
public void setBandMode (int bandMode, Message response) {
}
diff --git a/com/android/internal/telephony/test/SimulatedCommands.java b/com/android/internal/telephony/test/SimulatedCommands.java
index 0de2bec8..7553b618 100644
--- a/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/com/android/internal/telephony/test/SimulatedCommands.java
@@ -1446,15 +1446,6 @@ public class SimulatedCommands extends BaseCommands
}
@Override
- public void invokeOemRilRequestRaw(byte[] data, Message response) {
- // Just echo back data
- if (response != null) {
- AsyncResult.forMessage(response).result = data;
- response.sendToTarget();
- }
- }
-
- @Override
public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
Message response) {
// Just echo back data
@@ -1464,15 +1455,6 @@ public class SimulatedCommands extends BaseCommands
}
}
- @Override
- public void invokeOemRilRequestStrings(String[] strings, Message response) {
- // Just echo back data
- if (response != null) {
- AsyncResult.forMessage(response).result = strings;
- response.sendToTarget();
- }
- }
-
//***** SimulatedRadioControl
diff --git a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index d746259c..b03e70b6 100644
--- a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -1046,26 +1046,6 @@ public class SimulatedCommandsVerifier implements CommandsInterface {
}
@Override
- public void invokeOemRilRequestRaw(byte[] data, Message response) {
-
- }
-
- @Override
- public void invokeOemRilRequestStrings(String[] strings, Message response) {
-
- }
-
- @Override
- public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
-
- }
-
- @Override
- public void unSetOnUnsolOemHookRaw(Handler h) {
-
- }
-
- @Override
public void sendTerminalResponse(String contents, Message response) {
}
diff --git a/com/android/internal/telephony/uicc/CarrierTestOverride.java b/com/android/internal/telephony/uicc/CarrierTestOverride.java
index 18d29377..755b79f0 100644
--- a/com/android/internal/telephony/uicc/CarrierTestOverride.java
+++ b/com/android/internal/telephony/uicc/CarrierTestOverride.java
@@ -47,6 +47,7 @@ public class CarrierTestOverride {
<carrierTestOverride key="gid2" value="ffffffffffffffff"/>
<carrierTestOverride key="imsi" value="310010123456789"/>
<carrierTestOverride key="spn" value="Verizon"/>
+ <carrierTestOverride key="pnn" value="Verizon network"/>
</carrierTestOverrides>
*/
static final String DATA_CARRIER_TEST_OVERRIDE_PATH =
@@ -60,6 +61,7 @@ public class CarrierTestOverride {
static final String CARRIER_TEST_XML_ITEM_KEY_STRING_GID2 = "gid2";
static final String CARRIER_TEST_XML_ITEM_KEY_STRING_IMSI = "imsi";
static final String CARRIER_TEST_XML_ITEM_KEY_STRING_SPN = "spn";
+ static final String CARRIER_TEST_XML_ITEM_KEY_STRING_PNN = "pnn";
private HashMap<String, String> mCarrierTestParamMap;
@@ -118,6 +120,17 @@ public class CarrierTestOverride {
}
}
+ String getFakePnnHomeName() {
+ try {
+ String pnn = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_PNN);
+ Rlog.d(LOG_TAG, "reading pnn from CarrierTestConfig file: " + pnn);
+ return pnn;
+ } catch (NullPointerException e) {
+ Rlog.w(LOG_TAG, "No pnn in CarrierTestConfig file ");
+ return null;
+ }
+ }
+
private void loadCarrierTestOverrides() {
FileReader carrierTestConfigReader;
diff --git a/com/android/internal/telephony/uicc/IccRecords.java b/com/android/internal/telephony/uicc/IccRecords.java
index af26f5c7..4e4158a1 100644
--- a/com/android/internal/telephony/uicc/IccRecords.java
+++ b/com/android/internal/telephony/uicc/IccRecords.java
@@ -23,8 +23,10 @@ import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.telephony.Rlog;
+import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
@@ -57,6 +59,7 @@ public abstract class IccRecords extends Handler implements IccConstants {
protected RegistrantList mRecordsEventsRegistrants = new RegistrantList();
protected RegistrantList mNewSmsRegistrants = new RegistrantList();
protected RegistrantList mNetworkSelectionModeAutomaticRegistrants = new RegistrantList();
+ protected RegistrantList mSpnUpdatedRegistrants = new RegistrantList();
protected int mRecordsToLoad; // number of pending load requests
@@ -92,6 +95,9 @@ public abstract class IccRecords extends Handler implements IccConstants {
protected String mGid2;
protected String mFakeGid2;
+ protected String mPnnHomeName;
+ protected String mFakePnnHomeName;
+
protected String mPrefLang;
protected PlmnActRecord[] mHplmnActRecords;
@@ -146,12 +152,6 @@ public abstract class IccRecords extends Handler implements IccConstants {
+ " mCi=" + mCi
+ " mFh=" + mFh
+ " mParentApp=" + mParentApp
- + " recordsLoadedRegistrants=" + mRecordsLoadedRegistrants
- + " mImsiReadyRegistrants=" + mImsiReadyRegistrants
- + " mRecordsEventsRegistrants=" + mRecordsEventsRegistrants
- + " mNewSmsRegistrants=" + mNewSmsRegistrants
- + " mNetworkSelectionModeAutomaticRegistrants="
- + mNetworkSelectionModeAutomaticRegistrants
+ " recordsToLoad=" + mRecordsToLoad
+ " adnCache=" + mAdnCache
+ " recordsRequested=" + mRecordsRequested
@@ -164,13 +164,11 @@ public abstract class IccRecords extends Handler implements IccConstants {
+ " isVoiceMailFixed=" + mIsVoiceMailFixed
+ " mImsi=" + ((mImsi != null) ?
mImsi.substring(0, 6) + Rlog.pii(VDBG, mImsi.substring(6)) : "null")
- + (mCarrierTestOverride.isInTestMode()
- ? (" mFakeImsi=" + ((mFakeImsi != null) ? mFakeImsi : "null")) : "")
+ + (mCarrierTestOverride.isInTestMode() ? " mFakeImsi=" + mFakeImsi : "")
+ " mncLength=" + mMncLength
+ " mailboxIndex=" + mMailboxIndex
+ " spn=" + mSpn
- + (mCarrierTestOverride.isInTestMode()
- ? (" mFakeSpn=" + ((mFakeSpn != null) ? mFakeSpn : "null")) : "");
+ + (mCarrierTestOverride.isInTestMode() ? " mFakeSpn=" + mFakeSpn : "");
}
@@ -213,6 +211,9 @@ public abstract class IccRecords extends Handler implements IccConstants {
mFakeSpn = mCarrierTestOverride.getFakeSpn();
log("load mFakeSpn: " + mFakeSpn);
+
+ mFakePnnHomeName = mCarrierTestOverride.getFakePnnHomeName();
+ log("load mFakePnnHomeName: " + mFakePnnHomeName);
}
}
@@ -314,6 +315,22 @@ public abstract class IccRecords extends Handler implements IccConstants {
mImsiReadyRegistrants.remove(h);
}
+ public void registerForSpnUpdate(Handler h, int what, Object obj) {
+ if (mDestroyed.get()) {
+ return;
+ }
+
+ Registrant r = new Registrant(h, what, obj);
+ mSpnUpdatedRegistrants.add(r);
+
+ if (!TextUtils.isEmpty(mSpn)) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ public void unregisterForSpnUpdate(Handler h) {
+ mSpnUpdatedRegistrants.remove(h);
+ }
+
public void registerForRecordsEvents(Handler h, int what, Object obj) {
Registrant r = new Registrant (h, what, obj);
mRecordsEventsRegistrants.add(r);
@@ -406,6 +423,18 @@ public abstract class IccRecords extends Handler implements IccConstants {
}
}
+ /**
+ * Get the PLMN network name on a SIM.
+ * @return null if SIM is not yet ready
+ */
+ public String getPnnHomeName() {
+ if (mCarrierTestOverride.isInTestMode() && mFakePnnHomeName != null) {
+ return mFakePnnHomeName;
+ } else {
+ return mPnnHomeName;
+ }
+ }
+
public void setMsisdnNumber(String alphaTag, String number,
Message onComplete) {
loge("setMsisdn() should not be invoked on base IccRecords");
@@ -457,7 +486,10 @@ public abstract class IccRecords extends Handler implements IccConstants {
}
protected void setServiceProviderName(String spn) {
- mSpn = spn;
+ if (!TextUtils.equals(mSpn, spn)) {
+ mSpnUpdatedRegistrants.notifyRegistrants();
+ mSpn = spn;
+ }
}
/**
@@ -635,13 +667,19 @@ public abstract class IccRecords extends Handler implements IccConstants {
/**
* Returns the SpnDisplayRule based on settings on the SIM and the
- * specified plmn (currently-registered PLMN). See TS 22.101 Annex A
- * and TS 51.011 10.3.11 for details.
+ * current service state. See TS 22.101 Annex A and TS 51.011 10.3.11
+ * for details.
*
* If the SPN is not found on the SIM, the rule is always PLMN_ONLY.
* Generally used for GSM/UMTS and the like SIMs.
+ *
+ * @param serviceState Service state
+ * @return the display rule
+ *
+ * @see #SPN_RULE_SHOW_SPN
+ * @see #SPN_RULE_SHOW_PLMN
*/
- public abstract int getDisplayRule(String plmn);
+ public abstract int getDisplayRule(ServiceState serviceState);
/**
* Return true if "Restriction of menu options for manual PLMN selection"
@@ -821,13 +859,13 @@ public abstract class IccRecords extends Handler implements IccConstants {
pw.println(" mImsi=" + ((mImsi != null) ?
mImsi.substring(0, 6) + Rlog.pii(VDBG, mImsi.substring(6)) : "null"));
if (mCarrierTestOverride.isInTestMode()) {
- pw.println(" mFakeImsi=" + ((mFakeImsi != null) ? mFakeImsi : "null"));
+ pw.println(" mFakeImsi=" + mFakeImsi);
}
pw.println(" mMncLength=" + mMncLength);
pw.println(" mMailboxIndex=" + mMailboxIndex);
pw.println(" mSpn=" + mSpn);
if (mCarrierTestOverride.isInTestMode()) {
- pw.println(" mFakeSpn=" + ((mFakeSpn != null) ? mFakeSpn : "null"));
+ pw.println(" mFakeSpn=" + mFakeSpn);
}
pw.flush();
}
diff --git a/com/android/internal/telephony/uicc/IccUtils.java b/com/android/internal/telephony/uicc/IccUtils.java
index 62d570c8..99a82ad0 100644
--- a/com/android/internal/telephony/uicc/IccUtils.java
+++ b/com/android/internal/telephony/uicc/IccUtils.java
@@ -567,4 +567,12 @@ public class IccUtils {
} while (valueIndex < endIndex);
return result;
}
+
+ public static String getDecimalSubstring(String iccId) {
+ int position;
+ for (position = 0; position < iccId.length(); position ++) {
+ if (!Character.isDigit(iccId.charAt(position))) break;
+ }
+ return iccId.substring( 0, position );
+ }
}
diff --git a/com/android/internal/telephony/uicc/IsimUiccRecords.java b/com/android/internal/telephony/uicc/IsimUiccRecords.java
index 194d2599..3d1fafb0 100644
--- a/com/android/internal/telephony/uicc/IsimUiccRecords.java
+++ b/com/android/internal/telephony/uicc/IsimUiccRecords.java
@@ -27,6 +27,7 @@ import android.content.Intent;
import android.os.AsyncResult;
import android.os.Message;
import android.telephony.Rlog;
+import android.telephony.ServiceState;
import android.text.TextUtils;
import com.android.internal.telephony.CommandsInterface;
@@ -457,7 +458,7 @@ public class IsimUiccRecords extends IccRecords implements IsimRecords {
}
@Override
- public int getDisplayRule(String plmn) {
+ public int getDisplayRule(ServiceState serviceState) {
// Not applicable to Isim
return 0;
}
diff --git a/com/android/internal/telephony/uicc/RuimRecords.java b/com/android/internal/telephony/uicc/RuimRecords.java
index b303ca8e..5cc343e6 100644
--- a/com/android/internal/telephony/uicc/RuimRecords.java
+++ b/com/android/internal/telephony/uicc/RuimRecords.java
@@ -24,6 +24,7 @@ import android.os.AsyncResult;
import android.os.Message;
import android.os.SystemProperties;
import android.telephony.Rlog;
+import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
@@ -868,7 +869,7 @@ public class RuimRecords extends IccRecords {
* No Display rule for RUIMs yet.
*/
@Override
- public int getDisplayRule(String plmn) {
+ public int getDisplayRule(ServiceState serviceState) {
// TODO together with spn
return 0;
}
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 8aed9315..8594f804 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -23,9 +23,11 @@ import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.AsyncResult;
import android.os.Message;
+import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.telephony.Rlog;
+import android.telephony.ServiceState;
import android.telephony.SmsMessage;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -91,8 +93,6 @@ public class SIMRecords extends IccRecords {
// Numeric network codes listed in TS 51.011 EF[SPDI]
ArrayList<String> mSpdiNetworks = null;
- String mPnnHomeName = null;
-
UsimServiceTable mUsimServiceTable;
@Override
@@ -484,7 +484,7 @@ public class SIMRecords extends IccRecords {
}
}
- // Validate data is !null.
+ // Validate data is not null and not empty.
private boolean validEfCfis(byte[] data) {
if (data != null) {
if (data[0] < 1 || data[0] > 4) {
@@ -492,7 +492,12 @@ public class SIMRecords extends IccRecords {
// 1 and 4 according to ETSI TS 131 102 v11.3.0 section 4.2.64.
logw("MSP byte: " + data[0] + " is not between 1 and 4", null);
}
- return true;
+ // empty EF_CFIS should be considered as call forward disabled
+ for (byte b : data) {
+ if (b != (byte) 0xFF) {
+ return true;
+ }
+ }
}
return false;
}
@@ -1068,6 +1073,7 @@ public class SIMRecords extends IccRecords {
if (tlv.getTag() == TAG_FULL_NETWORK_NAME) {
mPnnHomeName = IccUtils.networkNameToString(
tlv.getData(), 0, tlv.getData().length);
+ log("PNN: " + mPnnHomeName);
break;
}
}
@@ -1840,14 +1846,20 @@ public class SIMRecords extends IccRecords {
/**
* Returns the SpnDisplayRule based on settings on the SIM and the
- * specified plmn (currently-registered PLMN). See TS 22.101 Annex A
- * and TS 51.011 10.3.11 for details.
+ * current service state. See TS 22.101 Annex A and TS 51.011 10.3.11
+ * for details.
*
* If the SPN is not found on the SIM or is empty, the rule is
* always PLMN_ONLY.
+ *
+ * @param serviceState Service state
+ * @return the display rule
+ *
+ * @see #SPN_RULE_SHOW_SPN
+ * @see #SPN_RULE_SHOW_PLMN
*/
@Override
- public int getDisplayRule(String plmn) {
+ public int getDisplayRule(ServiceState serviceState) {
int rule;
if (mParentApp != null && mParentApp.getUiccCard() != null &&
@@ -1857,7 +1869,8 @@ public class SIMRecords extends IccRecords {
} else if (TextUtils.isEmpty(getServiceProviderName()) || mSpnDisplayCondition == -1) {
// No EF_SPN content was found on the SIM, or not yet loaded. Just show ONS.
rule = SPN_RULE_SHOW_PLMN;
- } else if (isOnMatchingPlmn(plmn)) {
+ } else if (useRoamingFromServiceState() ? !serviceState.getRoaming()
+ : isOnMatchingPlmn(serviceState.getOperatorNumeric())) {
rule = SPN_RULE_SHOW_SPN;
if ((mSpnDisplayCondition & 0x01) == 0x01) {
// ONS required when registered to HPLMN or PLMN in EF_SPDI
@@ -1873,6 +1886,21 @@ public class SIMRecords extends IccRecords {
return rule;
}
+ private boolean useRoamingFromServiceState() {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(
+ SubscriptionController.getInstance().getSubIdUsingPhoneId(
+ mParentApp.getPhoneId()));
+ if (b != null && b.getBoolean(CarrierConfigManager
+ .KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Checks if plmn is HPLMN or on the spdiNetworks list.
*/
@@ -2210,11 +2238,15 @@ public class SIMRecords extends IccRecords {
pw.println(" mUsimServiceTable=" + mUsimServiceTable);
pw.println(" mGid1=" + mGid1);
if (mCarrierTestOverride.isInTestMode()) {
- pw.println(" mFakeGid1=" + ((mFakeGid1 != null) ? mFakeGid1 : "null"));
+ pw.println(" mFakeGid1=" + mFakeGid1);
}
pw.println(" mGid2=" + mGid2);
if (mCarrierTestOverride.isInTestMode()) {
- pw.println(" mFakeGid2=" + ((mFakeGid2 != null) ? mFakeGid2 : "null"));
+ pw.println(" mFakeGid2=" + mFakeGid2);
+ }
+ pw.println(" mPnnHomeName=" + mPnnHomeName);
+ if (mCarrierTestOverride.isInTestMode()) {
+ pw.println(" mFakePnnHomeName=" + mFakePnnHomeName);
}
pw.println(" mPlmnActRecords[]=" + Arrays.toString(mPlmnActRecords));
pw.println(" mOplmnActRecords[]=" + Arrays.toString(mOplmnActRecords));
diff --git a/com/android/internal/telephony/uicc/UiccCard.java b/com/android/internal/telephony/uicc/UiccCard.java
index baad60b9..3a71ad3f 100644
--- a/com/android/internal/telephony/uicc/UiccCard.java
+++ b/com/android/internal/telephony/uicc/UiccCard.java
@@ -33,11 +33,13 @@ import android.os.AsyncResult;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Registrant;
import android.os.RegistrantList;
import android.preference.PreferenceManager;
import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -47,6 +49,7 @@ import android.view.WindowManager;
import com.android.internal.R;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.CommandsInterface.RadioState;
+import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.cat.CatService;
import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -744,7 +747,21 @@ public class UiccCard {
return null;
}
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
- return sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
+ String brandName = sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
+ if (brandName == null) {
+ // Check if CarrierConfig sets carrier name
+ CarrierConfigManager manager = (CarrierConfigManager)
+ mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ int subId = SubscriptionController.getInstance().getSubIdUsingPhoneId(mPhoneId);
+ if (manager != null) {
+ PersistableBundle bundle = manager.getConfigForSubId(subId);
+ if (bundle != null && bundle.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) {
+ brandName = bundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+ }
+ }
+ }
+ return brandName;
}
public String getIccId() {
diff --git a/com/android/internal/telephony/uicc/UiccProfile.java b/com/android/internal/telephony/uicc/UiccProfile.java
new file mode 100644
index 00000000..3e7d2d7f
--- /dev/null
+++ b/com/android/internal/telephony/uicc/UiccProfile.java
@@ -0,0 +1,751 @@
+/*
+ * 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.uicc;
+
+import android.app.AlertDialog;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
+import com.android.internal.telephony.uicc.IccCardStatus.PinState;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * This class represents the carrier profiles in the {@link UiccCard}. Each profile contains
+ * multiple {@link UiccCardApplication}, one {@link UiccCarrierPrivilegeRules} and one
+ * {@link CatService}.
+ *
+ * Profile is related to {@link android.telephony.SubscriptionInfo} but those two concepts are
+ * different. {@link android.telephony.SubscriptionInfo} contains all the subscription information
+ * while Profile contains all the {@link UiccCardApplication} which will be used to fetch those
+ * subscription information from the {@link UiccCard}.
+ *
+ * {@hide}
+ */
+public class UiccProfile {
+ protected static final String LOG_TAG = "UiccProfile";
+ protected static final boolean DBG = true;
+
+ private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
+
+ private final Object mLock = new Object();
+ private PinState mUniversalPinState;
+ private int mGsmUmtsSubscriptionAppIndex;
+ private int mCdmaSubscriptionAppIndex;
+ private int mImsSubscriptionAppIndex;
+ private UiccCardApplication[] mUiccApplications =
+ new UiccCardApplication[IccCardStatus.CARD_MAX_APPS];
+ private Context mContext;
+ private CommandsInterface mCi;
+ private UiccCard mUiccCard; //parent
+ private CatService mCatService;
+ private UiccCarrierPrivilegeRules mCarrierPrivilegeRules;
+
+ private RegistrantList mCarrierPrivilegeRegistrants = new RegistrantList();
+
+ private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 15;
+ private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 16;
+ private static final int EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE = 17;
+ private static final int EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE = 18;
+ private static final int EVENT_SIM_IO_DONE = 19;
+ private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 20;
+
+ private static final LocalLog sLocalLog = new LocalLog(100);
+
+ private final int mPhoneId;
+
+ public UiccProfile(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId,
+ UiccCard uiccCard) {
+ if (DBG) log("Creating profile");
+ mUiccCard = uiccCard;
+ mPhoneId = phoneId;
+ update(c, ci, ics);
+ }
+
+ /**
+ * Dispose the UiccProfile.
+ */
+ public void dispose() {
+ synchronized (mLock) {
+ if (DBG) log("Disposing profile");
+ if (mCatService != null) mCatService.dispose();
+ for (UiccCardApplication app : mUiccApplications) {
+ if (app != null) {
+ app.dispose();
+ }
+ }
+ mCatService = null;
+ mUiccApplications = null;
+ mCarrierPrivilegeRules = null;
+ }
+ }
+
+ /**
+ * Update the UiccProfile.
+ */
+ public void update(Context c, CommandsInterface ci, IccCardStatus ics) {
+ synchronized (mLock) {
+ mUniversalPinState = ics.mUniversalPinState;
+ mGsmUmtsSubscriptionAppIndex = ics.mGsmUmtsSubscriptionAppIndex;
+ mCdmaSubscriptionAppIndex = ics.mCdmaSubscriptionAppIndex;
+ mImsSubscriptionAppIndex = ics.mImsSubscriptionAppIndex;
+ mContext = c;
+ mCi = ci;
+
+ //update applications
+ if (DBG) log(ics.mApplications.length + " applications");
+ for (int i = 0; i < mUiccApplications.length; i++) {
+ if (mUiccApplications[i] == null) {
+ //Create newly added Applications
+ if (i < ics.mApplications.length) {
+ mUiccApplications[i] = new UiccCardApplication(mUiccCard,
+ ics.mApplications[i], mContext, mCi);
+ }
+ } else if (i >= ics.mApplications.length) {
+ //Delete removed applications
+ mUiccApplications[i].dispose();
+ mUiccApplications[i] = null;
+ } else {
+ //Update the rest
+ mUiccApplications[i].update(ics.mApplications[i], mContext, mCi);
+ }
+ }
+
+ createAndUpdateCatServiceLocked();
+
+ log("Before privilege rules: " + mCarrierPrivilegeRules);
+ if (mCarrierPrivilegeRules == null) {
+ mCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccCard,
+ mHandler.obtainMessage(EVENT_CARRIER_PRIVILEGES_LOADED));
+ }
+
+ sanitizeApplicationIndexesLocked();
+ }
+ }
+
+ private void createAndUpdateCatServiceLocked() {
+ if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {
+ // Initialize or Reinitialize CatService
+ if (mCatService == null) {
+ mCatService = CatService.getInstance(mCi, mContext, mUiccCard, mPhoneId);
+ } else {
+ mCatService.update(mCi, mContext, mUiccCard);
+ }
+ } else {
+ if (mCatService != null) {
+ mCatService.dispose();
+ }
+ mCatService = null;
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ if (DBG) log("UiccProfile finalized");
+ }
+
+ /**
+ * This function makes sure that application indexes are valid
+ * and resets invalid indexes. (This should never happen, but in case
+ * RIL misbehaves we need to manage situation gracefully)
+ */
+ private void sanitizeApplicationIndexesLocked() {
+ mGsmUmtsSubscriptionAppIndex =
+ checkIndexLocked(
+ mGsmUmtsSubscriptionAppIndex, AppType.APPTYPE_SIM, AppType.APPTYPE_USIM);
+ mCdmaSubscriptionAppIndex =
+ checkIndexLocked(
+ mCdmaSubscriptionAppIndex, AppType.APPTYPE_RUIM, AppType.APPTYPE_CSIM);
+ mImsSubscriptionAppIndex =
+ checkIndexLocked(mImsSubscriptionAppIndex, AppType.APPTYPE_ISIM, null);
+ }
+
+ private int checkIndexLocked(int index, AppType expectedAppType, AppType altExpectedAppType) {
+ if (mUiccApplications == null || index >= mUiccApplications.length) {
+ loge("App index " + index + " is invalid since there are no applications");
+ return -1;
+ }
+
+ if (index < 0) {
+ // This is normal. (i.e. no application of this type)
+ return -1;
+ }
+
+ if (mUiccApplications[index].getType() != expectedAppType
+ && mUiccApplications[index].getType() != altExpectedAppType) {
+ loge("App index " + index + " is invalid since it's not "
+ + expectedAppType + " and not " + altExpectedAppType);
+ return -1;
+ }
+
+ // Seems to be valid
+ return index;
+ }
+
+ /**
+ * Registers the handler when carrier privilege rules are loaded.
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ public void registerForCarrierPrivilegeRulesLoaded(Handler h, int what, Object obj) {
+ synchronized (mLock) {
+ Registrant r = new Registrant(h, what, obj);
+
+ mCarrierPrivilegeRegistrants.add(r);
+
+ if (areCarrierPriviligeRulesLoaded()) {
+ r.notifyRegistrant();
+ }
+ }
+ }
+
+ /**
+ * Unregister for notifications when carrier privilege rules are loaded.
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForCarrierPrivilegeRulesLoaded(Handler h) {
+ synchronized (mLock) {
+ mCarrierPrivilegeRegistrants.remove(h);
+ }
+ }
+
+ protected Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
+ case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
+ case EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE:
+ case EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE:
+ case EVENT_SIM_IO_DONE:
+ AsyncResult ar = (AsyncResult) msg.obj;
+ if (ar.exception != null) {
+ loglocal("Exception: " + ar.exception);
+ log("Error in SIM access with exception" + ar.exception);
+ }
+ AsyncResult.forMessage((Message) ar.userObj, ar.result, ar.exception);
+ ((Message) ar.userObj).sendToTarget();
+ break;
+ case EVENT_CARRIER_PRIVILEGES_LOADED:
+ onCarrierPriviligesLoadedMessage();
+ break;
+ default:
+ loge("Unknown Event " + msg.what);
+ }
+ }
+ };
+
+ private boolean isPackageInstalled(String pkgName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ pm.getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES);
+ if (DBG) log(pkgName + " is installed.");
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DBG) log(pkgName + " is not installed.");
+ return false;
+ }
+ }
+
+ private class ClickListener implements DialogInterface.OnClickListener {
+ String mPkgName;
+ ClickListener(String pkgName) {
+ this.mPkgName = pkgName;
+ }
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ synchronized (mLock) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ Intent market = new Intent(Intent.ACTION_VIEW);
+ market.setData(Uri.parse("market://details?id=" + mPkgName));
+ market.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(market);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ if (DBG) log("Not now clicked for carrier app dialog.");
+ }
+ }
+ }
+ }
+
+ private void promptInstallCarrierApp(String pkgName) {
+ DialogInterface.OnClickListener listener = new ClickListener(pkgName);
+
+ Resources r = Resources.getSystem();
+ String message = r.getString(R.string.carrier_app_dialog_message);
+ String buttonTxt = r.getString(R.string.carrier_app_dialog_button);
+ String notNowTxt = r.getString(R.string.carrier_app_dialog_not_now);
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setMessage(message)
+ .setNegativeButton(notNowTxt, listener)
+ .setPositiveButton(buttonTxt, listener)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ private void onCarrierPriviligesLoadedMessage() {
+ UsageStatsManager usm = (UsageStatsManager) mContext.getSystemService(
+ Context.USAGE_STATS_SERVICE);
+ if (usm != null) {
+ usm.onCarrierPrivilegedAppsChanged();
+ }
+ synchronized (mLock) {
+ mCarrierPrivilegeRegistrants.notifyRegistrants();
+ String whitelistSetting = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CARRIER_APP_WHITELIST);
+ if (TextUtils.isEmpty(whitelistSetting)) {
+ return;
+ }
+ HashSet<String> carrierAppSet = new HashSet<String>(
+ Arrays.asList(whitelistSetting.split("\\s*;\\s*")));
+ if (carrierAppSet.isEmpty()) {
+ return;
+ }
+
+ List<String> pkgNames = mCarrierPrivilegeRules.getPackageNames();
+ for (String pkgName : pkgNames) {
+ if (!TextUtils.isEmpty(pkgName) && carrierAppSet.contains(pkgName)
+ && !isPackageInstalled(pkgName)) {
+ promptInstallCarrierApp(pkgName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check whether the specified type of application exists in the profile.
+ *
+ * @param type UICC application type.
+ */
+ public boolean isApplicationOnIcc(IccCardApplicationStatus.AppType type) {
+ synchronized (mLock) {
+ for (int i = 0; i < mUiccApplications.length; i++) {
+ if (mUiccApplications[i] != null && mUiccApplications[i].getType() == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Return the universal pin state of the profile.
+ */
+ public PinState getUniversalPinState() {
+ synchronized (mLock) {
+ return mUniversalPinState;
+ }
+ }
+
+ /**
+ * Return the application of the specified family.
+ *
+ * @param family UICC application family.
+ * @return application corresponding to family or a null if no match found
+ */
+ public UiccCardApplication getApplication(int family) {
+ synchronized (mLock) {
+ int index = IccCardStatus.CARD_MAX_APPS;
+ switch (family) {
+ case UiccController.APP_FAM_3GPP:
+ index = mGsmUmtsSubscriptionAppIndex;
+ break;
+ case UiccController.APP_FAM_3GPP2:
+ index = mCdmaSubscriptionAppIndex;
+ break;
+ case UiccController.APP_FAM_IMS:
+ index = mImsSubscriptionAppIndex;
+ break;
+ }
+ if (index >= 0 && index < mUiccApplications.length) {
+ return mUiccApplications[index];
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Return the application with the index of the array.
+ *
+ * @param index Index of the application array.
+ * @return application corresponding to index or a null if no match found
+ */
+ public UiccCardApplication getApplicationIndex(int index) {
+ synchronized (mLock) {
+ if (index >= 0 && index < mUiccApplications.length) {
+ return mUiccApplications[index];
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Returns the SIM application of the specified type.
+ *
+ * @param type ICC application type
+ * (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+ * @return application corresponding to type or a null if no match found
+ */
+ public UiccCardApplication getApplicationByType(int type) {
+ synchronized (mLock) {
+ for (int i = 0; i < mUiccApplications.length; i++) {
+ if (mUiccApplications[i] != null
+ && mUiccApplications[i].getType().ordinal() == type) {
+ return mUiccApplications[i];
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Resets the application with the input AID. Returns true if any changes were made.
+ *
+ * A null aid implies a card level reset - all applications must be reset.
+ */
+ public boolean resetAppWithAid(String aid) {
+ synchronized (mLock) {
+ boolean changed = false;
+ for (int i = 0; i < mUiccApplications.length; i++) {
+ if (mUiccApplications[i] != null
+ && (TextUtils.isEmpty(aid) || aid.equals(mUiccApplications[i].getAid()))) {
+ // Delete removed applications
+ mUiccApplications[i].dispose();
+ mUiccApplications[i] = null;
+ changed = true;
+ }
+ }
+ if (TextUtils.isEmpty(aid)) {
+ if (mCarrierPrivilegeRules != null) {
+ mCarrierPrivilegeRules = null;
+ changed = true;
+ }
+ if (mCatService != null) {
+ mCatService.dispose();
+ mCatService = null;
+ changed = true;
+ }
+ }
+ return changed;
+ }
+ }
+
+ /**
+ * Exposes {@link CommandsInterface#iccOpenLogicalChannel}
+ */
+ public void iccOpenLogicalChannel(String aid, int p2, Message response) {
+ loglocal("Open Logical Channel: " + aid + " , " + p2 + " by pid:" + Binder.getCallingPid()
+ + " uid:" + Binder.getCallingUid());
+ mCi.iccOpenLogicalChannel(aid, p2,
+ mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, response));
+ }
+
+ /**
+ * Exposes {@link CommandsInterface#iccCloseLogicalChannel}
+ */
+ public void iccCloseLogicalChannel(int channel, Message response) {
+ loglocal("Close Logical Channel: " + channel);
+ mCi.iccCloseLogicalChannel(channel,
+ mHandler.obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE, response));
+ }
+
+ /**
+ * Exposes {@link CommandsInterface#iccTransmitApduLogicalChannel}
+ */
+ public void iccTransmitApduLogicalChannel(int channel, int cla, int command,
+ int p1, int p2, int p3, String data, Message response) {
+ mCi.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3,
+ data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response));
+ }
+
+ /**
+ * Exposes {@link CommandsInterface#iccTransmitApduBasicChannel}
+ */
+ public void iccTransmitApduBasicChannel(int cla, int command,
+ int p1, int p2, int p3, String data, Message response) {
+ mCi.iccTransmitApduBasicChannel(cla, command, p1, p2, p3,
+ data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE, response));
+ }
+
+ /**
+ * Exposes {@link CommandsInterface#iccIO}
+ */
+ public void iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
+ String pathID, Message response) {
+ mCi.iccIO(command, fileID, pathID, p1, p2, p3, null, null,
+ mHandler.obtainMessage(EVENT_SIM_IO_DONE, response));
+ }
+
+ /**
+ * Exposes {@link CommandsInterface#sendEnvelopeWithStatus}
+ */
+ public void sendEnvelopeWithStatus(String contents, Message response) {
+ mCi.sendEnvelopeWithStatus(contents, response);
+ }
+
+ /**
+ * Returns number of applications on this card
+ */
+ public int getNumApplications() {
+ int count = 0;
+ for (UiccCardApplication a : mUiccApplications) {
+ if (a != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns the id of the phone which is associated with this profile.
+ */
+ public int getPhoneId() {
+ return mPhoneId;
+ }
+
+ /**
+ * Returns true iff carrier privileges rules are null (dont need to be loaded) or loaded.
+ */
+ public boolean areCarrierPriviligeRulesLoaded() {
+ UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+ return carrierPrivilegeRules == null
+ || carrierPrivilegeRules.areCarrierPriviligeRulesLoaded();
+ }
+
+ /**
+ * Returns true if there are some carrier privilege rules loaded and specified.
+ */
+ public boolean hasCarrierPrivilegeRules() {
+ UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+ return carrierPrivilegeRules != null && carrierPrivilegeRules.hasCarrierPrivilegeRules();
+ }
+
+ /**
+ * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatus}.
+ */
+ public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
+ UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+ return carrierPrivilegeRules == null
+ ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+ carrierPrivilegeRules.getCarrierPrivilegeStatus(signature, packageName);
+ }
+
+ /**
+ * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatus}.
+ */
+ public int getCarrierPrivilegeStatus(PackageManager packageManager, String packageName) {
+ UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+ return carrierPrivilegeRules == null
+ ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+ carrierPrivilegeRules.getCarrierPrivilegeStatus(packageManager, packageName);
+ }
+
+ /**
+ * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatus}.
+ */
+ public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
+ UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+ return carrierPrivilegeRules == null
+ ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+ carrierPrivilegeRules.getCarrierPrivilegeStatus(packageInfo);
+ }
+
+ /**
+ * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatusForCurrentTransaction}.
+ */
+ public int getCarrierPrivilegeStatusForCurrentTransaction(PackageManager packageManager) {
+ UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+ return carrierPrivilegeRules == null
+ ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+ carrierPrivilegeRules.getCarrierPrivilegeStatusForCurrentTransaction(
+ packageManager);
+ }
+
+ /**
+ * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPackageNamesForIntent}.
+ */
+ public List<String> getCarrierPackageNamesForIntent(
+ PackageManager packageManager, Intent intent) {
+ UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+ return carrierPrivilegeRules == null ? null :
+ carrierPrivilegeRules.getCarrierPackageNamesForIntent(
+ packageManager, intent);
+ }
+
+ /** Returns a reference to the current {@link UiccCarrierPrivilegeRules}. */
+ private UiccCarrierPrivilegeRules getCarrierPrivilegeRules() {
+ synchronized (mLock) {
+ return mCarrierPrivilegeRules;
+ }
+ }
+
+ /**
+ * Sets the overridden operator brand.
+ */
+ public boolean setOperatorBrandOverride(String brand) {
+ log("setOperatorBrandOverride: " + brand);
+ log("current iccId: " + getIccId());
+
+ String iccId = getIccId();
+ if (TextUtils.isEmpty(iccId)) {
+ return false;
+ }
+
+ SharedPreferences.Editor spEditor =
+ PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+ String key = OPERATOR_BRAND_OVERRIDE_PREFIX + iccId;
+ if (brand == null) {
+ spEditor.remove(key).commit();
+ } else {
+ spEditor.putString(key, brand).commit();
+ }
+ return true;
+ }
+
+ /**
+ * Returns the overridden operator brand.
+ */
+ public String getOperatorBrandOverride() {
+ String iccId = getIccId();
+ if (TextUtils.isEmpty(iccId)) {
+ return null;
+ }
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ return sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
+ }
+
+ /**
+ * Returns the iccid of the profile.
+ */
+ public String getIccId() {
+ // ICCID should be same across all the apps.
+ for (UiccCardApplication app : mUiccApplications) {
+ if (app != null) {
+ IccRecords ir = app.getIccRecords();
+ if (ir != null && ir.getIccId() != null) {
+ return ir.getIccId();
+ }
+ }
+ }
+ return null;
+ }
+
+ private void log(String msg) {
+ Rlog.d(LOG_TAG, msg);
+ }
+
+ private void loge(String msg) {
+ Rlog.e(LOG_TAG, msg);
+ }
+
+ private void loglocal(String msg) {
+ if (DBG) sLocalLog.log(msg);
+ }
+
+ /**
+ * Dump
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("UiccProfile:");
+ pw.println(" mCi=" + mCi);
+ pw.println(" mCatService=" + mCatService);
+ for (int i = 0; i < mCarrierPrivilegeRegistrants.size(); i++) {
+ pw.println(" mCarrierPrivilegeRegistrants[" + i + "]="
+ + ((Registrant) mCarrierPrivilegeRegistrants.get(i)).getHandler());
+ }
+ pw.println(" mUniversalPinState=" + mUniversalPinState);
+ pw.println(" mGsmUmtsSubscriptionAppIndex=" + mGsmUmtsSubscriptionAppIndex);
+ pw.println(" mCdmaSubscriptionAppIndex=" + mCdmaSubscriptionAppIndex);
+ pw.println(" mImsSubscriptionAppIndex=" + mImsSubscriptionAppIndex);
+ pw.println(" mUiccApplications: length=" + mUiccApplications.length);
+ for (int i = 0; i < mUiccApplications.length; i++) {
+ if (mUiccApplications[i] == null) {
+ pw.println(" mUiccApplications[" + i + "]=" + null);
+ } else {
+ pw.println(" mUiccApplications[" + i + "]="
+ + mUiccApplications[i].getType() + " " + mUiccApplications[i]);
+ }
+ }
+ pw.println();
+ // Print details of all applications
+ for (UiccCardApplication app : mUiccApplications) {
+ if (app != null) {
+ app.dump(fd, pw, args);
+ pw.println();
+ }
+ }
+ // Print details of all IccRecords
+ for (UiccCardApplication app : mUiccApplications) {
+ if (app != null) {
+ IccRecords ir = app.getIccRecords();
+ if (ir != null) {
+ ir.dump(fd, pw, args);
+ pw.println();
+ }
+ }
+ }
+ // Print UiccCarrierPrivilegeRules and registrants.
+ if (mCarrierPrivilegeRules == null) {
+ pw.println(" mCarrierPrivilegeRules: null");
+ } else {
+ pw.println(" mCarrierPrivilegeRules: " + mCarrierPrivilegeRules);
+ mCarrierPrivilegeRules.dump(fd, pw, args);
+ }
+ pw.println(" mCarrierPrivilegeRegistrants: size=" + mCarrierPrivilegeRegistrants.size());
+ for (int i = 0; i < mCarrierPrivilegeRegistrants.size(); i++) {
+ pw.println(" mCarrierPrivilegeRegistrants[" + i + "]="
+ + ((Registrant) mCarrierPrivilegeRegistrants.get(i)).getHandler());
+ }
+ pw.flush();
+ pw.println("sLocalLog:");
+ sLocalLog.dump(fd, pw, args);
+ pw.flush();
+ }
+}
diff --git a/com/android/internal/util/Predicate.java b/com/android/internal/util/Predicate.java
index 1b5eaff6..e87f489f 100644
--- a/com/android/internal/util/Predicate.java
+++ b/com/android/internal/util/Predicate.java
@@ -27,6 +27,7 @@ package com.android.internal.util;
* strongly encouraged to state this fact clearly in their API documentation.
*
* @deprecated Use {@code java.util.function.Predicate} instead.
+ * This must not be used outside frameworks/base/test-runner.
*/
@Deprecated
public interface Predicate<T> {
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
index ad84353f..c22be2cc 100644
--- a/com/android/internal/util/RingBuffer.java
+++ b/com/android/internal/util/RingBuffer.java
@@ -45,6 +45,17 @@ public class RingBuffer<T> {
return (int) Math.min(mBuffer.length, (long) mCursor);
}
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ public void clear() {
+ for (int i = 0; i < size(); ++i) {
+ mBuffer[i] = null;
+ }
+ mCursor = 0;
+ }
+
public void append(T t) {
mBuffer[indexOf(mCursor++)] = t;
}
diff --git a/com/android/internal/util/StateMachine.java b/com/android/internal/util/StateMachine.java
index 8d9630fe..e5ad1f47 100644
--- a/com/android/internal/util/StateMachine.java
+++ b/com/android/internal/util/StateMachine.java
@@ -804,7 +804,7 @@ public class StateMachine {
/** State that processed the message */
State msgProcessedState = null;
- if (mIsConstructionCompleted) {
+ if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
/** Normal path */
msgProcessedState = processMsg(msg);
} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
diff --git a/com/android/internal/widget/ImageFloatingTextView.java b/com/android/internal/widget/ImageFloatingTextView.java
index 7870333c..09f7282f 100644
--- a/com/android/internal/widget/ImageFloatingTextView.java
+++ b/com/android/internal/widget/ImageFloatingTextView.java
@@ -176,8 +176,4 @@ public class ImageFloatingTextView extends TextView {
}
return false;
}
-
- public int getLayoutHeight() {
- return getLayout().getHeight();
- }
}
diff --git a/com/android/internal/widget/MessagingGroup.java b/com/android/internal/widget/MessagingGroup.java
new file mode 100644
index 00000000..792f9212
--- /dev/null
+++ b/com/android/internal/widget/MessagingGroup.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+ private static Pools.SimplePool<MessagingGroup> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private MessagingLinearLayout mMessageContainer;
+ private ImageFloatingTextView mSenderName;
+ private ImageView mAvatarView;
+ private String mAvatarSymbol = "";
+ private int mLayoutColor;
+ private CharSequence mAvatarName = "";
+ private Icon mAvatarIcon;
+ private ColorFilter mMessageBackgroundFilter;
+ private int mTextColor;
+ private List<MessagingMessage> mMessages;
+ private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
+ private boolean mFirstLayout;
+ private boolean mIsHidingAnimated;
+
+ public MessagingGroup(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMessageContainer = findViewById(R.id.group_message_container);
+ mSenderName = findViewById(R.id.message_name);
+ mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ mAvatarView = findViewById(R.id.message_icon);
+ }
+
+ public void setSender(CharSequence sender) {
+ if (sender == null) {
+ mAvatarView.setVisibility(GONE);
+ mSenderName.setVisibility(GONE);
+ setGravity(Gravity.END);
+ mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
+ PorterDuff.Mode.SRC_ATOP);
+ mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
+ : Color.WHITE;
+ } else {
+ mSenderName.setText(sender);
+ mAvatarView.setVisibility(VISIBLE);
+ mSenderName.setVisibility(VISIBLE);
+ setGravity(Gravity.START);
+ mMessageBackgroundFilter = null;
+ mTextColor = getNormalTextColor();
+ }
+ }
+
+ private int getNormalTextColor() {
+ return mContext.getColor(R.color.notification_primary_text_color_light);
+ }
+
+ public void setAvatar(Icon icon) {
+ mAvatarIcon = icon;
+ mAvatarView.setImageIcon(icon);
+ mAvatarSymbol = "";
+ mLayoutColor = 0;
+ mAvatarName = "";
+ }
+
+ static MessagingGroup createGroup(MessagingLinearLayout layout) {;
+ MessagingGroup createdGroup = sInstancePool.acquire();
+ if (createdGroup == null) {
+ createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_group, layout,
+ false);
+ createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ }
+ layout.addView(createdGroup);
+ return createdGroup;
+ }
+
+ public void removeMessage(MessagingMessage messagingMessage) {
+ mMessageContainer.removeView(messagingMessage);
+ Runnable recycleRunnable = () -> {
+ mMessageContainer.removeTransientView(messagingMessage);
+ messagingMessage.recycle();
+ if (mMessageContainer.getChildCount() == 0
+ && mMessageContainer.getTransientViewCount() == 0) {
+ ViewParent parent = getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(MessagingGroup.this);
+ }
+ setAvatar(null);
+ mAvatarView.setAlpha(1.0f);
+ mAvatarView.setTranslationY(0.0f);
+ mSenderName.setAlpha(1.0f);
+ mSenderName.setTranslationY(0.0f);
+ sInstancePool.release(MessagingGroup.this);
+ }
+ };
+ if (isShown()) {
+ mMessageContainer.addTransientView(messagingMessage, 0);
+ performRemoveAnimation(messagingMessage, recycleRunnable);
+ if (mMessageContainer.getChildCount() == 0) {
+ removeGroupAnimated(null);
+ }
+ } else {
+ recycleRunnable.run();
+ }
+
+ }
+
+ private void removeGroupAnimated(Runnable endAction) {
+ MessagingPropertyAnimator.fadeOut(mAvatarView, null);
+ MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ MessagingPropertyAnimator.fadeOut(mSenderName, null);
+ MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ boolean endActionTriggered = false;
+ for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide
+ && !((MessagingLinearLayout.LayoutParams) lp).visibleBefore) {
+ continue;
+ }
+ Runnable childEndAction = endActionTriggered ? null : endAction;
+ performRemoveAnimation(child, childEndAction);
+ endActionTriggered = true;
+ }
+ if (!endActionTriggered && endAction != null) {
+ endAction.run();
+ }
+ }
+
+ public void performRemoveAnimation(View message,
+ Runnable recycleRunnable) {
+ MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+ MessagingPropertyAnimator.startLocalTranslationTo(message,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ }
+
+ public CharSequence getSenderName() {
+ return mSenderName.getText();
+ }
+
+ public void setSenderVisible(boolean visible) {
+ mSenderName.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean hasNormal = false;
+ for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
+ if (type == MEASURED_TOO_SMALL) {
+ if (hasNormal) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_TOO_SMALL;
+ }
+ } else if (type == MEASURED_SHORTENED) {
+ return MEASURED_SHORTENED;
+ } else {
+ hasNormal = true;
+ }
+ }
+ }
+ return MEASURED_NORMAL;
+ }
+
+ @Override
+ public int getConsumedLines() {
+ int result = 0;
+ for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
+ }
+ }
+ // A group is usually taking up quite some space with the padding and the name, let's add 1
+ return result + 1;
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ mMessageContainer.setMaxDisplayedLines(lines);
+ }
+
+ @Override
+ public void hideAnimated() {
+ setIsHidingAnimated(true);
+ removeGroupAnimated(() -> setIsHidingAnimated(false));
+ }
+
+ @Override
+ public boolean isHidingAnimated() {
+ return mIsHidingAnimated;
+ }
+
+ private void setIsHidingAnimated(boolean isHiding) {
+ ViewParent parent = getParent();
+ mIsHidingAnimated = isHiding;
+ invalidate();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).invalidate();
+ }
+ }
+
+ public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
+ && layoutColor == mLayoutColor) {
+ return mAvatarIcon;
+ }
+ return null;
+ }
+
+ public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
+ || layoutColor != mLayoutColor) {
+ setAvatar(cachedIcon);
+ mAvatarSymbol = avatarSymbol;
+ mLayoutColor = layoutColor;
+ mAvatarName = avatarName;
+ }
+ }
+
+ public void setLayoutColor(int layoutColor) {
+ mLayoutColor = layoutColor;
+ }
+
+ public void setMessages(List<MessagingMessage> group) {
+ // Let's now make sure all children are added and in the correct order
+ for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
+ MessagingMessage message = group.get(messageIndex);
+ if (message.getGroup() != this) {
+ message.setMessagingGroup(this);
+ ViewParent parent = mMessageContainer.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(message);
+ }
+ mMessageContainer.addView(message, messageIndex);
+ mAddedMessages.add(message);
+ }
+ if (messageIndex != mMessageContainer.indexOfChild(message)) {
+ mMessageContainer.removeView(message);
+ mMessageContainer.addView(message, messageIndex);
+ }
+ // Let's make sure the message color is correct
+ Drawable targetDrawable = message.getBackground();
+
+ if (targetDrawable != null) {
+ targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
+ }
+ message.setTextColor(mTextColor);
+ }
+ mMessages = group;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!mAddedMessages.isEmpty()) {
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ for (MessagingMessage message : mAddedMessages) {
+ if (!message.isShown()) {
+ continue;
+ }
+ MessagingPropertyAnimator.fadeIn(message);
+ if (!mFirstLayout) {
+ MessagingPropertyAnimator.startLocalTranslationFrom(message,
+ message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+ }
+ }
+ mAddedMessages.clear();
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+ mFirstLayout = false;
+ }
+
+ /**
+ * Calculates the group compatibility between this and another group.
+ *
+ * @param otherGroup the other group to compare it with
+ *
+ * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if
+ * they match.
+ */
+ public int calculateGroupCompatibility(MessagingGroup otherGroup) {
+ if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) {
+ int result = 1;
+ for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) {
+ MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i);
+ MessagingMessage otherMessage = otherGroup.mMessages.get(
+ otherGroup.mMessages.size() - 1 - i);
+ if (!ownMessage.sameAs(otherMessage)) {
+ return result;
+ }
+ result++;
+ }
+ return result;
+ }
+ return 0;
+ }
+
+ public View getSender() {
+ return mSenderName;
+ }
+
+ public View getAvatar() {
+ return mAvatarView;
+ }
+
+ public MessagingLinearLayout getMessageContainer() {
+ return mMessageContainer;
+ }
+}
diff --git a/com/android/internal/widget/MessagingLayout.java b/com/android/internal/widget/MessagingLayout.java
new file mode 100644
index 00000000..2acdc015
--- /dev/null
+++ b/com/android/internal/widget/MessagingLayout.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
+ * messages and adapts the layout accordingly.
+ */
+@RemoteViews.RemoteView
+public class MessagingLayout extends FrameLayout {
+
+ private static final float COLOR_SHIFT_AMOUNT = 60;
+ private static final Consumer<MessagingMessage> REMOVE_MESSAGE
+ = MessagingMessage::removeMessage;
+ public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
+ public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
+ = new MessagingPropertyAnimator();
+ private List<MessagingMessage> mMessages = new ArrayList<>();
+ private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
+ private MessagingLinearLayout mMessagingLinearLayout;
+ private View mContractedMessage;
+ private boolean mShowHistoricMessages;
+ private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
+ private TextView mTitleView;
+ private int mLayoutColor;
+ private int mAvatarSize;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Paint mTextPaint = new Paint();
+ private CharSequence mConversationTitle;
+ private Icon mLargeIcon;
+ private boolean mIsOneToOne;
+ private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
+
+ public MessagingLayout(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMessagingLinearLayout = findViewById(R.id.notification_messaging);
+ mMessagingLinearLayout.setMessagingLayout(this);
+ // We still want to clip, but only on the top, since views can temporarily out of bounds
+ // during transitions.
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ Rect rect = new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+ mMessagingLinearLayout.setClipBounds(rect);
+ mTitleView = findViewById(R.id.title);
+ mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setAntiAlias(true);
+ }
+
+ @RemotableViewMethod
+ public void setLargeIcon(Icon icon) {
+ mLargeIcon = icon;
+ }
+
+ @RemotableViewMethod
+ public void setData(Bundle extras) {
+ Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ List<Notification.MessagingStyle.Message> newMessages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+ Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+ List<Notification.MessagingStyle.Message> newHistoricMessages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+ mConversationTitle = null;
+ TextView headerText = findViewById(R.id.header_text);
+ if (headerText != null) {
+ mConversationTitle = headerText.getText();
+ }
+ bind(newMessages, newHistoricMessages);
+ }
+
+ private void bind(List<Notification.MessagingStyle.Message> newMessages,
+ List<Notification.MessagingStyle.Message> newHistoricMessages) {
+
+ List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
+ true /* isHistoric */);
+ List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+ addMessagesToGroups(historicMessages, messages);
+
+ // Let's remove the remaining messages
+ mMessages.forEach(REMOVE_MESSAGE);
+ mHistoricMessages.forEach(REMOVE_MESSAGE);
+
+ mMessages = messages;
+ mHistoricMessages = historicMessages;
+
+ updateContractedMessage();
+ updateHistoricMessageVisibility();
+ updateTitleAndNamesDisplay();
+ }
+
+ private void updateTitleAndNamesDisplay() {
+ ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
+ ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+ for (int i = 0; i < mGroups.size(); i++) {
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ boolean visible = !mIsOneToOne;
+ group.setSenderVisible(visible);
+ if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
+ char c = senderName.charAt(0);
+ if (uniqueCharacters.containsKey(c)) {
+ // this character was already used, lets make it more unique. We first need to
+ // resolve the existing character if it exists
+ CharSequence existingName = uniqueCharacters.get(c);
+ if (existingName != null) {
+ uniqueNames.put(existingName, findNameSplit((String) existingName));
+ uniqueCharacters.put(c, null);
+ }
+ uniqueNames.put(senderName, findNameSplit((String) senderName));
+ } else {
+ uniqueNames.put(senderName, Character.toString(c));
+ uniqueCharacters.put(c, senderName);
+ }
+ }
+ }
+
+ // Now that we have the correct symbols, let's look what we have cached
+ ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
+ for (int i = 0; i < mGroups.size(); i++) {
+ // Let's now set the avatars
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
+ continue;
+ }
+ String symbol = uniqueNames.get(senderName);
+ Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName,
+ symbol, mLayoutColor);
+ if (cachedIcon != null) {
+ cachedAvatars.put(senderName, cachedIcon);
+ }
+ }
+
+ for (int i = 0; i < mGroups.size(); i++) {
+ // Let's now set the avatars
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ if (mIsOneToOne && mLargeIcon != null) {
+ group.setAvatar(mLargeIcon);
+ } else {
+ Icon cachedIcon = cachedAvatars.get(senderName);
+ if (cachedIcon == null) {
+ cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
+ mLayoutColor);
+ cachedAvatars.put(senderName, cachedIcon);
+ }
+ group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
+ mLayoutColor);
+ }
+ }
+ }
+
+ public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
+ Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ float radius = mAvatarSize / 2.0f;
+ int color = findColor(senderName, layoutColor);
+ mPaint.setColor(color);
+ canvas.drawCircle(radius, radius, radius, mPaint);
+ boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
+ mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+ mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.75f : mAvatarSize * 0.4f);
+ int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
+ canvas.drawText(symbol, radius, yPos, mTextPaint);
+ return Icon.createWithBitmap(bitmap);
+ }
+
+ private int findColor(CharSequence senderName, int layoutColor) {
+ double luminance = NotificationColorUtil.calculateLuminance(layoutColor);
+ float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+ // we need to offset the range if the luminance is too close to the borders
+ shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+ shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+ return NotificationColorUtil.getShiftedColor(layoutColor,
+ (int) (shift * COLOR_SHIFT_AMOUNT));
+ }
+
+ private String findNameSplit(String existingName) {
+ String[] split = existingName.split(" ");
+ if (split.length > 1) {
+ return Character.toString(split[0].charAt(0))
+ + Character.toString(split[1].charAt(0));
+ }
+ return existingName.substring(0, 1);
+ }
+
+ @RemotableViewMethod
+ public void setLayoutColor(int color) {
+ mLayoutColor = color;
+ }
+
+ @RemotableViewMethod
+ public void setIsOneToOne(boolean oneToOne) {
+ mIsOneToOne = oneToOne;
+ }
+
+ private void addMessagesToGroups(List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages) {
+ // Let's first find our groups!
+ List<List<MessagingMessage>> groups = new ArrayList<>();
+ List<CharSequence> senders = new ArrayList<>();
+
+ // Lets first find the groups
+ findGroups(historicMessages, messages, groups, senders);
+
+ // Let's now create the views and reorder them accordingly
+ createGroupViews(groups, senders);
+ }
+
+ private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
+ mGroups.clear();
+ for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
+ List<MessagingMessage> group = groups.get(groupIndex);
+ MessagingGroup newGroup = null;
+ // we'll just take the first group that exists or create one there is none
+ for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) {
+ MessagingMessage message = group.get(messageIndex);
+ newGroup = message.getGroup();
+ if (newGroup != null) {
+ break;
+ }
+ }
+ if (newGroup == null) {
+ newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
+ mAddedGroups.add(newGroup);
+ }
+ newGroup.setLayoutColor(mLayoutColor);
+ newGroup.setSender(senders.get(groupIndex));
+ mGroups.add(newGroup);
+
+ if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) {
+ mMessagingLinearLayout.removeView(newGroup);
+ mMessagingLinearLayout.addView(newGroup, groupIndex);
+ }
+ newGroup.setMessages(group);
+ }
+ }
+
+ private void findGroups(List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+ List<CharSequence> senders) {
+ CharSequence currentSender = null;
+ List<MessagingMessage> currentGroup = null;
+ int histSize = historicMessages.size();
+ for (int i = 0; i < histSize + messages.size(); i++) {
+ MessagingMessage message;
+ if (i < histSize) {
+ message = historicMessages.get(i);
+ } else {
+ message = messages.get(i - histSize);
+ }
+ boolean isNewGroup = currentGroup == null;
+ CharSequence sender = message.getMessage().getSender();
+ isNewGroup |= !TextUtils.equals(sender, currentSender);
+ if (isNewGroup) {
+ currentGroup = new ArrayList<>();
+ groups.add(currentGroup);
+ senders.add(sender);
+ currentSender = sender;
+ }
+ currentGroup.add(message);
+ }
+ }
+
+ private void updateContractedMessage() {
+ for (int i = mMessages.size() - 1; i >= 0; i--) {
+ MessagingMessage m = mMessages.get(i);
+ // Incoming messages have a non-empty sender.
+ if (!TextUtils.isEmpty(m.getMessage().getSender())) {
+ mContractedMessage = m;
+ return;
+ }
+ }
+ if (!mMessages.isEmpty()) {
+ // No incoming messages, fall back to outgoing message
+ mContractedMessage = mMessages.get(mMessages.size() - 1);
+ return;
+ }
+ mContractedMessage = null;
+ }
+
+ /**
+ * Creates new messages, reusing existing ones if they are available.
+ *
+ * @param newMessages the messages to parse.
+ */
+ private List<MessagingMessage> createMessages(
+ List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+ List<MessagingMessage> result = new ArrayList<>();;
+ for (int i = 0; i < newMessages.size(); i++) {
+ Notification.MessagingStyle.Message m = newMessages.get(i);
+ MessagingMessage message = findAndRemoveMatchingMessage(m);
+ if (message == null) {
+ message = MessagingMessage.createMessage(this, m);
+ message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
+ }
+ message.setIsHistoric(historic);
+ result.add(message);
+ }
+ return result;
+ }
+
+ private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) {
+ for (int i = 0; i < mMessages.size(); i++) {
+ MessagingMessage existing = mMessages.get(i);
+ if (existing.sameAs(m)) {
+ mMessages.remove(i);
+ return existing;
+ }
+ }
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ MessagingMessage existing = mHistoricMessages.get(i);
+ if (existing.sameAs(m)) {
+ mHistoricMessages.remove(i);
+ return existing;
+ }
+ }
+ return null;
+ }
+
+ public void showHistoricMessages(boolean show) {
+ mShowHistoricMessages = show;
+ updateHistoricMessageVisibility();
+ }
+
+ private void updateHistoricMessageVisibility() {
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ MessagingMessage existing = mHistoricMessages.get(i);
+ existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!mAddedGroups.isEmpty()) {
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ for (MessagingGroup group : mAddedGroups) {
+ if (!group.isShown()) {
+ continue;
+ }
+ MessagingPropertyAnimator.fadeIn(group.getAvatar());
+ MessagingPropertyAnimator.fadeIn(group.getSender());
+ MessagingPropertyAnimator.startLocalTranslationFrom(group,
+ group.getHeight(), LINEAR_OUT_SLOW_IN);
+ }
+ mAddedGroups.clear();
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+ }
+
+ public View getContractedMessage() {
+ return mContractedMessage;
+ }
+
+ public MessagingLinearLayout getMessagingLinearLayout() {
+ return mMessagingLinearLayout;
+ }
+
+ public ArrayList<MessagingGroup> getMessagingGroups() {
+ return mGroups;
+ }
+}
diff --git a/com/android/internal/widget/MessagingLinearLayout.java b/com/android/internal/widget/MessagingLinearLayout.java
index 70473a01..f0ef3707 100644
--- a/com/android/internal/widget/MessagingLinearLayout.java
+++ b/com/android/internal/widget/MessagingLinearLayout.java
@@ -36,28 +36,14 @@ import com.android.internal.R;
@RemoteViews.RemoteView
public class MessagingLinearLayout extends ViewGroup {
- private static final int NOT_MEASURED_BEFORE = -1;
/**
* Spacing to be applied between views.
*/
private int mSpacing;
- /**
- * The maximum height allowed.
- */
- private int mMaxHeight;
+ private int mMaxDisplayedLines = Integer.MAX_VALUE;
- private int mIndentLines;
-
- /**
- * Id of the child that's also visible in the contracted layout.
- */
- private int mContractedChildId;
- /**
- * The last measured with in a layout pass if it was measured before or
- * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
- */
- private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
+ private MessagingLayout mMessagingLayout;
public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -79,7 +65,6 @@ public class MessagingLinearLayout extends ViewGroup {
a.recycle();
}
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// This is essentially a bottom-up linear layout that only adds children that fit entirely
@@ -91,118 +76,67 @@ public class MessagingLinearLayout extends ViewGroup {
break;
}
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
- || getMeasuredHeight() != targetHeight
- || mLastMeasuredWidth != widthSize;
-
- final int count = getChildCount();
- if (recalculateVisibility) {
- // We only need to recalculate the view visibilities if the view wasn't measured already
- // in this pass, otherwise we may drop messages here already since we are measured
- // exactly with what we returned before, which was optimized already with the
- // line-indents.
- for (int i = 0; i < count; ++i) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.hide = true;
- }
-
- int totalHeight = mPaddingTop + mPaddingBottom;
- boolean first = true;
-
- // Starting from the bottom: we measure every view as if it were the only one. If it still
-
- // fits, we take it, otherwise we stop there.
- for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
- if (getChildAt(i).getVisibility() == GONE) {
- continue;
- }
- final View child = getChildAt(i);
- LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
- ImageFloatingTextView textChild = null;
- if (child instanceof ImageFloatingTextView) {
- // Pretend we need the image padding for all views, we don't know which
- // one will end up needing to do this (might end up not using all the space,
- // but calculating this exactly would be more expensive).
- textChild = (ImageFloatingTextView) child;
- textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
- }
-
- int spacing = first ? 0 : mSpacing;
- measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
- - mPaddingTop - mPaddingBottom + spacing);
-
- final int childHeight = child.getMeasuredHeight();
- int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
- lp.bottomMargin + spacing);
- first = false;
- boolean measuredTooSmall = false;
- if (textChild != null) {
- measuredTooSmall = childHeight < textChild.getLayoutHeight()
- + textChild.getPaddingTop() + textChild.getPaddingBottom();
- }
-
- if (newHeight <= targetHeight && !measuredTooSmall) {
- totalHeight = newHeight;
- lp.hide = false;
- } else {
- break;
- }
- }
- }
// Now that we know which views to take, fix up the indents and see what width we get.
int measuredWidth = mPaddingLeft + mPaddingRight;
- int imageLines = mIndentLines;
- // Need to redo the height because it may change due to changing indents.
- int totalHeight = mPaddingTop + mPaddingBottom;
- boolean first = true;
- for (int i = 0; i < count; i++) {
+ final int count = getChildCount();
+ int totalHeight;
+ for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.hide = true;
+ }
+
+ totalHeight = mPaddingTop + mPaddingBottom;
+ boolean first = true;
+ int linesRemaining = mMaxDisplayedLines;
- if (child.getVisibility() == GONE || lp.hide) {
+ // Starting from the bottom: we measure every view as if it were the only one. If it still
+ // fits, we take it, otherwise we stop there.
+ for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
+ if (getChildAt(i).getVisibility() == GONE) {
continue;
}
-
- if (child instanceof ImageFloatingTextView) {
- ImageFloatingTextView textChild = (ImageFloatingTextView) child;
- if (imageLines == 2 && textChild.getLineCount() > 2) {
- // HACK: If we need indent for two lines, and they're coming from the same
- // view, we need extra spacing to compensate for the lack of margins,
- // so add an extra line of indent.
- imageLines = 3;
- }
- boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
- if (changed || !recalculateVisibility) {
- final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
- lp.width);
- // we want to measure it at most as high as it is currently, otherwise we'll
- // drop later lines
- final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
- targetHeight - child.getMeasuredHeight(), lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
- }
- imageLines -= textChild.getLineCount();
+ final View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+ MessagingChild messagingChild = null;
+ if (child instanceof MessagingChild) {
+ messagingChild = (MessagingChild) child;
+ messagingChild.setMaxDisplayedLines(linesRemaining);
}
+ int spacing = first ? 0 : mSpacing;
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
+ - mPaddingTop - mPaddingBottom + spacing);
- measuredWidth = Math.max(measuredWidth,
- child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
- + mPaddingLeft + mPaddingRight);
- totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
- lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
+ final int childHeight = child.getMeasuredHeight();
+ int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
+ lp.bottomMargin + spacing);
first = false;
+ int measureType = MessagingChild.MEASURED_NORMAL;
+ if (messagingChild != null) {
+ measureType = messagingChild.getMeasuredType();
+ linesRemaining -= messagingChild.getConsumedLines();
+ }
+ boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
+ boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
+ if (newHeight <= targetHeight && !isTooSmall) {
+ totalHeight = newHeight;
+ measuredWidth = Math.max(measuredWidth,
+ child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+ + mPaddingLeft + mPaddingRight);
+ lp.hide = false;
+ if (isShortened || linesRemaining <= 0) {
+ break;
+ }
+ } else {
+ break;
+ }
}
-
setMeasuredDimension(
resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
widthMeasureSpec),
- resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
- heightMeasureSpec));
- mLastMeasuredWidth = widthSize;
+ Math.max(getSuggestedMinimumHeight(), totalHeight));
}
@Override
@@ -221,13 +155,22 @@ public class MessagingLinearLayout extends ViewGroup {
childTop = mPaddingTop;
boolean first = true;
-
+ final boolean shown = isShown();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (child.getVisibility() == GONE || lp.hide) {
+ MessagingChild messagingChild = (MessagingChild) child;
+ if (lp.hide) {
+ if (shown && lp.visibleBefore) {
+ messagingChild.hideAnimated();
+ }
+ lp.visibleBefore = false;
continue;
+ } else {
+ lp.visibleBefore = true;
}
final int childWidth = child.getMeasuredWidth();
@@ -251,14 +194,16 @@ public class MessagingLinearLayout extends ViewGroup {
first = false;
}
- mLastMeasuredWidth = NOT_MEASURED_BEFORE;
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.hide) {
- return true;
+ MessagingChild messagingChild = (MessagingChild) child;
+ if (!messagingChild.isHidingAnimated()) {
+ return true;
+ }
}
return super.drawChild(canvas, child, drawingTime);
}
@@ -284,31 +229,37 @@ public class MessagingLinearLayout extends ViewGroup {
}
/**
- * Sets how many lines should be indented to avoid a floating image.
+ * Sets how many lines should be displayed at most
*/
@RemotableViewMethod
- public void setNumIndentLines(int numberLines) {
- mIndentLines = numberLines;
+ public void setMaxDisplayedLines(int numberLines) {
+ mMaxDisplayedLines = numberLines;
}
- /**
- * Set id of the child that's also visible in the contracted layout.
- */
- @RemotableViewMethod
- public void setContractedChildId(int contractedChildId) {
- mContractedChildId = contractedChildId;
+ public void setMessagingLayout(MessagingLayout layout) {
+ mMessagingLayout = layout;
}
- /**
- * Get id of the child that's also visible in the contracted layout.
- */
- public int getContractedChildId() {
- return mContractedChildId;
+ public MessagingLayout getMessagingLayout() {
+ return mMessagingLayout;
+ }
+
+ public interface MessagingChild {
+ int MEASURED_NORMAL = 0;
+ int MEASURED_SHORTENED = 1;
+ int MEASURED_TOO_SMALL = 2;
+
+ int getMeasuredType();
+ int getConsumedLines();
+ void setMaxDisplayedLines(int lines);
+ void hideAnimated();
+ boolean isHidingAnimated();
}
public static class LayoutParams extends MarginLayoutParams {
- boolean hide = false;
+ public boolean hide = false;
+ public boolean visibleBefore = false;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
diff --git a/com/android/internal/widget/MessagingMessage.java b/com/android/internal/widget/MessagingMessage.java
new file mode 100644
index 00000000..f09621f5
--- /dev/null
+++ b/com/android/internal/widget/MessagingMessage.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingMessage extends ImageFloatingTextView implements
+ MessagingLinearLayout.MessagingChild {
+
+ private static Pools.SimplePool<MessagingMessage> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private Notification.MessagingStyle.Message mMessage;
+ private MessagingGroup mGroup;
+ private boolean mIsHistoric;
+ private boolean mIsHidingAnimated;
+
+ public MessagingMessage(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private void setMessage(Notification.MessagingStyle.Message message) {
+ mMessage = message;
+ setText(message.getText());
+ }
+
+ public Notification.MessagingStyle.Message getMessage() {
+ return mMessage;
+ }
+
+ boolean sameAs(Notification.MessagingStyle.Message message) {
+ if (!Objects.equals(message.getText(), mMessage.getText())) {
+ return false;
+ }
+ if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+ return false;
+ }
+ if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean sameAs(MessagingMessage message) {
+ return sameAs(message.getMessage());
+ }
+
+ static MessagingMessage createMessage(MessagingLayout layout,
+ Notification.MessagingStyle.Message m) {
+ MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+ MessagingMessage createdMessage = sInstancePool.acquire();
+ if (createdMessage == null) {
+ createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_message, messagingLinearLayout,
+ false);
+ }
+ createdMessage.setMessage(m);
+ return createdMessage;
+ }
+
+ public void removeMessage() {
+ mGroup.removeMessage(this);
+ }
+
+ public void recycle() {
+ mGroup = null;
+ mMessage = null;
+ setAlpha(1.0f);
+ setTranslationY(0);
+ sInstancePool.release(this);
+ }
+
+ public void setMessagingGroup(MessagingGroup group) {
+ mGroup = group;
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ public void setIsHistoric(boolean isHistoric) {
+ mIsHistoric = isHistoric;
+ }
+
+ public MessagingGroup getGroup() {
+ return mGroup;
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return MEASURED_TOO_SMALL;
+ }
+ if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_NORMAL;
+ }
+ }
+ }
+
+ @Override
+ public void hideAnimated() {
+ setIsHidingAnimated(true);
+ mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+ }
+
+ private void setIsHidingAnimated(boolean isHiding) {
+ ViewParent parent = getParent();
+ mIsHidingAnimated = isHiding;
+ invalidate();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).invalidate();
+ }
+ }
+
+ @Override
+ public boolean isHidingAnimated() {
+ return mIsHidingAnimated;
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ setMaxLines(lines);
+ }
+
+ @Override
+ public int getConsumedLines() {
+ return getLineCount();
+ }
+
+ public int getLayoutHeight() {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return 0;
+ }
+ return layout.getHeight();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/com/android/internal/widget/MessagingPropertyAnimator.java b/com/android/internal/widget/MessagingPropertyAnimator.java
new file mode 100644
index 00000000..7c3ab7f9
--- /dev/null
+++ b/com/android/internal/widget/MessagingPropertyAnimator.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.IntProperty;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.R;
+
+/**
+ * A listener that automatically starts animations when the layout bounds change.
+ */
+public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
+ static final long APPEAR_ANIMATION_LENGTH = 210;
+ private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+ private static final int TAG_LOCAL_TRANSLATION_ANIMATOR = R.id.tag_local_translation_y_animator;
+ private static final int TAG_LOCAL_TRANSLATION_Y = R.id.tag_local_translation_y;
+ private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
+ private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
+ private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
+ view -> view.getId() == com.android.internal.R.id.notification_messaging;
+ private static final IntProperty<View> LOCAL_TRANSLATION_Y =
+ new IntProperty<View>("localTranslationY") {
+ @Override
+ public void setValue(View object, int value) {
+ setLocalTranslationY(object, value);
+ }
+
+ @Override
+ public Integer get(View object) {
+ return getLocalTranslationY(object);
+ }
+ };
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ int oldHeight = oldBottom - oldTop;
+ Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+ if (layoutTop != null) {
+ oldTop = layoutTop;
+ }
+ int topChange = oldTop - top;
+ if (oldHeight == 0 || topChange == 0 || !v.isShown() || isGone(v)) {
+ // First layout
+ return;
+ }
+ if (layoutTop != null) {
+ v.setTagInternal(TAG_LAYOUT_TOP, top);
+ }
+ int newHeight = bottom - top;
+ int heightDifference = oldHeight - newHeight;
+ // Only add the difference if the height changes and it's getting smaller
+ heightDifference = Math.max(heightDifference, 0);
+ startLocalTranslationFrom(v, topChange + heightDifference + getLocalTranslationY(v));
+ }
+
+ private boolean isGone(View view) {
+ if (view.getVisibility() == View.GONE) {
+ return true;
+ }
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+ return true;
+ }
+ return false;
+ }
+
+ public static void startLocalTranslationFrom(View v, int startTranslation) {
+ startLocalTranslationFrom(v, startTranslation, MessagingLayout.FAST_OUT_SLOW_IN);
+ }
+
+ public static void startLocalTranslationFrom(View v, int startTranslation,
+ Interpolator interpolator) {
+ startLocalTranslation(v, startTranslation, 0, interpolator);
+ }
+
+ public static void startLocalTranslationTo(View v, int endTranslation,
+ Interpolator interpolator) {
+ startLocalTranslation(v, getLocalTranslationY(v), endTranslation, interpolator);
+ }
+
+ public static int getLocalTranslationY(View v) {
+ Integer tag = (Integer) v.getTag(TAG_LOCAL_TRANSLATION_Y);
+ if (tag == null) {
+ return 0;
+ }
+ return tag;
+ }
+
+ private static void setLocalTranslationY(View v, int value) {
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
+ updateTopAndBottom(v);
+ }
+
+ private static void updateTopAndBottom(View v) {
+ int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
+ int localTranslation = getLocalTranslationY(v);
+ int height = v.getHeight();
+ v.setTop(layoutTop + localTranslation);
+ v.setBottom(layoutTop + height + localTranslation);
+ }
+
+ private static void startLocalTranslation(final View v, int start, int end,
+ Interpolator interpolator) {
+ ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ ObjectAnimator animator = ObjectAnimator.ofInt(v, LOCAL_TRANSLATION_Y, start, end);
+ Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+ if (layoutTop == null) {
+ layoutTop = v.getTop();
+ v.setTagInternal(TAG_LAYOUT_TOP, layoutTop);
+ }
+ setLocalTranslationY(v, start);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ public boolean mCancelled;
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, null);
+ setClippingDeactivated(v, false);
+ if (!mCancelled) {
+ setLocalTranslationY(v, 0);
+ v.setTagInternal(TAG_LAYOUT_TOP, null);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ });
+ setClippingDeactivated(v, true);
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
+ animator.start();
+ }
+
+ public static void fadeIn(final View v) {
+ ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ if (v.getVisibility() == View.INVISIBLE) {
+ v.setVisibility(View.VISIBLE);
+ }
+ ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
+ 0.0f, 1.0f);
+ v.setAlpha(0.0f);
+ animator.setInterpolator(ALPHA_IN);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+ updateLayerType(v, false /* animating */);
+ }
+ });
+ updateLayerType(v, true /* animating */);
+ v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+ animator.start();
+ }
+
+ private static void updateLayerType(View view, boolean animating) {
+ if (view.hasOverlappingRendering() && animating) {
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
+ view.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ public static void fadeOut(final View view, Runnable endAction) {
+ ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
+ view.getAlpha(), 0.0f);
+ animator.setInterpolator(ALPHA_OUT);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+ updateLayerType(view, false /* animating */);
+ if (endAction != null) {
+ endAction.run();
+ }
+ }
+ });
+ updateLayerType(view, true /* animating */);
+ view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+ animator.start();
+ }
+
+ public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
+ CLIPPING_PARAMETERS);
+ }
+
+ public static boolean isAnimatingTranslation(View v) {
+ return v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR) != null;
+ }
+
+ public static boolean isAnimatingAlpha(View v) {
+ return v.getTag(TAG_ALPHA_ANIMATOR) != null;
+ }
+}
diff --git a/com/android/internal/widget/NotificationActionListLayout.java b/com/android/internal/widget/NotificationActionListLayout.java
index 073aac54..26023b49 100644
--- a/com/android/internal/widget/NotificationActionListLayout.java
+++ b/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,7 +16,10 @@
package com.android.internal.widget;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Pair;
@@ -37,6 +40,7 @@ import java.util.Comparator;
@RemoteViews.RemoteView
public class NotificationActionListLayout extends LinearLayout {
+ private final int mGravity;
private int mTotalWidth = 0;
private ArrayList<Pair<Integer, TextView>> mMeasureOrderTextViews = new ArrayList<>();
private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
@@ -45,7 +49,20 @@ public class NotificationActionListLayout extends LinearLayout {
private Drawable mDefaultBackground;
public NotificationActionListLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public NotificationActionListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationActionListLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ int[] attrIds = { android.R.attr.gravity };
+ TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
+ mGravity = ta.getInt(0, 0);
+ ta.recycle();
}
@Override
@@ -95,6 +112,7 @@ public class NotificationActionListLayout extends LinearLayout {
final boolean constrained =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED;
+ final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
final int otherSize = mMeasureOrderOther.size();
@@ -137,7 +155,7 @@ public class NotificationActionListLayout extends LinearLayout {
// Make sure to measure the last child full-width if we didn't use up the entire width,
// or we didn't measure yet because there's just one child.
- if (lastNotGoneChild != null && (constrained && usedWidth < innerWidth
+ if (lastNotGoneChild != null && !centerAligned && (constrained && usedWidth < innerWidth
|| notGoneChildren == 1)) {
MarginLayoutParams lp = (MarginLayoutParams) lastNotGoneChild.getLayoutParams();
if (notGoneChildren > 1) {
@@ -201,9 +219,10 @@ public class NotificationActionListLayout extends LinearLayout {
}
final boolean isLayoutRtl = isLayoutRtl();
final int paddingTop = mPaddingTop;
+ final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
int childTop;
- int childLeft;
+ int childLeft = centerAligned ? left + (right - left) / 2 - mTotalWidth / 2 : 0;
// Where bottom of child should go
final int height = bottom - top;
@@ -216,13 +235,12 @@ public class NotificationActionListLayout extends LinearLayout {
final int layoutDirection = getLayoutDirection();
switch (Gravity.getAbsoluteGravity(Gravity.START, layoutDirection)) {
case Gravity.RIGHT:
- // mTotalWidth contains the padding already
- childLeft = mPaddingLeft + right - left - mTotalWidth;
+ childLeft += mPaddingLeft + right - left - mTotalWidth;
break;
case Gravity.LEFT:
default:
- childLeft = mPaddingLeft;
+ childLeft += mPaddingLeft;
break;
}
diff --git a/com/android/internal/widget/RemeasuringLinearLayout.java b/com/android/internal/widget/RemeasuringLinearLayout.java
new file mode 100644
index 00000000..e352b45e
--- /dev/null
+++ b/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+/**
+ * A LinearLayout that sets it's height again after the last measure pass. This is needed for
+ * MessagingLayouts where groups need to be able to snap it's height to.
+ */
+@RemoteViews.RemoteView
+public class RemeasuringLinearLayout extends LinearLayout {
+
+ public RemeasuringLinearLayout(Context context) {
+ super(context);
+ }
+
+ public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RemeasuringLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int count = getChildCount();
+ int height = 0;
+ for (int i = 0; i < count; ++i) {
+ final View child = getChildAt(i);
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin +
+ lp.bottomMargin);
+ }
+ setMeasuredDimension(getMeasuredWidth(), height);
+ }
+}
diff --git a/com/android/internal/widget/ViewClippingUtil.java b/com/android/internal/widget/ViewClippingUtil.java
new file mode 100644
index 00000000..59bbed44
--- /dev/null
+++ b/com/android/internal/widget/ViewClippingUtil.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.internal.R;
+
+/**
+ * A utility class that allows to clip views and their parents to allow for better transitions
+ */
+public class ViewClippingUtil {
+ private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+ private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+ private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+
+ public static void setClippingDeactivated(final View transformedView, boolean deactivated,
+ ClippingParameters clippingParameters) {
+ if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+ return;
+ }
+ if (!(transformedView.getParent() instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup parent = (ViewGroup) transformedView.getParent();
+ while (true) {
+ if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+ return;
+ }
+ ArraySet<View> clipSet = (ArraySet<View>) parent.getTag(CLIP_CLIPPING_SET);
+ if (clipSet == null) {
+ clipSet = new ArraySet<>();
+ parent.setTagInternal(CLIP_CLIPPING_SET, clipSet);
+ }
+ Boolean clipChildren = (Boolean) parent.getTag(CLIP_CHILDREN_TAG);
+ if (clipChildren == null) {
+ clipChildren = parent.getClipChildren();
+ parent.setTagInternal(CLIP_CHILDREN_TAG, clipChildren);
+ }
+ Boolean clipToPadding = (Boolean) parent.getTag(CLIP_TO_PADDING);
+ if (clipToPadding == null) {
+ clipToPadding = parent.getClipToPadding();
+ parent.setTagInternal(CLIP_TO_PADDING, clipToPadding);
+ }
+ if (!deactivated) {
+ clipSet.remove(transformedView);
+ if (clipSet.isEmpty()) {
+ parent.setClipChildren(clipChildren);
+ parent.setClipToPadding(clipToPadding);
+ parent.setTagInternal(CLIP_CLIPPING_SET, null);
+ clippingParameters.onClippingStateChanged(parent, true);
+ }
+ } else {
+ clipSet.add(transformedView);
+ parent.setClipChildren(false);
+ parent.setClipToPadding(false);
+ clippingParameters.onClippingStateChanged(parent, false);
+ }
+ if (clippingParameters.shouldFinish(parent)) {
+ return;
+ }
+ final ViewParent viewParent = parent.getParent();
+ if (viewParent instanceof ViewGroup) {
+ parent = (ViewGroup) viewParent;
+ } else {
+ return;
+ }
+ }
+ }
+
+ public interface ClippingParameters {
+ /**
+ * Should we stop clipping at this view? If true is returned, {@param view} is the last view
+ * where clipping is activated / deactivated.
+ */
+ boolean shouldFinish(View view);
+
+ /**
+ * Is it allowed to enable clipping on this view.
+ */
+ default boolean isClippingEnablingAllowed(View view) {
+ return !MessagingPropertyAnimator.isAnimatingTranslation(view);
+ }
+
+ /**
+ * A method that is called whenever the view starts clipping again / stops clipping to the
+ * children and padding.
+ */
+ default void onClippingStateChanged(View view, boolean isClipping) {};
+ }
+}
diff --git a/com/android/keyguard/KeyguardSliceView.java b/com/android/keyguard/KeyguardSliceView.java
new file mode 100644
index 00000000..c18f9b61
--- /dev/null
+++ b/com/android/keyguard/KeyguardSliceView.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+
+/**
+ * View visible under the clock on the lock screen and AoD.
+ */
+public class KeyguardSliceView extends LinearLayout {
+
+ private final Uri mKeyguardSliceUri;
+ private TextView mTitle;
+ private TextView mText;
+ private Slice mSlice;
+ private PendingIntent mSliceAction;
+ private int mTextColor;
+ private float mDarkAmount = 0;
+
+ private final ContentObserver mObserver;
+
+ public KeyguardSliceView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mObserver = new KeyguardSliceObserver(new Handler());
+ mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTitle = findViewById(R.id.title);
+ mText = findViewById(R.id.text);
+ mTextColor = mTitle.getCurrentTextColor();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Set initial content
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+
+ // Make sure we always have the most current slice
+ getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
+ false /* notifyDescendants */, mObserver);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ 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);
+ if (actionSlice != null) {
+ mSlice = actionSlice.getSlice();
+ mSliceAction = actionSlice.getAction();
+ } else {
+ mSlice = slice;
+ mSliceAction = null;
+ }
+
+ if (mSlice == null) {
+ setVisibility(GONE);
+ return;
+ }
+
+ SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+ if (title == null) {
+ mTitle.setVisibility(GONE);
+ } else {
+ mTitle.setVisibility(VISIBLE);
+ mTitle.setText(title.getText());
+ }
+
+ SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+ if (text == null) {
+ mText.setVisibility(GONE);
+ } else {
+ mText.setVisibility(VISIBLE);
+ mText.setText(text.getText());
+ }
+
+ final int visibility = title == null && text == null ? GONE : VISIBLE;
+ if (visibility != getVisibility()) {
+ setVisibility(visibility);
+ }
+ }
+
+ public void setDark(float darkAmount) {
+ mDarkAmount = darkAmount;
+ updateTextColors();
+ }
+
+ public void setTextColor(int textColor) {
+ mTextColor = textColor;
+ }
+
+ private void updateTextColors() {
+ final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+ mTitle.setTextColor(blendedColor);
+ mText.setTextColor(blendedColor);
+ }
+
+ private class KeyguardSliceObserver extends ContentObserver {
+ KeyguardSliceObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ this.onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+ }
+ }
+}
diff --git a/com/android/keyguard/KeyguardStatusView.java b/com/android/keyguard/KeyguardStatusView.java
index bc2a59df..78cf2b9b 100644
--- a/com/android/keyguard/KeyguardStatusView.java
+++ b/com/android/keyguard/KeyguardStatusView.java
@@ -19,11 +19,9 @@ package com.android.keyguard;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PorterDuff;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -42,8 +40,8 @@ import android.widget.TextView;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.Utils;
import com.android.systemui.ChargingView;
-import com.android.systemui.statusbar.policy.DateView;
import java.util.Locale;
@@ -55,13 +53,11 @@ public class KeyguardStatusView extends GridLayout {
private final LockPatternUtils mLockPatternUtils;
private final AlarmManager mAlarmManager;
- private TextView mAlarmStatusView;
- private DateView mDateView;
private TextClock mClockView;
private TextView mOwnerInfo;
private ViewGroup mClockContainer;
private ChargingView mBatteryDoze;
- private View mKeyguardStatusArea;
+ private KeyguardSliceView mKeyguardSlice;
private Runnable mPendingMarqueeStart;
private Handler mHandler;
@@ -69,8 +65,6 @@ public class KeyguardStatusView extends GridLayout {
private boolean mPulsing;
private float mDarkAmount = 0;
private int mTextColor;
- private int mDateTextColor;
- private int mAlarmTextColor;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@@ -141,7 +135,6 @@ public class KeyguardStatusView extends GridLayout {
private void setEnableMarqueeImpl(boolean enabled) {
if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
- if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
}
@@ -149,8 +142,6 @@ public class KeyguardStatusView extends GridLayout {
protected void onFinishInflate() {
super.onFinishInflate();
mClockContainer = findViewById(R.id.keyguard_clock_container);
- mAlarmStatusView = findViewById(R.id.alarm_status);
- mDateView = findViewById(R.id.date_view);
mClockView = findViewById(R.id.clock_view);
mClockView.setShowCurrentUserTime(true);
if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
@@ -158,11 +149,9 @@ public class KeyguardStatusView extends GridLayout {
}
mOwnerInfo = findViewById(R.id.owner_info);
mBatteryDoze = findViewById(R.id.battery_doze);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
- mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
+ mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
mTextColor = mClockView.getCurrentTextColor();
- mDateTextColor = mDateView.getCurrentTextColor();
- mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
@@ -184,8 +173,6 @@ public class KeyguardStatusView extends GridLayout {
layoutParams.bottomMargin = getResources().getDimensionPixelSize(
R.dimen.bottom_text_spacing_digital);
mClockView.setLayoutParams(layoutParams);
- mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
if (mOwnerInfo != null) {
mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
@@ -193,8 +180,6 @@ public class KeyguardStatusView extends GridLayout {
}
public void refreshTime() {
- mDateView.setDatePattern(Patterns.dateViewSkel);
-
mClockView.setFormat12Hour(Patterns.clockView12);
mClockView.setFormat24Hour(Patterns.clockView24);
}
@@ -205,23 +190,11 @@ public class KeyguardStatusView extends GridLayout {
Patterns.update(mContext, nextAlarm != null);
refreshTime();
- refreshAlarmStatus(nextAlarm);
- }
-
- void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
- if (nextAlarm != null) {
- String alarm = formatNextAlarm(mContext, nextAlarm);
- mAlarmStatusView.setText(alarm);
- mAlarmStatusView.setContentDescription(
- getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
- mAlarmStatusView.setVisibility(View.VISIBLE);
- } else {
- mAlarmStatusView.setVisibility(View.GONE);
- }
}
public int getClockBottom() {
- return mKeyguardStatusArea.getBottom();
+ return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
+ : mClockView.getBottom();
}
public float getClockTextSize() {
@@ -341,11 +314,8 @@ public class KeyguardStatusView extends GridLayout {
updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
+ mKeyguardSlice.setDark(darkAmount);
mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
- mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
- int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
- mAlarmStatusView.setTextColor(blendedAlarmColor);
- mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
}
public void setPulsing(boolean pulsing) {
diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java
index 2bb992c4..41b007af 100644
--- a/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -76,8 +76,9 @@ import com.android.internal.telephony.IccCardConstants.State;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.google.android.collect.Lists;
@@ -1187,7 +1188,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
mFpm.addLockoutResetCallback(mLockoutResetCallback);
}
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
}
@@ -1738,6 +1739,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
mFailedAttempts.delete(sCurrentUser);
}
+ public ServiceState getServiceState(int subId) {
+ return mServiceStates.get(subId);
+ }
+
public int getFailedUnlockAttempts(int userId) {
return mFailedAttempts.get(userId, 0);
}
@@ -1772,7 +1777,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
- private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ private final SysUiTaskStackChangeListener
+ mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChangedBackground() {
try {
diff --git a/com/android/layout/remote/api/RemoteLayoutlibCallback.java b/com/android/layout/remote/api/RemoteLayoutlibCallback.java
index 5c138efd..0f315ca2 100644
--- a/com/android/layout/remote/api/RemoteLayoutlibCallback.java
+++ b/com/android/layout/remote/api/RemoteLayoutlibCallback.java
@@ -26,6 +26,7 @@ import com.android.resources.ResourceType;
import com.android.util.Pair;
import java.io.Serializable;
+import java.nio.file.Path;
import java.rmi.Remote;
import java.rmi.RemoteException;
@@ -58,14 +59,11 @@ public interface RemoteLayoutlibCallback extends Remote {
RemoteActionBarCallback getActionBarCallback() throws RemoteException;
- Object loadClass(String name, Class[] constructorSignature, Object[] constructorArgs)
- throws ClassNotFoundException, RemoteException;
-
<T> T getFlag(Key<T> key) throws RemoteException;
RemoteParserFactory getParserFactory() throws RemoteException;
- Class<?> findClass(String name) throws ClassNotFoundException, RemoteException;
+ Path findClassPath(String name) throws RemoteException;
RemoteXmlPullParser getXmlFileParser(String fileName) throws RemoteException;
diff --git a/com/android/layoutlib/bridge/impl/ResourceHelper.java b/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 45330d03..16f92f38 100644
--- a/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -49,6 +49,7 @@ import android.graphics.NinePatch_Delegate;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Typeface_Accessor;
+import android.graphics.Typeface_Delegate;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -387,59 +388,8 @@ public final class ResourceHelper {
return null;
}
- // Check if this is an asset that we've already loaded dynamically
- Typeface typeface = Typeface.findFromCache(context.getAssets(), fontName);
- if (typeface != null) {
- return typeface;
- }
-
- String lowerCaseValue = fontName.toLowerCase();
- if (lowerCaseValue.endsWith(".xml")) {
- // create a block parser for the file
- Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
- RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
- XmlPullParser parser = null;
- if (psiParserSupport != null && psiParserSupport) {
- parser = context.getLayoutlibCallback().getXmlFileParser(fontName);
- }
- else {
- File f = new File(fontName);
- if (f.isFile()) {
- try {
- parser = ParserFactory.create(f);
- } catch (XmlPullParserException | FileNotFoundException e) {
- // this is an error and not warning since the file existence is checked before
- // attempting to parse it.
- Bridge.getLog().error(null, "Failed to parse file " + fontName,
- e, null /*data*/);
- }
- }
- }
-
- if (parser != null) {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, context, isFramework);
- try {
- FontResourcesParser.FamilyResourceEntry entry =
- FontResourcesParser.parse(blockParser, context.getResources());
- typeface = Typeface.createFromResources(entry, context.getAssets(),
- fontName);
- } catch (XmlPullParserException | IOException e) {
- Bridge.getLog().error(null, "Failed to parse file " + fontName,
- e, null /*data*/);
- } finally {
- blockParser.ensurePopped();
- }
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("File %s does not exist (or is not a file)", fontName),
- null /*data*/);
- }
- } else {
- typeface = Typeface.createFromResources(context.getAssets(), fontName, 0);
- }
- return typeface;
+ return Typeface_Delegate.createFromDisk(context, fontName, isFramework);
}
/**
diff --git a/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java b/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
index 43ea5699..b9b1a00e 100644
--- a/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
+++ b/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -29,8 +29,13 @@ import com.android.layout.remote.api.RemoteParserFactory;
import com.android.layout.remote.api.RemoteXmlPullParser;
import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
import com.android.util.Pair;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
@@ -113,12 +118,6 @@ public class RemoteLayoutlibCallbackAdapter implements RemoteLayoutlibCallback {
}
@Override
- public Object loadClass(String name, Class[] constructorSignature, Object[] constructorArgs)
- throws ClassNotFoundException {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- @Override
public <T> T getFlag(Key<T> key) {
return mDelegate.getFlag(key);
}
@@ -132,9 +131,21 @@ public class RemoteLayoutlibCallbackAdapter implements RemoteLayoutlibCallback {
}
}
+ @Nullable
@Override
- public Class<?> findClass(String name) throws ClassNotFoundException {
- throw new UnsupportedOperationException("Not implemented yet");
+ public Path findClassPath(String name) {
+ try {
+ Class<?> clazz = mDelegate.findClass(name);
+ URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
+ if (url != null) {
+ return Paths.get(url.toURI());
+ }
+ } catch (ClassNotFoundException ignore) {
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+
+ return null;
}
@Override
diff --git a/com/android/layoutlib/bridge/remote/server/ServerMain.java b/com/android/layoutlib/bridge/remote/server/ServerMain.java
index daecdd4f..09c27fd2 100644
--- a/com/android/layoutlib/bridge/remote/server/ServerMain.java
+++ b/com/android/layoutlib/bridge/remote/server/ServerMain.java
@@ -19,17 +19,31 @@ package com.android.layoutlib.bridge.remote.server;
import com.android.layout.remote.api.RemoteBridge;
import com.android.tools.layoutlib.annotations.NotNull;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* Main server class. The main method will start an RMI server for the {@link RemoteBridgeImpl}
* class.
*/
public class ServerMain {
+ private static final String RUNNING_SERVER_STR = "Server is running on port ";
public static int REGISTRY_BASE_PORT = 9000;
private final int mPort;
@@ -52,13 +66,101 @@ public class ServerMain {
}
/**
+ * This will start a new JVM and connect to the new JVM RMI registry.
+ * <p/>
* The server will start looking for ports available for the {@link Registry} until a free one
* is found. The port number will be returned.
* If no ports are available, a {@link RemoteException} will be thrown.
* @param basePort port number to start looking for available ports
* @param limit number of ports to check. The last port to be checked will be (basePort + limit)
*/
- public static ServerMain startServer(int basePort, int limit) throws RemoteException {
+ public static ServerMain forkAndStartServer(int basePort, int limit)
+ throws IOException, InterruptedException {
+ // We start a new VM by copying all the parameter that we received in the current VM.
+ // We only remove the agentlib parameter since that could cause a port collision and avoid
+ // the new VM from starting.
+ RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
+ List<String> arguments = runtimeMxBean.getInputArguments().stream()
+ .filter(arg -> !arg.contains("-agentlib")) // Filter agentlib to avoid conflicts
+ .collect(Collectors.toList());
+
+ Path javaPath = Paths.get(System.getProperty("java.home"), "bin", "java");
+ String thisClassName = ServerMain.class.getName()
+ .replace('.','/');
+
+ List<String> cmd = new ArrayList<>();
+ cmd.add(javaPath.toString());
+
+ // Inherited arguments
+ cmd.addAll(arguments);
+
+ // Classpath
+ cmd.add("-cp");
+ cmd.add(System.getProperty("java.class.path"));
+
+ // Class name and path
+ cmd.add(thisClassName);
+
+ // ServerMain parameters [basePort. limit]
+ cmd.add(Integer.toString(basePort));
+ cmd.add(Integer.toString(limit));
+
+ Process process = new ProcessBuilder()
+ .command(cmd)
+ .start();
+
+ BlockingQueue<String> outputQueue = new ArrayBlockingQueue<>(10);
+ Thread outputThread = new Thread(() -> {
+ BufferedReader inputStream = new BufferedReader(
+ new InputStreamReader(process.getInputStream()));
+ inputStream.lines()
+ .forEach(outputQueue::offer);
+
+ });
+ outputThread.setName("output thread");
+ outputThread.start();
+
+ Runnable killServer = () -> {
+ process.destroyForcibly();
+ outputThread.interrupt();
+ try {
+ outputThread.join();
+ } catch (InterruptedException ignore) {
+ }
+ };
+
+ // Try to read the "Running on port" line in 10 lines. If it's not there just fail.
+ for (int i = 0; i < 10; i++) {
+ String line = outputQueue.poll(1000, TimeUnit.SECONDS);
+
+ if (line.startsWith(RUNNING_SERVER_STR)) {
+ int runningPort = Integer.parseInt(line.substring(RUNNING_SERVER_STR.length()));
+ System.out.println("Running on port " + runningPort);
+
+ // We already know where the server is running so we just need to get the registry
+ // and return our own instance of ServerMain
+ Registry registry = LocateRegistry.getRegistry(runningPort);
+ return new ServerMain(runningPort, registry) {
+ @Override
+ public void stop() {
+ killServer.run();
+ }
+ };
+ }
+ }
+
+ killServer.run();
+ throw new IOException("Unable to find start string");
+ }
+
+ /**
+ * The server will start looking for ports available for the {@link Registry} until a free one
+ * is found. The port number will be returned.
+ * If no ports are available, a {@link RemoteException} will be thrown.
+ * @param basePort port number to start looking for available ports
+ * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
+ */
+ private static ServerMain startServer(int basePort, int limit) throws RemoteException {
RemoteBridgeImpl remoteBridge = new RemoteBridgeImpl();
RemoteBridge stub = (RemoteBridge) UnicastRemoteObject.exportObject(remoteBridge, 0);
@@ -81,7 +183,10 @@ public class ServerMain {
}
public static void main(String[] args) throws RemoteException {
- ServerMain server = startServer(REGISTRY_BASE_PORT, 10);
- System.out.println("Server is running on port " + server.getPort());
+ int basePort = args.length > 0 ? Integer.parseInt(args[0]) : REGISTRY_BASE_PORT;
+ int limit = args.length > 1 ? Integer.parseInt(args[1]) : 10;
+
+ ServerMain server = startServer(basePort, limit);
+ System.out.println(RUNNING_SERVER_STR + server.getPort());
}
}
diff --git a/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java b/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
index 57295798..b685098e 100644
--- a/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
+++ b/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -26,25 +26,138 @@ import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams.Key;
import com.android.layout.remote.api.RemoteLayoutlibCallback;
import com.android.layout.remote.api.RemoteLayoutlibCallback.RemoteResolveResult;
+import com.android.layoutlib.bridge.MockView;
import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.function.Function;
public class RemoteLayoutlibCallbackAdapter extends LayoutlibCallback {
private final RemoteLayoutlibCallback mDelegate;
+ private final PathClassLoader mPathClassLoader;
public RemoteLayoutlibCallbackAdapter(@NotNull RemoteLayoutlibCallback remote) {
mDelegate = remote;
+
+ // Views requested to this callback need to be brought from the "client" side.
+ // We transform any loadView into two operations:
+ // - First we ask to where the class is located on disk via findClassPath
+ // - Second, we instantiate the class in the "server" side
+ HashMap<String, Path> nameToPathCache = new HashMap<>();
+ Function<String, Path> getPathFromName = cacheName -> nameToPathCache.computeIfAbsent(
+ cacheName,
+ name -> {
+ try {
+ return mDelegate.findClassPath(name);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ mPathClassLoader = new PathClassLoader(getPathFromName);
+ }
+
+ @NotNull
+ private Object createNewInstance(@NotNull Class<?> clazz,
+ @Nullable Class<?>[] constructorSignature, @Nullable Object[] constructorParameters,
+ boolean isView)
+ throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException,
+ IllegalAccessException, InstantiationException {
+ Constructor<?> constructor = null;
+
+ try {
+ constructor = clazz.getConstructor(constructorSignature);
+ } catch (NoSuchMethodException e) {
+ if (!isView) {
+ throw e;
+ }
+
+ // View class has 1-parameter, 2-parameter and 3-parameter constructors
+
+ final int paramsCount = constructorSignature != null ? constructorSignature.length : 0;
+ if (paramsCount == 0) {
+ throw e;
+ }
+ assert constructorParameters != null;
+
+ for (int i = 3; i >= 1; i--) {
+ if (i == paramsCount) {
+ continue;
+ }
+
+ final int k = paramsCount < i ? paramsCount : i;
+
+ final Class[] sig = new Class[i];
+ System.arraycopy(constructorSignature, 0, sig, 0, k);
+
+ final Object[] params = new Object[i];
+ System.arraycopy(constructorParameters, 0, params, 0, k);
+
+ for (int j = k + 1; j <= i; j++) {
+ if (j == 2) {
+ sig[j - 1] = findClass("android.util.AttributeSet");
+ params[j - 1] = null;
+ } else if (j == 3) {
+ // parameter 3: int defstyle
+ sig[j - 1] = int.class;
+ params[j - 1] = 0;
+ }
+ }
+
+ constructorSignature = sig;
+ constructorParameters = params;
+
+ try {
+ constructor = clazz.getConstructor(constructorSignature);
+ if (constructor != null) {
+ if (constructorSignature.length < 2) {
+ // TODO: Convert this to remote
+// LOG.info("wrong_constructor: Custom view " +
+// clazz.getSimpleName() +
+// " is not using the 2- or 3-argument " +
+// "View constructors; XML attributes will not work");
+// mDelegate.warning("wrongconstructor", //$NON-NLS-1$
+// String.format(
+// "Custom view %1$s is not using the 2- or 3-argument
+// View constructors; XML attributes will not work",
+// clazz.getSimpleName()), null, null);
+ }
+ break;
+ }
+ } catch (NoSuchMethodException ignored) {
+ }
+ }
+
+ if (constructor == null) {
+ throw e;
+ }
+ }
+
+ constructor.setAccessible(true);
+ return constructor.newInstance(constructorParameters);
}
@Override
public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
throws Exception {
- throw new UnsupportedOperationException("Not implemented yet");
+ Class<?> viewClass = MockView.class;
+ try {
+ viewClass = findClass(name);
+ } catch (ClassNotFoundException ignore) {
+ // MockView will be used instead
+ }
+ return createNewInstance(viewClass, constructorSignature, constructorArgs, true);
}
@Override
@@ -152,7 +265,7 @@ public class RemoteLayoutlibCallbackAdapter extends LayoutlibCallback {
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
- throw new UnsupportedOperationException("Not implemented yet");
+ return mPathClassLoader.loadClass(name);
}
@Override
@@ -163,4 +276,30 @@ public class RemoteLayoutlibCallbackAdapter extends LayoutlibCallback {
throw new RuntimeException(e);
}
}
+
+ /**
+ * Simple class loaders that loads classes from Paths
+ */
+ private static class PathClassLoader extends ClassLoader {
+ private final Function<String, Path> mGetPath;
+
+ private PathClassLoader(@NotNull Function<String, Path> getUrl) {
+ mGetPath = getUrl;
+ }
+
+ @Override
+ protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
+ Path path = mGetPath.apply(name);
+
+ if (path != null) {
+ try {
+ byte[] content = Files.readAllBytes(path);
+ return defineClass(name, content, 0, content.length);
+ } catch (IOException ignore) {
+ }
+ }
+
+ throw new ClassNotFoundException(name);
+ }
+ }
}
diff --git a/com/android/printspooler/ui/SelectPrinterActivity.java b/com/android/printspooler/ui/SelectPrinterActivity.java
index a9a6cbd4..7c2e55f3 100644
--- a/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -122,7 +122,7 @@ public final class SelectPrinterActivity extends Activity implements
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getActionBar().setIcon(R.drawable.ic_print);
+ getActionBar().setIcon(com.android.internal.R.drawable.ic_print);
setContentView(R.layout.select_printer_activity);
diff --git a/com/android/providers/settings/DatabaseHelper.java b/com/android/providers/settings/DatabaseHelper.java
index 1557d911..74281492 100644
--- a/com/android/providers/settings/DatabaseHelper.java
+++ b/com/android/providers/settings/DatabaseHelper.java
@@ -1836,20 +1836,10 @@ class DatabaseHelper extends SQLiteOpenHelper {
}
if (upgradeVersion < 116) {
- if (mUserHandle == UserHandle.USER_SYSTEM) {
- db.beginTransaction();
- SQLiteStatement stmt = null;
- try {
- stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)"
- + " VALUES(?,?);");
- loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED,
- ImsConfig.FeatureValueConstants.ON);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- if (stmt != null) stmt.close();
- }
- }
+ /*
+ * To control the default value by carrier config manager, initializing
+ * ENHANCED_4G_MODE_ENABLED has been removed.
+ */
upgradeVersion = 116;
}
@@ -2633,9 +2623,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
loadSetting(stmt, Settings.Global.DEVICE_NAME, getDefaultDeviceName());
- loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED,
- ImsConfig.FeatureValueConstants.ON);
-
/*
* IMPORTANT: Do not add any more upgrade steps here as the global,
* secure, and system settings are no longer stored in a database
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index 67fb4d9f..41b205bc 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -37,9 +37,11 @@ class SettingsProtoDumpUtil {
// Global settings
SettingsState globalSettings = settingsRegistry.getSettingsLocked(
SettingsProvider.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
- long globalSettingsToken = proto.start(SettingsServiceDumpProto.GLOBAL_SETTINGS);
- dumpProtoGlobalSettingsLocked(globalSettings, proto);
- proto.end(globalSettingsToken);
+ if (globalSettings != null) {
+ long globalSettingsToken = proto.start(SettingsServiceDumpProto.GLOBAL_SETTINGS);
+ dumpProtoGlobalSettingsLocked(globalSettings, proto);
+ proto.end(globalSettingsToken);
+ }
// Per-user settings
SparseBooleanArray users = settingsRegistry.getKnownUsersLocked();
@@ -67,19 +69,26 @@ class SettingsProtoDumpUtil {
SettingsState secureSettings = settingsRegistry.getSettingsLocked(
SettingsProvider.SETTINGS_TYPE_SECURE, user.getIdentifier());
- long secureSettingsToken = proto.start(UserSettingsProto.SECURE_SETTINGS);
- dumpProtoSecureSettingsLocked(secureSettings, proto);
- proto.end(secureSettingsToken);
+ if (secureSettings != null) {
+ long secureSettingsToken = proto.start(UserSettingsProto.SECURE_SETTINGS);
+ dumpProtoSecureSettingsLocked(secureSettings, proto);
+ proto.end(secureSettingsToken);
+ }
SettingsState systemSettings = settingsRegistry.getSettingsLocked(
SettingsProvider.SETTINGS_TYPE_SYSTEM, user.getIdentifier());
- long systemSettingsToken = proto.start(UserSettingsProto.SYSTEM_SETTINGS);
- dumpProtoSystemSettingsLocked(systemSettings, proto);
- proto.end(systemSettingsToken);
+ if (systemSettings != null) {
+ long systemSettingsToken = proto.start(UserSettingsProto.SYSTEM_SETTINGS);
+ dumpProtoSystemSettingsLocked(systemSettings, proto);
+ proto.end(systemSettingsToken);
+ }
}
private static void dumpProtoGlobalSettingsLocked(
@NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ s.dumpHistoricalOperations(p, GlobalSettingsProto.HISTORICAL_OPERATIONS);
+
+ // This uses the same order as in Settings.Global.
dumpSetting(s, p,
Settings.Global.ADD_USERS_WHEN_LOCKED,
GlobalSettingsProto.ADD_USERS_WHEN_LOCKED);
@@ -114,6 +123,9 @@ class SettingsProtoDumpUtil {
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
GlobalSettingsProto.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_CLASS_OF_DEVICE,
+ GlobalSettingsProto.BLUETOOTH_CLASS_OF_DEVICE);
+ dumpSetting(s, p,
Settings.Global.BLUETOOTH_DISABLED_PROFILES,
GlobalSettingsProto.BLUETOOTH_DISABLED_PROFILES);
dumpSetting(s, p,
@@ -194,6 +206,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.CDMA_SUBSCRIPTION_MODE,
GlobalSettingsProto.CDMA_SUBSCRIPTION_MODE);
+ // Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA intentionally excluded.
dumpSetting(s, p,
Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
GlobalSettingsProto.DATA_ACTIVITY_TIMEOUT_MOBILE);
@@ -209,6 +222,10 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
GlobalSettingsProto.FORCE_ALLOW_ON_EXTERNAL);
+ // Settings.Global.DEFAULT_SM_DP_PLUS intentionally excluded.
+ dumpSetting(s, p,
+ Settings.Global.EUICC_PROVISIONED,
+ GlobalSettingsProto.EUICC_PROVISIONED);
dumpSetting(s, p,
Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
GlobalSettingsProto.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
@@ -236,6 +253,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
GlobalSettingsProto.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+ // Settings.Global.INSTALL_NON_MARKET_APPS intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Global.HDMI_CONTROL_ENABLED,
GlobalSettingsProto.HDMI_CONTROL_ENABLED);
@@ -249,6 +267,21 @@ class SettingsProtoDumpUtil {
Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
GlobalSettingsProto.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED);
dumpSetting(s, p,
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+ GlobalSettingsProto.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+ GlobalSettingsProto.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+ GlobalSettingsProto.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS,
+ GlobalSettingsProto.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+ GlobalSettingsProto.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+ dumpSetting(s, p,
Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
GlobalSettingsProto.MHL_INPUT_SWITCHING_ENABLED);
dumpSetting(s, p,
@@ -279,6 +312,9 @@ class SettingsProtoDumpUtil {
Settings.Global.NETSTATS_SAMPLE_ENABLED,
GlobalSettingsProto.NETSTATS_SAMPLE_ENABLED);
dumpSetting(s, p,
+ Settings.Global.NETSTATS_AUGMENT_ENABLED,
+ GlobalSettingsProto.NETSTATS_AUGMENT_ENABLED);
+ dumpSetting(s, p,
Settings.Global.NETSTATS_DEV_BUCKET_DURATION,
GlobalSettingsProto.NETSTATS_DEV_BUCKET_DURATION);
dumpSetting(s, p,
@@ -420,6 +456,9 @@ class SettingsProtoDumpUtil {
Settings.Global.TETHER_DUN_APN,
GlobalSettingsProto.TETHER_DUN_APN);
dumpSetting(s, p,
+ Settings.Global.TETHER_OFFLOAD_DISABLED,
+ GlobalSettingsProto.TETHER_OFFLOAD_DISABLED);
+ dumpSetting(s, p,
Settings.Global.CARRIER_APP_WHITELIST,
GlobalSettingsProto.CARRIER_APP_WHITELIST);
dumpSetting(s, p,
@@ -450,6 +489,15 @@ class SettingsProtoDumpUtil {
Settings.Global.NETWORK_AVOID_BAD_WIFI,
GlobalSettingsProto.NETWORK_AVOID_BAD_WIFI);
dumpSetting(s, p,
+ Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
+ GlobalSettingsProto.NETWORK_METERED_MULTIPATH_PREFERENCE);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+ GlobalSettingsProto.NETWORK_WATCHLIST_LAST_REPORT_TIME);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_BADGING_THRESHOLDS,
+ GlobalSettingsProto.WIFI_BADGING_THRESHOLDS);
+ dumpSetting(s, p,
Settings.Global.WIFI_DISPLAY_ON,
GlobalSettingsProto.WIFI_DISPLAY_ON);
dumpSetting(s, p,
@@ -489,12 +537,30 @@ class SettingsProtoDumpUtil {
Settings.Global.WIFI_WAKEUP_ENABLED,
GlobalSettingsProto.WIFI_WAKEUP_ENABLED);
dumpSetting(s, p,
+ Settings.Global.WIFI_WAKEUP_AVAILABLE,
+ GlobalSettingsProto.WIFI_WAKEUP_AVAILABLE);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SCORING_UI_ENABLED,
+ GlobalSettingsProto.NETWORK_SCORING_UI_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
+ GlobalSettingsProto.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS);
+ dumpSetting(s, p,
Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
GlobalSettingsProto.NETWORK_RECOMMENDATIONS_ENABLED);
dumpSetting(s, p,
Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
GlobalSettingsProto.NETWORK_RECOMMENDATIONS_PACKAGE);
dumpSetting(s, p,
+ Settings.Global.USE_OPEN_WIFI_PACKAGE,
+ GlobalSettingsProto.USE_OPEN_WIFI_PACKAGE);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
+ GlobalSettingsProto.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
+ dumpSetting(s, p,
+ Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
+ GlobalSettingsProto.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS);
+ dumpSetting(s, p,
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
GlobalSettingsProto.BLE_SCAN_ALWAYS_AVAILABLE);
dumpSetting(s, p,
@@ -612,6 +678,12 @@ class SettingsProtoDumpUtil {
Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
GlobalSettingsProto.SYS_STORAGE_FULL_THRESHOLD_BYTES);
dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE,
+ GlobalSettingsProto.SYS_STORAGE_CACHE_PERCENTAGE);
+ dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
+ GlobalSettingsProto.SYS_STORAGE_CACHE_MAX_BYTES);
+ dumpSetting(s, p,
Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
GlobalSettingsProto.SYNC_MAX_RETRY_DELAY_IN_SECONDS);
dumpSetting(s, p,
@@ -627,6 +699,9 @@ class SettingsProtoDumpUtil {
Settings.Global.CAPTIVE_PORTAL_MODE,
GlobalSettingsProto.CAPTIVE_PORTAL_MODE);
dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED,
+ GlobalSettingsProto.CAPTIVE_PORTAL_DETECTION_ENABLED);
+ dumpSetting(s, p,
Settings.Global.CAPTIVE_PORTAL_SERVER,
GlobalSettingsProto.CAPTIVE_PORTAL_SERVER);
dumpSetting(s, p,
@@ -639,6 +714,9 @@ class SettingsProtoDumpUtil {
Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
GlobalSettingsProto.CAPTIVE_PORTAL_FALLBACK_URL);
dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
+ GlobalSettingsProto.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS);
+ dumpSetting(s, p,
Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
GlobalSettingsProto.CAPTIVE_PORTAL_USE_HTTPS);
dumpSetting(s, p,
@@ -684,6 +762,12 @@ class SettingsProtoDumpUtil {
Settings.Global.DEFAULT_DNS_SERVER,
GlobalSettingsProto.DEFAULT_DNS_SERVER);
dumpSetting(s, p,
+ Settings.Global.PRIVATE_DNS_MODE,
+ GlobalSettingsProto.PRIVATE_DNS_MODE);
+ dumpSetting(s, p,
+ Settings.Global.PRIVATE_DNS_SPECIFIER,
+ GlobalSettingsProto.PRIVATE_DNS_SPECIFIER);
+ dumpSetting(s, p,
Settings.Global.BLUETOOTH_HEADSET_PRIORITY_PREFIX,
GlobalSettingsProto.BLUETOOTH_HEADSET_PRIORITY_PREFIX);
dumpSetting(s, p,
@@ -717,12 +801,27 @@ class SettingsProtoDumpUtil {
Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
dumpSetting(s, p,
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
+ GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
Settings.Global.DEVICE_IDLE_CONSTANTS,
GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
dumpSetting(s, p,
+ Settings.Global.BATTERY_SAVER_CONSTANTS,
+ GlobalSettingsProto.BATTERY_SAVER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.ANOMALY_DETECTION_CONSTANTS,
+ GlobalSettingsProto.ANOMALY_DETECTION_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
+ GlobalSettingsProto.ALWAYS_ON_DISPLAY_CONSTANTS);
+ dumpSetting(s, p,
Settings.Global.APP_IDLE_CONSTANTS,
GlobalSettingsProto.APP_IDLE_CONSTANTS);
dumpSetting(s, p,
+ Settings.Global.POWER_MANAGER_CONSTANTS,
+ GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
Settings.Global.ALARM_MANAGER_CONSTANTS,
GlobalSettingsProto.ALARM_MANAGER_CONSTANTS);
dumpSetting(s, p,
@@ -732,6 +831,12 @@ class SettingsProtoDumpUtil {
Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
GlobalSettingsProto.SHORTCUT_MANAGER_CONSTANTS);
dumpSetting(s, p,
+ Settings.Global.DEVICE_POLICY_CONSTANTS,
+ GlobalSettingsProto.DEVICE_POLICY_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
+ GlobalSettingsProto.TEXT_CLASSIFIER_CONSTANTS);
+ dumpSetting(s, p,
Settings.Global.WINDOW_ANIMATION_SCALE,
GlobalSettingsProto.WINDOW_ANIMATION_SCALE);
dumpSetting(s, p,
@@ -764,6 +869,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.WAIT_FOR_DEBUGGER,
GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+ // Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Global.LOW_POWER_MODE,
GlobalSettingsProto.LOW_POWER_MODE);
@@ -819,6 +925,18 @@ class SettingsProtoDumpUtil {
Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
GlobalSettingsProto.INTENT_FIREWALL_UPDATE_METADATA_URL);
dumpSetting(s, p,
+ Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.LANG_ID_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.LANG_ID_UPDATE_METADATA_URL,
+ GlobalSettingsProto.LANG_ID_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.SMART_SELECTION_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
+ GlobalSettingsProto.SMART_SELECTION_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
Settings.Global.SELINUX_STATUS,
GlobalSettingsProto.SELINUX_STATUS);
dumpSetting(s, p,
@@ -882,11 +1000,8 @@ class SettingsProtoDumpUtil {
Settings.Global.ENABLE_EPHEMERAL_FEATURE,
GlobalSettingsProto.ENABLE_EPHEMERAL_FEATURE);
dumpSetting(s, p,
- Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
- GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
- dumpSetting(s, p,
- Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
- GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
+ Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
+ GlobalSettingsProto.INSTANT_APP_DEXOPT_ENABLED);
dumpSetting(s, p,
Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
GlobalSettingsProto.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
@@ -894,6 +1009,12 @@ class SettingsProtoDumpUtil {
Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
GlobalSettingsProto.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
dumpSetting(s, p,
+ Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+ GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
+ dumpSetting(s, p,
+ Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
+ GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
+ dumpSetting(s, p,
Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
GlobalSettingsProto.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD);
dumpSetting(s, p,
@@ -909,12 +1030,30 @@ class SettingsProtoDumpUtil {
Settings.Global.DEVICE_DEMO_MODE,
GlobalSettingsProto.DEVICE_DEMO_MODE);
dumpSetting(s, p,
+ Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
+ GlobalSettingsProto.NETWORK_ACCESS_TIMEOUT_MS);
+ dumpSetting(s, p,
Settings.Global.DATABASE_DOWNGRADE_REASON,
GlobalSettingsProto.DATABASE_DOWNGRADE_REASON);
dumpSetting(s, p,
+ Settings.Global.DATABASE_CREATION_BUILDID,
+ GlobalSettingsProto.DATABASE_CREATION_BUILDID);
+ dumpSetting(s, p,
Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
GlobalSettingsProto.CONTACTS_DATABASE_WAL_ENABLED);
dumpSetting(s, p,
+ Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
+ GlobalSettingsProto.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED,
+ GlobalSettingsProto.BACKUP_REFACTORED_SERVICE_DISABLED);
+ dumpSetting(s, p,
+ Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
+ GlobalSettingsProto.EUICC_FACTORY_RESET_TIMEOUT_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.STORAGE_SETTINGS_CLOBBER_THRESHOLD,
+ GlobalSettingsProto.STORAGE_SETTINGS_CLOBBER_THRESHOLD);
+ dumpSetting(s, p,
Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
GlobalSettingsProto.MULTI_SIM_VOICE_CALL_SUBSCRIPTION);
dumpSetting(s, p,
@@ -932,6 +1071,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.NEW_CONTACT_AGGREGATOR,
GlobalSettingsProto.NEW_CONTACT_AGGREGATOR);
+ // Settings.Global.CONTACT_METADATA_SYNC intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Global.CONTACT_METADATA_SYNC_ENABLED,
GlobalSettingsProto.CONTACT_METADATA_SYNC_ENABLED);
@@ -942,8 +1082,31 @@ class SettingsProtoDumpUtil {
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
GlobalSettingsProto.MAX_NOTIFICATION_ENQUEUE_RATE);
dumpSetting(s, p,
+ Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
+ GlobalSettingsProto.SHOW_NOTIFICATION_CHANNEL_WARNINGS);
+ dumpSetting(s, p,
Settings.Global.CELL_ON,
GlobalSettingsProto.CELL_ON);
+ dumpSetting(s, p,
+ Settings.Global.SHOW_TEMPERATURE_WARNING,
+ GlobalSettingsProto.SHOW_TEMPERATURE_WARNING);
+ dumpSetting(s, p,
+ Settings.Global.WARNING_TEMPERATURE,
+ GlobalSettingsProto.WARNING_TEMPERATURE);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_DISKSTATS_LOGGING,
+ GlobalSettingsProto.ENABLE_DISKSTATS_LOGGING);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION,
+ GlobalSettingsProto.ENABLE_CACHE_QUOTA_CALCULATION);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
+ GlobalSettingsProto.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE);
+ // The list of snooze options for notifications. This is encoded as a key=value list,
+ // separated by commas.
+ dumpSetting(s, p,
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ GlobalSettingsProto.NOTIFICATION_SNOOZE_OPTIONS);
}
/** Dump a single {@link SettingsState.Setting} to a proto buf */
@@ -966,9 +1129,19 @@ class SettingsProtoDumpUtil {
static void dumpProtoSecureSettingsLocked(
@NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ s.dumpHistoricalOperations(p, SecureSettingsProto.HISTORICAL_OPERATIONS);
+
+ // This uses the same order as in Settings.Secure.
+
+ // Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED intentionally excluded since it's deprecated.
+ // Settings.Secure.BUGREPORT_IN_POWER_MENU intentionally excluded since it's deprecated.
+ // Settings.Secure.ADB_ENABLED intentionally excluded since it's deprecated.
+ // Settings.Secure.ALLOW_MOCK_LOCATION intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.ANDROID_ID,
SecureSettingsProto.ANDROID_ID);
+ // Settings.Secure.BLUETOOTH_ON intentionally excluded since it's deprecated.
+ // Settings.Secure.DATA_ROAMING intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.DEFAULT_INPUT_METHOD,
SecureSettingsProto.DEFAULT_INPUT_METHOD);
@@ -987,9 +1160,16 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.AUTOFILL_SERVICE,
SecureSettingsProto.AUTOFILL_SERVICE);
+ // Settings.Secure.DEVICE_PROVISIONED intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.USER_SETUP_COMPLETE,
SecureSettingsProto.USER_SETUP_COMPLETE);
+ // Whether the current user has been set up via setup wizard (0 = false, 1 = true). This
+ // value differs from USER_SETUP_COMPLETE in that it can be reset back to 0 in case
+ // SetupWizard has been re-enabled on TV devices.
+ dumpSetting(s, p,
+ Settings.Secure.TV_USER_SETUP_COMPLETE,
+ SecureSettingsProto.TV_USER_SETUP_COMPLETE);
dumpSetting(s, p,
Settings.Secure.COMPLETED_CATEGORY_PREFIX,
SecureSettingsProto.COMPLETED_CATEGORY_PREFIX);
@@ -1002,6 +1182,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
SecureSettingsProto.SHOW_IME_WITH_HARD_KEYBOARD);
+ // Settings.Secure.HTTP_PROXY intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.ALWAYS_ON_VPN_APP,
SecureSettingsProto.ALWAYS_ON_VPN_APP);
@@ -1012,17 +1193,33 @@ class SettingsProtoDumpUtil {
Settings.Secure.INSTALL_NON_MARKET_APPS,
SecureSettingsProto.INSTALL_NON_MARKET_APPS);
dumpSetting(s, p,
+ Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED,
+ SecureSettingsProto.UNKNOWN_SOURCES_DEFAULT_REVERSED);
+ // Settings.Secure.LOCATION_PROVIDERS_ALLOWED intentionally excluded since it's deprecated.
+ dumpSetting(s, p,
Settings.Secure.LOCATION_MODE,
SecureSettingsProto.LOCATION_MODE);
dumpSetting(s, p,
Settings.Secure.LOCATION_PREVIOUS_MODE,
SecureSettingsProto.LOCATION_PREVIOUS_MODE);
+ // Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
SecureSettingsProto.LOCK_TO_APP_EXIT_LOCKED);
+ // Settings.Secure.LOCK_PATTERN_ENABLED intentionally excluded since it's deprecated.
+ // Settings.Secure.LOCK_PATTERN_VISIBLE intentionally excluded since it's deprecated.
+ // Settings.Secure.LOCK_PATTERN_TACTICLE_FEEDBACK_ENABLED intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
SecureSettingsProto.LOCK_SCREEN_LOCK_AFTER_TIMEOUT);
+ // Settings.Secure.LOCK_SCREEN_OWNER_INFO intentionally excluded since it's deprecated.
+ // Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS intentionally excluded since it's deprecated.
+ // Settings.Secure.LOCK_SCREEN_FALLBACK_APPWIDGET_ID intentionally excluded since it's deprecated.
+ // Settings.Secure.LOCK_SCREEN_STICKY_APPWIDGET intentionally excluded since it's deprecated.
+ // Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED intentionally excluded since it's deprecated.
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ SecureSettingsProto.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
dumpSetting(s, p,
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
SecureSettingsProto.LOCK_SCREEN_ALLOW_REMOTE_INPUT);
@@ -1032,6 +1229,8 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.TRUST_AGENTS_INITIALIZED,
SecureSettingsProto.TRUST_AGENTS_INITIALIZED);
+ // Settings.Secure.LOGGING_ID intentionally excluded since it's deprecated.
+ // Settings.Secure.NETWORK_PREFERENCE intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.PARENTAL_CONTROL_ENABLED,
SecureSettingsProto.PARENTAL_CONTROL_ENABLED);
@@ -1044,10 +1243,27 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.SETTINGS_CLASSNAME,
SecureSettingsProto.SETTINGS_CLASSNAME);
+ // Settings.Secure.USB_MASS_STORAGE_ENABLED intentionally excluded since it's deprecated.
+ // Settings.Secure.USE_GOOGLE_MAIL intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_ENABLED,
SecureSettingsProto.ACCESSIBILITY_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_SHORTCUT_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
+ SecureSettingsProto.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ SecureSettingsProto.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ SecureSettingsProto.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ SecureSettingsProto.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+ dumpSetting(s, p,
Settings.Secure.TOUCH_EXPLORATION_ENABLED,
SecureSettingsProto.TOUCH_EXPLORATION_ENABLED);
dumpSetting(s, p,
@@ -1066,9 +1282,15 @@ class SettingsProtoDumpUtil {
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
SecureSettingsProto.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
dumpSetting(s, p,
@@ -1134,6 +1356,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.DISPLAY_DENSITY_FORCED,
SecureSettingsProto.DISPLAY_DENSITY_FORCED);
+ // Settings.Secure.TTS_USE_DEFAULTS intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.TTS_DEFAULT_RATE,
SecureSettingsProto.TTS_DEFAULT_RATE);
@@ -1143,15 +1366,37 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.TTS_DEFAULT_SYNTH,
SecureSettingsProto.TTS_DEFAULT_SYNTH);
+ // Settings.Secure.TTS_DEFAULT_LANG intentionally excluded since it's deprecated.
+ // Settings.Secure.TTS_DEFAULT_COUNTRY intentionally excluded since it's deprecated.
+ // Settings.Secure.TTS_DEFAULT_VARIANT intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.TTS_DEFAULT_LOCALE,
SecureSettingsProto.TTS_DEFAULT_LOCALE);
dumpSetting(s, p,
Settings.Secure.TTS_ENABLED_PLUGINS,
SecureSettingsProto.TTS_ENABLED_PLUGINS);
+ // Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_ON intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_AP_COUNT intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_ON intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_WATCH_LIST intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_PING_COUNT intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT intentionally excluded since it's deprecated.
+ // Settings.Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
SecureSettingsProto.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS);
+ // Settings.Secure.BACKGROUND_DATA intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
SecureSettingsProto.ALLOWED_GEOLOCATION_ORIGINS);
@@ -1179,6 +1424,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.LAST_SETUP_SHOWN,
SecureSettingsProto.LAST_SETUP_SHOWN);
+ // Settings.Secure.WIFI_IDLE_MS intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
SecureSettingsProto.SEARCH_GLOBAL_SEARCH_ACTIVITY);
@@ -1285,6 +1531,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.DOZE_PULSE_ON_PICK_UP,
SecureSettingsProto.DOZE_PULSE_ON_PICK_UP);
dumpSetting(s, p,
+ Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
+ SecureSettingsProto.DOZE_PULSE_ON_LONG_PRESS);
+ dumpSetting(s, p,
Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
SecureSettingsProto.DOZE_PULSE_ON_DOUBLE_TAP);
dumpSetting(s, p,
@@ -1351,6 +1600,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
SecureSettingsProto.PAYMENT_SERVICE_SEARCH_URI);
dumpSetting(s, p,
+ Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
+ SecureSettingsProto.AUTOFILL_SERVICE_SEARCH_URI);
+ dumpSetting(s, p,
Settings.Secure.SKIP_FIRST_USE_HINTS,
SecureSettingsProto.SKIP_FIRST_USE_HINTS);
dumpSetting(s, p,
@@ -1387,18 +1639,42 @@ class SettingsProtoDumpUtil {
Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
SecureSettingsProto.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED,
+ SecureSettingsProto.CAMERA_LIFT_TRIGGER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_GESTURE_ENABLED,
+ SecureSettingsProto.ASSIST_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_GESTURE_SENSITIVITY,
+ SecureSettingsProto.ASSIST_GESTURE_SENSITIVITY);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
+ SecureSettingsProto.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_GESTURE_WAKE_ENABLED,
+ SecureSettingsProto.ASSIST_GESTURE_WAKE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
+ SecureSettingsProto.ASSIST_GESTURE_SETUP_COMPLETE);
+ dumpSetting(s, p,
Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
SecureSettingsProto.NIGHT_DISPLAY_ACTIVATED);
dumpSetting(s, p,
Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
SecureSettingsProto.NIGHT_DISPLAY_AUTO_MODE);
dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+ SecureSettingsProto.NIGHT_DISPLAY_COLOR_TEMPERATURE);
+ dumpSetting(s, p,
Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_START_TIME);
dumpSetting(s, p,
Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_END_TIME);
dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+ SecureSettingsProto.NIGHT_DISPLAY_LAST_ACTIVATED_TIME);
+ dumpSetting(s, p,
Settings.Secure.ENABLED_VR_LISTENERS,
SecureSettingsProto.ENABLED_VR_LISTENERS);
dumpSetting(s, p,
@@ -1423,6 +1699,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_LAST_RUN);
dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY);
+ dumpSetting(s, p,
Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
SecureSettingsProto.SYSTEM_NAVIGATION_KEYS_ENABLED);
dumpSetting(s, p,
@@ -1438,33 +1717,76 @@ class SettingsProtoDumpUtil {
Settings.Secure.DEVICE_PAIRED,
SecureSettingsProto.DEVICE_PAIRED);
dumpSetting(s, p,
+ Settings.Secure.PACKAGE_VERIFIER_STATE,
+ SecureSettingsProto.PACKAGE_VERIFIER_STATE);
+ dumpSetting(s, p,
+ Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
+ SecureSettingsProto.CMAS_ADDITIONAL_BROADCAST_PKG);
+ dumpSetting(s, p,
Settings.Secure.NOTIFICATION_BADGING,
SecureSettingsProto.NOTIFICATION_BADGING);
dumpSetting(s, p,
+ Settings.Secure.QS_AUTO_ADDED_TILES,
+ SecureSettingsProto.QS_AUTO_ADDED_TILES);
+ dumpSetting(s, p,
+ Settings.Secure.LOCKDOWN_IN_POWER_MENU,
+ SecureSettingsProto.LOCKDOWN_IN_POWER_MENU);
+ dumpSetting(s, p,
Settings.Secure.BACKUP_MANAGER_CONSTANTS,
SecureSettingsProto.BACKUP_MANAGER_CONSTANTS);
}
private static void dumpProtoSystemSettingsLocked(
@NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ s.dumpHistoricalOperations(p, SystemSettingsProto.HISTORICAL_OPERATIONS);
+
+ // This uses the same order as in Settings.System.
+
+ // Settings.System.STAY_ON_WHILE_PLUGGED_IN intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.END_BUTTON_BEHAVIOR,
SystemSettingsProto.END_BUTTON_BEHAVIOR);
dumpSetting(s, p,
Settings.System.ADVANCED_SETTINGS,
SystemSettingsProto.ADVANCED_SETTINGS);
+ // Settings.System.AIRPLANE_MODE_ON intentionally excluded since it's deprecated.
+ // Settings.System.RADIO_BLUETOOTH intentionally excluded since it's deprecated.
+ // Settings.System.RADIO_WIFI intentionally excluded since it's deprecated.
+ // Settings.System.RADIO_WIMAX intentionally excluded since it's deprecated.
+ // Settings.System.RADIO_CELL intentionally excluded since it's deprecated.
+ // Settings.System.RADIO_NFC intentionally excluded since it's deprecated.
+ // Settings.System.AIRPLANE_MODE_RADIOS intentionally excluded since it's deprecated.
+ // Settings.System.AIRPLANE_MODE_TOGGLABLE_RADIOS intentionally excluded since it's deprecated.
+ // Settings.System.WIFI_SLEEP_POLICY intentionally excluded since it's deprecated.
+ // Settings.System.MODE_RINGER intentionally excluded since it's deprecated.
+ // Settings.System.WIFI_USE_STATIC_IP intentionally excluded since it's deprecated.
+ // Settings.System.WIFI_STATIC_IP intentionally excluded since it's deprecated.
+ // Settings.System.WIFI_STATIC_GATEWAY intentionally excluded since it's deprecated.
+ // Settings.System.WIFI_STATIC_NETMASK intentionally excluded since it's deprecated.
+ // Settings.System.WIFI_STATIC_DNS1 intentionally excluded since it's deprecated.
+ // Settings.System.WIFI_STATIC_DNS2 intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.BLUETOOTH_DISCOVERABILITY,
SystemSettingsProto.BLUETOOTH_DISCOVERABILITY);
dumpSetting(s, p,
Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
SystemSettingsProto.BLUETOOTH_DISCOVERABILITY_TIMEOUT);
+ // Settings.System.LOCK_PATTERN_ENABLED intentionally excluded since it's deprecated.
+ // Settings.System.LOCK_PATTERN_VISIBLE intentionally excluded since it's deprecated.
+ // Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED intentionally excluded since it's deprecated.
+ // Settings.System.NEXT_ALARM_FORMATTED intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.FONT_SCALE,
SystemSettingsProto.FONT_SCALE);
dumpSetting(s, p,
Settings.System.SYSTEM_LOCALES,
SystemSettingsProto.SYSTEM_LOCALES);
+ // Settings.System.DEBUG_APP intentionally excluded since it's deprecated.
+ // Settings.System.WAIT_FOR_DEBUGGER intentionally excluded since it's deprecated.
+ // Settings.System.DIM_SCREEN intentionally excluded since it's deprecated.
+ dumpSetting(s, p,
+ Settings.System.DISPLAY_COLOR_MODE,
+ SystemSettingsProto.DISPLAY_COLOR_MODE);
dumpSetting(s, p,
Settings.System.SCREEN_OFF_TIMEOUT,
SystemSettingsProto.SCREEN_OFF_TIMEOUT);
@@ -1480,6 +1802,8 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
SystemSettingsProto.SCREEN_AUTO_BRIGHTNESS_ADJ);
+ // Settings.System.SHOW_PROCESSES intentionally excluded since it's deprecated.
+ // Settings.System.ALWAYS_FINISH_ACTIVITIES intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
SystemSettingsProto.MODE_RINGER_STREAMS_AFFECTED);
@@ -1514,11 +1838,15 @@ class SettingsProtoDumpUtil {
Settings.System.VOLUME_BLUETOOTH_SCO,
SystemSettingsProto.VOLUME_BLUETOOTH_SCO);
dumpSetting(s, p,
+ Settings.System.VOLUME_ACCESSIBILITY,
+ SystemSettingsProto.VOLUME_ACCESSIBILITY);
+ dumpSetting(s, p,
Settings.System.VOLUME_MASTER,
SystemSettingsProto.VOLUME_MASTER);
dumpSetting(s, p,
Settings.System.MASTER_MONO,
SystemSettingsProto.MASTER_MONO);
+ // Settings.System.NOTIFICATIONS_USE_RING_VOLUME intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.VIBRATE_IN_SILENT,
SystemSettingsProto.VIBRATE_IN_SILENT);
@@ -1561,6 +1889,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.SHOW_GTALK_SERVICE_STATUS,
SystemSettingsProto.SHOW_GTALK_SERVICE_STATUS);
+ // Settings.System.WALLPAPER_ACTIVITY intentionally excluded since it's deprecated.
+ // Settings.System.AUTO_TIME intentionally excluded since it's deprecated.
+ // Settings.System.AUTO_TIME_ZONE intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.TIME_12_24,
SystemSettingsProto.TIME_12_24);
@@ -1570,6 +1901,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.SETUP_WIZARD_HAS_RUN,
SystemSettingsProto.SETUP_WIZARD_HAS_RUN);
+ // Settings.System.WINDOW_ANIMATION_SCALE intentionally excluded since it's deprecated.
+ // Settings.System.TRANSITION_ANIMATION_SCALE intentionally excluded since it's deprecated.
+ // Settings.System.ANIMATOR_ANIMATION_SCALE intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.ACCELEROMETER_ROTATION,
SystemSettingsProto.ACCELEROMETER_ROTATION);
@@ -1600,6 +1934,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.HAPTIC_FEEDBACK_ENABLED,
SystemSettingsProto.HAPTIC_FEEDBACK_ENABLED);
+ // Settings.System.SHOW_WEB_SUGGESTIONS intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.NOTIFICATION_LIGHT_PULSE,
SystemSettingsProto.NOTIFICATION_LIGHT_PULSE);
@@ -1612,12 +1947,21 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG,
SystemSettingsProto.WINDOW_ORIENTATION_LISTENER_LOG);
+ // Settings.System.POWER_SOUNDS_ENABLED intentionally excluded since it's deprecated.
+ // Settings.System.DOCK_SOUNDS_ENABLED intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
SystemSettingsProto.LOCKSCREEN_SOUNDS_ENABLED);
dumpSetting(s, p,
Settings.System.LOCKSCREEN_DISABLED,
SystemSettingsProto.LOCKSCREEN_DISABLED);
+ // Settings.System.LOW_BATTERY_SOUND intentionally excluded since it's deprecated.
+ // Settings.System.DESK_DOCK_SOUND intentionally excluded since it's deprecated.
+ // Settings.System.DESK_UNDOCK_SOUND intentionally excluded since it's deprecated.
+ // Settings.System.CAR_DOCK_SOUND intentionally excluded since it's deprecated.
+ // Settings.System.CAR_UNDOCK_SOUND intentionally excluded since it's deprecated.
+ // Settings.System.LOCK_SOUND intentionally excluded since it's deprecated.
+ // Settings.System.UNLOCK_SOUND intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.SIP_RECEIVE_CALLS,
SystemSettingsProto.SIP_RECEIVE_CALLS);
@@ -1630,6 +1974,7 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.SIP_ADDRESS_ONLY,
SystemSettingsProto.SIP_ADDRESS_ONLY);
+ // Settings.System.SIP_ASK_ME_EACH_TIME intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.System.POINTER_SPEED,
SystemSettingsProto.POINTER_SPEED);
@@ -1640,7 +1985,12 @@ class SettingsProtoDumpUtil {
Settings.System.EGG_MODE,
SystemSettingsProto.EGG_MODE);
dumpSetting(s, p,
+ Settings.System.SHOW_BATTERY_PERCENT,
+ SystemSettingsProto.SHOW_BATTERY_PERCENT);
+ dumpSetting(s, p,
Settings.System.WHEN_TO_MAKE_WIFI_CALLS,
SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS);
+ // The rest of the settings were moved to Settings.Secure, and are thus excluded here since
+ // they're deprecated from Settings.System.
}
}
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index 36f9b840..258c96cf 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -18,6 +18,7 @@ package com.android.providers.settings;
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.backup.BackupManager;
@@ -657,7 +658,6 @@ public class SettingsProvider extends ContentProvider {
synchronized (mLock) {
SettingsProtoDumpUtil.dumpProtoLocked(mSettingsRegistry, proto);
-
}
proto.flush();
@@ -2284,6 +2284,7 @@ public class SettingsProvider extends ContentProvider {
return users;
}
+ @Nullable
public SettingsState getSettingsLocked(int type, int userId) {
final int key = makeKey(type, userId);
return peekSettingsStateLocked(key);
@@ -2578,6 +2579,7 @@ public class SettingsProvider extends ContentProvider {
ssaidSettings.deleteSettingLocked(Integer.toString(uid));
}
+ @Nullable
private SettingsState peekSettingsStateLocked(int key) {
SettingsState settingsState = mSettingsStates.get(key);
if (settingsState != null) {
diff --git a/com/android/providers/settings/SettingsState.java b/com/android/providers/settings/SettingsState.java
index 4151ada9..f901bcae 100644
--- a/com/android/providers/settings/SettingsState.java
+++ b/com/android/providers/settings/SettingsState.java
@@ -434,8 +434,9 @@ final class SettingsState {
* Dump historical operations as a proto buf.
*
* @param proto The proto buf stream to dump to
+ * @param fieldId The repeated field ID to use to save an operation to.
*/
- void dumpProtoHistoricalOperations(@NonNull ProtoOutputStream proto) {
+ void dumpHistoricalOperations(@NonNull ProtoOutputStream proto, long fieldId) {
synchronized (mLock) {
if (mHistoricalOperations == null) {
return;
@@ -448,7 +449,8 @@ final class SettingsState {
index = operationCount + index;
}
HistoricalOperation operation = mHistoricalOperations.get(index);
- long settingsOperationToken = proto.start(GlobalSettingsProto.HISTORICAL_OP);
+
+ final long token = proto.start(fieldId);
proto.write(SettingsOperationProto.TIMESTAMP, operation.mTimestamp);
proto.write(SettingsOperationProto.OPERATION, operation.mOperation);
if (operation.mSetting != null) {
@@ -457,7 +459,7 @@ final class SettingsState {
// add is what the current data is).
proto.write(SettingsOperationProto.SETTING, operation.mSetting.getName());
}
- proto.end(settingsOperationToken);
+ proto.end(token);
}
}
}
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 68546bd2..ea0ed271 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -38,7 +38,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
-import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V1_0.HealthInfo;
import android.hardware.health.V2_0.IHealthInfoCallback;
import android.hardware.health.V2_0.IHealth;
import android.hardware.health.V2_0.Result;
@@ -49,6 +49,7 @@ import android.os.BatteryProperty;
import android.os.Binder;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBatteryPropertiesListener;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
@@ -56,6 +57,7 @@ import android.os.DropBoxManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UEventObserver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -74,6 +76,7 @@ import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -175,6 +178,7 @@ public final class BatteryService extends SystemService {
private HealthServiceWrapper mHealthServiceWrapper;
private HealthHalCallback mHealthHalCallback;
private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+ private HandlerThread mHandlerThread;
public BatteryService(Context context) {
super(context);
@@ -246,6 +250,7 @@ public final class BatteryService extends SystemService {
}
private void registerHealthCallback() {
+ traceBegin("HealthInitWrapper");
mHealthServiceWrapper = new HealthServiceWrapper();
mHealthHalCallback = new HealthHalCallback();
// IHealth is lazily retrieved.
@@ -259,8 +264,11 @@ public final class BatteryService extends SystemService {
} catch (NoSuchElementException ex) {
Slog.e(TAG, "health: cannot register callback. (no supported health HAL service)");
throw ex;
+ } finally {
+ traceEnd();
}
+ traceBegin("HealthInitWaitUpdate");
// init register for new service notifications, and IServiceManager should return the
// existing service in a near future. Wait for this.update() to instantiate
// the initial mHealthInfo.
@@ -280,6 +288,7 @@ public final class BatteryService extends SystemService {
Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait)
+ "ms and received the update.");
+ traceEnd();
}
private void updateBatteryWarningLevelLocked() {
@@ -302,16 +311,16 @@ public final class BatteryService extends SystemService {
private boolean isPoweredLocked(int plugTypeSet) {
// assume we are powered if battery state is unknown so
// the "stay on while plugged in" option will work.
- if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+ if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.chargerAcOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.chargerUsbOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.chargerWirelessOnline) {
return true;
}
return false;
@@ -328,15 +337,15 @@ public final class BatteryService extends SystemService {
* (becomes <= mLowBatteryWarningLevel).
*/
return !plugged
- && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
+ && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
&& (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
}
private void shutdownIfNoPowerLocked() {
// shut down gracefully if our battery is critically low and we are not powered.
// wait until the system has booted before attempting to display the shutdown dialog.
- if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+ if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -357,7 +366,7 @@ public final class BatteryService extends SystemService {
// shut down gracefully if temperature is too high (> 68.0C by default)
// wait until the system has booted before attempting to display the
// shutdown dialog.
- if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
+ if (mHealthInfo.batteryTemperature > mShutdownBatteryTemperature) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -375,6 +384,7 @@ public final class BatteryService extends SystemService {
}
private void update(HealthInfo info) {
+ traceBegin("HealthInfoUpdate");
synchronized (mLock) {
if (!mUpdatesStopped) {
mHealthInfo = info;
@@ -385,40 +395,38 @@ public final class BatteryService extends SystemService {
copy(mLastHealthInfo, info);
}
}
+ traceEnd();
}
private static void copy(HealthInfo dst, HealthInfo src) {
- dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
- dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
- dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
- dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
- dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
- dst.legacy.batteryStatus = src.legacy.batteryStatus;
- dst.legacy.batteryHealth = src.legacy.batteryHealth;
- dst.legacy.batteryPresent = src.legacy.batteryPresent;
- dst.legacy.batteryLevel = src.legacy.batteryLevel;
- dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
- dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
- dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
- dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
- dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
- dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
- dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
- dst.batteryCurrentAverage = src.batteryCurrentAverage;
- dst.batteryCapacity = src.batteryCapacity;
- dst.energyCounter = src.energyCounter;
+ dst.chargerAcOnline = src.chargerAcOnline;
+ dst.chargerUsbOnline = src.chargerUsbOnline;
+ dst.chargerWirelessOnline = src.chargerWirelessOnline;
+ dst.maxChargingCurrent = src.maxChargingCurrent;
+ dst.maxChargingVoltage = src.maxChargingVoltage;
+ dst.batteryStatus = src.batteryStatus;
+ dst.batteryHealth = src.batteryHealth;
+ dst.batteryPresent = src.batteryPresent;
+ dst.batteryLevel = src.batteryLevel;
+ dst.batteryVoltage = src.batteryVoltage;
+ dst.batteryTemperature = src.batteryTemperature;
+ dst.batteryCurrent = src.batteryCurrent;
+ dst.batteryCycleCount = src.batteryCycleCount;
+ dst.batteryFullCharge = src.batteryFullCharge;
+ dst.batteryChargeCounter = src.batteryChargeCounter;
+ dst.batteryTechnology = src.batteryTechnology;
}
private void processValuesLocked(boolean force) {
boolean logOutlier = false;
long dischargeDuration = 0;
- mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
- if (mHealthInfo.legacy.chargerAcOnline) {
+ mBatteryLevelCritical = (mHealthInfo.batteryLevel <= mCriticalBatteryLevel);
+ if (mHealthInfo.chargerAcOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
- } else if (mHealthInfo.legacy.chargerUsbOnline) {
+ } else if (mHealthInfo.chargerUsbOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
- } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+ } else if (mHealthInfo.chargerWirelessOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
} else {
mPlugType = BATTERY_PLUGGED_NONE;
@@ -433,10 +441,10 @@ public final class BatteryService extends SystemService {
// Let the battery stats keep track of the current level.
try {
- mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
- mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
- mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
- mHealthInfo.legacy.batteryFullCharge);
+ mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+ mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature,
+ mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter,
+ mHealthInfo.batteryFullCharge);
} catch (RemoteException e) {
// Should never happen.
}
@@ -444,16 +452,16 @@ public final class BatteryService extends SystemService {
shutdownIfNoPowerLocked();
shutdownIfOverTempLocked();
- if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
- mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
+ if (force || (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.batteryPresent != mLastBatteryPresent ||
+ mHealthInfo.batteryLevel != mLastBatteryLevel ||
mPlugType != mLastPlugType ||
- mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
- mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
- mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
- mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
- mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
+ mHealthInfo.batteryVoltage != mLastBatteryVoltage ||
+ mHealthInfo.batteryTemperature != mLastBatteryTemperature ||
+ mHealthInfo.maxChargingCurrent != mLastMaxChargingCurrent ||
+ mHealthInfo.maxChargingVoltage != mLastMaxChargingVoltage ||
+ mHealthInfo.batteryChargeCounter != mLastChargeCounter ||
mInvalidCharger != mLastInvalidCharger)) {
if (mPlugType != mLastPlugType) {
@@ -462,33 +470,33 @@ public final class BatteryService extends SystemService {
// There's no value in this data unless we've discharged at least once and the
// battery level has changed; so don't log until it does.
- if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
+ if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
logOutlier = true;
EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
- mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
+ mDischargeStartLevel, mHealthInfo.batteryLevel);
// make sure we see a discharge event before logging again
mDischargeStartTime = 0;
}
} else if (mPlugType == BATTERY_PLUGGED_NONE) {
// charging -> discharging or we just powered up
mDischargeStartTime = SystemClock.elapsedRealtime();
- mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
+ mDischargeStartLevel = mHealthInfo.batteryLevel;
}
}
- if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+ if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.batteryPresent != mLastBatteryPresent ||
mPlugType != mLastPlugType) {
EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
- mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
- mPlugType, mHealthInfo.legacy.batteryTechnology);
+ mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+ mPlugType, mHealthInfo.batteryTechnology);
}
- if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
+ if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
// Don't do this just from voltage or temperature changes, that is
// too noisy.
EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
- mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
+ mHealthInfo.batteryLevel, mHealthInfo.batteryVoltage, mHealthInfo.batteryTemperature);
}
if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
mPlugType == BATTERY_PLUGGED_NONE) {
@@ -501,16 +509,16 @@ public final class BatteryService extends SystemService {
if (!mBatteryLevelLow) {
// Should we now switch in to low battery mode?
if (mPlugType == BATTERY_PLUGGED_NONE
- && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
+ && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
mBatteryLevelLow = true;
}
} else {
// Should we now switch out of low battery mode?
if (mPlugType != BATTERY_PLUGGED_NONE) {
mBatteryLevelLow = false;
- } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mBatteryLevelLow = false;
- } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
+ } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
// If being forced, the previous state doesn't matter, we will just
// absolutely check to see if we are now above the warning level.
mBatteryLevelLow = false;
@@ -557,7 +565,7 @@ public final class BatteryService extends SystemService {
}
});
} else if (mSentLowBatteryBroadcast &&
- mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mSentLowBatteryBroadcast = false;
final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -583,16 +591,16 @@ public final class BatteryService extends SystemService {
logOutlierLocked(dischargeDuration);
}
- mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
- mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
- mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
- mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
+ mLastBatteryStatus = mHealthInfo.batteryStatus;
+ mLastBatteryHealth = mHealthInfo.batteryHealth;
+ mLastBatteryPresent = mHealthInfo.batteryPresent;
+ mLastBatteryLevel = mHealthInfo.batteryLevel;
mLastPlugType = mPlugType;
- mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
- mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
- mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
- mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
- mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
+ mLastBatteryVoltage = mHealthInfo.batteryVoltage;
+ mLastBatteryTemperature = mHealthInfo.batteryTemperature;
+ mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrent;
+ mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltage;
+ mLastChargeCounter = mHealthInfo.batteryChargeCounter;
mLastBatteryLevelCritical = mBatteryLevelCritical;
mLastInvalidCharger = mInvalidCharger;
}
@@ -604,23 +612,23 @@ public final class BatteryService extends SystemService {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
- int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
+ int icon = getIconLocked(mHealthInfo.batteryLevel);
intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
- intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
- intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
- intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
- intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.batteryStatus);
+ 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_SCALE, BATTERY_SCALE);
intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
- intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
- intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
- intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+ intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage);
+ intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature);
+ intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.batteryTechnology);
intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
- intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+ intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
if (DEBUG) {
Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+ ", info:" + mHealthInfo.toString());
@@ -684,14 +692,14 @@ public final class BatteryService extends SystemService {
long durationThreshold = Long.parseLong(durationThresholdString);
int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
if (duration <= durationThreshold &&
- mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
+ mDischargeStartLevel - mHealthInfo.batteryLevel >= dischargeThreshold) {
// If the discharge cycle is bad enough we want to know about it.
logBatteryStatsLocked();
}
if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
" discharge threshold: " + dischargeThreshold);
if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
- (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
+ (mDischargeStartLevel - mHealthInfo.batteryLevel));
} catch (NumberFormatException e) {
Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
durationThresholdString + " or " + dischargeThresholdString);
@@ -700,14 +708,14 @@ public final class BatteryService extends SystemService {
}
private int getIconLocked(int level) {
- if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+ if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
- } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
return com.android.internal.R.drawable.stat_sys_battery;
- } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
- || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+ } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+ || mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
- && mHealthInfo.legacy.batteryLevel >= 100) {
+ && mHealthInfo.batteryLevel >= 100) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
} else {
return com.android.internal.R.drawable.stat_sys_battery;
@@ -771,9 +779,9 @@ public final class BatteryService extends SystemService {
if (!mUpdatesStopped) {
copy(mLastHealthInfo, mHealthInfo);
}
- mHealthInfo.legacy.chargerAcOnline = false;
- mHealthInfo.legacy.chargerUsbOnline = false;
- mHealthInfo.legacy.chargerWirelessOnline = false;
+ mHealthInfo.chargerAcOnline = false;
+ mHealthInfo.chargerUsbOnline = false;
+ mHealthInfo.chargerWirelessOnline = false;
long ident = Binder.clearCallingIdentity();
try {
mUpdatesStopped = true;
@@ -805,25 +813,25 @@ public final class BatteryService extends SystemService {
boolean update = true;
switch (key) {
case "present":
- mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
+ mHealthInfo.batteryPresent = Integer.parseInt(value) != 0;
break;
case "ac":
- mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerAcOnline = Integer.parseInt(value) != 0;
break;
case "usb":
- mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerUsbOnline = Integer.parseInt(value) != 0;
break;
case "wireless":
- mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerWirelessOnline = Integer.parseInt(value) != 0;
break;
case "status":
- mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
+ mHealthInfo.batteryStatus = Integer.parseInt(value);
break;
case "level":
- mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
+ mHealthInfo.batteryLevel = Integer.parseInt(value);
break;
case "temp":
- mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
+ mHealthInfo.batteryTemperature = Integer.parseInt(value);
break;
case "invalid":
mInvalidCharger = Integer.parseInt(value);
@@ -882,20 +890,20 @@ public final class BatteryService extends SystemService {
if (mUpdatesStopped) {
pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
}
- pw.println(" AC powered: " + mHealthInfo.legacy.chargerAcOnline);
- pw.println(" USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
- pw.println(" Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
- pw.println(" Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
- pw.println(" Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
- pw.println(" Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
- pw.println(" status: " + mHealthInfo.legacy.batteryStatus);
- pw.println(" health: " + mHealthInfo.legacy.batteryHealth);
- pw.println(" present: " + mHealthInfo.legacy.batteryPresent);
- pw.println(" level: " + mHealthInfo.legacy.batteryLevel);
+ pw.println(" AC powered: " + mHealthInfo.chargerAcOnline);
+ pw.println(" USB powered: " + mHealthInfo.chargerUsbOnline);
+ pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
+ pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrent);
+ pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltage);
+ pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounter);
+ pw.println(" status: " + mHealthInfo.batteryStatus);
+ pw.println(" health: " + mHealthInfo.batteryHealth);
+ pw.println(" present: " + mHealthInfo.batteryPresent);
+ pw.println(" level: " + mHealthInfo.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
- pw.println(" voltage: " + mHealthInfo.legacy.batteryVoltage);
- pw.println(" temperature: " + mHealthInfo.legacy.batteryTemperature);
- pw.println(" technology: " + mHealthInfo.legacy.batteryTechnology);
+ pw.println(" voltage: " + mHealthInfo.batteryVoltage);
+ pw.println(" temperature: " + mHealthInfo.batteryTemperature);
+ pw.println(" technology: " + mHealthInfo.batteryTechnology);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -909,29 +917,37 @@ public final class BatteryService extends SystemService {
synchronized (mLock) {
proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
- if (mHealthInfo.legacy.chargerAcOnline) {
+ if (mHealthInfo.chargerAcOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
- } else if (mHealthInfo.legacy.chargerUsbOnline) {
+ } else if (mHealthInfo.chargerUsbOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
- } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+ } else if (mHealthInfo.chargerWirelessOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
}
proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
- proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
- proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
- proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
- proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
- proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+ proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
+ proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.batteryStatus);
+ proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.batteryHealth);
+ proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.batteryPresent);
+ proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.batteryLevel);
proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
- proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
- proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
- proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+ proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.batteryVoltage);
+ proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.batteryTemperature);
+ proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.batteryTechnology);
}
proto.flush();
}
+ private static void traceBegin(String name) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
+ }
+
+ private static void traceEnd() {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
private final class Led {
private final Light mBatteryLight;
@@ -960,8 +976,8 @@ public final class BatteryService extends SystemService {
* Synchronize on BatteryService.
*/
public void updateLightsLocked() {
- final int level = mHealthInfo.legacy.batteryLevel;
- final int status = mHealthInfo.legacy.batteryStatus;
+ final int level = mHealthInfo.batteryLevel;
+ final int status = mHealthInfo.batteryStatus;
if (level < mLowBatteryWarningLevel) {
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
// Solid red when battery is charging
@@ -997,6 +1013,7 @@ public final class BatteryService extends SystemService {
String instance) {
if (newService == null) return;
+ traceBegin("HealthUnregisterCallback");
try {
if (oldService != null) {
int r = oldService.unregisterCallback(this);
@@ -1008,8 +1025,11 @@ public final class BatteryService extends SystemService {
} catch (RemoteException ex) {
Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
+ ex.getMessage());
+ } finally {
+ traceEnd();
}
+ traceBegin("HealthRegisterCallback");
try {
int r = newService.registerCallback(this);
if (r != Result.SUCCESS) {
@@ -1022,6 +1042,8 @@ public final class BatteryService extends SystemService {
} catch (RemoteException ex) {
Slog.e(TAG, "health: cannot register callback (transaction error): "
+ ex.getMessage());
+ } finally {
+ traceEnd();
}
}
}
@@ -1054,53 +1076,63 @@ public final class BatteryService extends SystemService {
Slog.e(TAG, "health: must not call unregisterListener on battery properties");
}
public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
- IHealth service = mHealthServiceWrapper.getLastService();
- if (service == null) throw new RemoteException("no health service");
- final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
- switch(id) {
- case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
- service.getChargeCounter((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
- service.getCurrentNow((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
- service.getCurrentAverage((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_CAPACITY:
- service.getCapacity((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_STATUS:
- service.getChargeStatus((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
- service.getEnergyCounter((int result, long value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
+ traceBegin("HealthGetProperty");
+ try {
+ IHealth service = mHealthServiceWrapper.getLastService();
+ if (service == null) throw new RemoteException("no health service");
+ final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+ switch(id) {
+ case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+ service.getChargeCounter((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+ service.getCurrentNow((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+ service.getCurrentAverage((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+ service.getCapacity((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_STATUS:
+ service.getChargeStatus((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+ service.getEnergyCounter((int result, long value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ }
+ return outResult.value;
+ } finally {
+ traceEnd();
}
- return outResult.value;
}
public void scheduleUpdate() throws RemoteException {
- IHealth service = mHealthServiceWrapper.getLastService();
- if (service == null) throw new RemoteException("no health service");
- service.update();
+ traceBegin("HealthScheduleUpdate");
+ try {
+ IHealth service = mHealthServiceWrapper.getLastService();
+ if (service == null) throw new RemoteException("no health service");
+ service.update();
+ } finally {
+ traceEnd();
+ }
}
}
@@ -1122,7 +1154,7 @@ public final class BatteryService extends SystemService {
@Override
public int getBatteryLevel() {
synchronized (mLock) {
- return mHealthInfo.legacy.batteryLevel;
+ return mHealthInfo.batteryLevel;
}
}
@@ -1163,12 +1195,13 @@ public final class BatteryService extends SystemService {
Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
private final IServiceNotification mNotification = new Notification();
+ private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh");
+ // These variables are fixed after init.
private Callback mCallback;
private IHealthSupplier mHealthSupplier;
+ private String mInstanceName;
- private final Object mLastServiceSetLock = new Object();
// Last IHealth service received.
- // set must be also be guarded with mLastServiceSetLock to ensure ordering.
private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
/**
@@ -1186,6 +1219,10 @@ public final class BatteryService extends SystemService {
* Start monitoring registration of new IHealth services. Only instances that are in
* {@code sAllInstances} and in device / framework manifest are used. This function should
* only be called once.
+ *
+ * mCallback.onRegistration() is called synchronously (aka in init thread) before
+ * this method returns.
+ *
* @throws RemoteException transaction error when talking to IServiceManager
* @throws NoSuchElementException if one of the following cases:
* - No service manager;
@@ -1201,24 +1238,51 @@ public final class BatteryService extends SystemService {
if (callback == null || managerSupplier == null || healthSupplier == null)
throw new NullPointerException();
+ IServiceManager manager;
+
mCallback = callback;
mHealthSupplier = healthSupplier;
- IServiceManager manager = managerSupplier.get();
+ // Initialize mLastService and call callback for the first time (in init thread)
+ IHealth newService = null;
for (String name : sAllInstances) {
- if (manager.getTransport(IHealth.kInterfaceName, name) ==
- IServiceManager.Transport.EMPTY) {
- continue;
+ traceBegin("HealthInitGetService_" + name);
+ try {
+ newService = healthSupplier.get(name);
+ } catch (NoSuchElementException ex) {
+ /* ignored, handled below */
+ } finally {
+ traceEnd();
}
+ if (newService != null) {
+ mInstanceName = name;
+ mLastService.set(newService);
+ break;
+ }
+ }
- manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification);
- Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name);
- return;
+ if (mInstanceName == null || newService == null) {
+ throw new NoSuchElementException(String.format(
+ "No IHealth service instance among %s is available. Perhaps no permission?",
+ sAllInstances.toString()));
}
+ mCallback.onRegistration(null, newService, mInstanceName);
- throw new NoSuchElementException(String.format(
- "No IHealth service instance among %s is available. Perhaps no permission?",
- sAllInstances.toString()));
+ // Register for future service registrations
+ traceBegin("HealthInitRegisterNotification");
+ mHandlerThread.start();
+ try {
+ managerSupplier.get().registerForNotifications(
+ IHealth.kInterfaceName, mInstanceName, mNotification);
+ } finally {
+ traceEnd();
+ }
+ Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
+ }
+
+ @VisibleForTesting
+ HandlerThread getHandlerThread() {
+ return mHandlerThread;
}
interface Callback {
@@ -1249,7 +1313,7 @@ public final class BatteryService extends SystemService {
*/
interface IHealthSupplier {
default IHealth get(String name) throws NoSuchElementException, RemoteException {
- return IHealth.getService(name);
+ return IHealth.getService(name, true /* retry */);
}
}
@@ -1258,19 +1322,28 @@ public final class BatteryService extends SystemService {
public final void onRegistration(String interfaceName, String instanceName,
boolean preexisting) {
if (!IHealth.kInterfaceName.equals(interfaceName)) return;
- if (!sAllInstances.contains(instanceName)) return;
- try {
- // ensures the order of multiple onRegistration on different threads.
- synchronized (mLastServiceSetLock) {
- IHealth newService = mHealthSupplier.get(instanceName);
- IHealth oldService = mLastService.getAndSet(newService);
- Slog.i(TAG, "health: new instance registered " + instanceName);
- mCallback.onRegistration(oldService, newService, instanceName);
+ if (!mInstanceName.equals(instanceName)) return;
+
+ // This runnable only runs on mHandlerThread and ordering is ensured, hence
+ // no locking is needed inside the runnable.
+ mHandlerThread.getThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IHealth newService = mHealthSupplier.get(mInstanceName);
+ IHealth oldService = mLastService.getAndSet(newService);
+
+ // preexisting may be inaccurate (race). Check for equality here.
+ if (Objects.equals(newService, oldService)) return;
+
+ Slog.i(TAG, "health: new instance registered " + mInstanceName);
+ mCallback.onRegistration(oldService, newService, mInstanceName);
+ } catch (NoSuchElementException | RemoteException ex) {
+ Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
+ + "': " + ex.getMessage() + ". Perhaps no permission?");
+ }
}
- } catch (NoSuchElementException | RemoteException ex) {
- Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
- ex.getMessage() + ". Perhaps no permission?");
- }
+ });
}
}
}
diff --git a/com/android/server/BluetoothManagerService.java b/com/android/server/BluetoothManagerService.java
index 75206e48..c34c30cf 100644
--- a/com/android/server/BluetoothManagerService.java
+++ b/com/android/server/BluetoothManagerService.java
@@ -195,6 +195,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private LinkedList<ActiveLog> mActiveLogs;
private LinkedList<Long> mCrashTimestamps;
private int mCrashes;
+ private long mLastEnabledTime;
// configuration from external IBinder call which is used to
// synchronize with broadcast receiver.
@@ -2021,6 +2022,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
quietMode ? 1 : 0, 0));
addActiveLog(packageName, true);
+ mLastEnabledTime = SystemClock.elapsedRealtime();
}
private void addActiveLog(String packageName, boolean enable) {
@@ -2142,7 +2144,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
writer.println(" address: " + mAddress);
writer.println(" name: " + mName);
if (mEnable) {
- long onDuration = System.currentTimeMillis() - mActiveLogs.getLast().getTime();
+ long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime;
String onDurationString = String.format("%02d:%02d:%02d.%03d",
(int)(onDuration / (1000 * 60 * 60)),
(int)((onDuration / (1000 * 60)) % 60),
diff --git a/com/android/server/ConnectivityService.java b/com/android/server/ConnectivityService.java
index 7e65d360..bccae062 100644
--- a/com/android/server/ConnectivityService.java
+++ b/com/android/server/ConnectivityService.java
@@ -29,7 +29,10 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.Nullable;
@@ -70,6 +73,7 @@ import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.Uri;
+import android.net.VpnService;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.util.MultinetworkPolicyTracker;
@@ -2119,9 +2123,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
final boolean valid =
(msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated;
+ final boolean wasDefault = isDefaultNetwork(nai);
if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") +
(msg.obj == null ? "" : " with redirect to " + (String)msg.obj));
if (valid != nai.lastValidated) {
+ if (wasDefault) {
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
+ SystemClock.elapsedRealtime(), valid);
+ }
final int oldScore = nai.getCurrentScore();
nai.lastValidated = valid;
nai.everValidated |= valid;
@@ -2293,7 +2302,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Let rematchAllNetworksAndRequests() below record a new default network event
// if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
// whose timestamps tell how long it takes to recover a default network.
- metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
+ long now = SystemClock.elapsedRealtime();
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
}
notifyIfacesChangedForNetworkStats();
// TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -4675,10 +4685,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
- final NetworkCapabilities prevNc = nai.networkCapabilities;
+ final NetworkCapabilities prevNc;
synchronized (nai) {
+ prevNc = nai.networkCapabilities;
nai.networkCapabilities = networkCapabilities;
}
+
if (nai.getCurrentScore() == oldScore &&
networkCapabilities.equalRequestableCapabilities(prevNc)) {
// If the requestable capabilities haven't changed, and the score hasn't changed, then
@@ -4692,6 +4704,28 @@ public class ConnectivityService extends IConnectivityManager.Stub
rematchAllNetworksAndRequests(nai, oldScore);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
+
+ // Report changes that are interesting for network statistics tracking.
+ if (prevNc != null) {
+ final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) !=
+ networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED);
+ final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) !=
+ networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+ if (meteredChanged || roamingChanged) {
+ notifyIfacesChangedForNetworkStats();
+ }
+ }
+
+ if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) {
+ // Tell VPNs about updated capabilities, since they may need to
+ // bubble those changes through.
+ synchronized (mVpns) {
+ for (int i = 0; i < mVpns.size(); i++) {
+ final Vpn vpn = mVpns.valueAt(i);
+ vpn.updateCapabilities();
+ }
+ }
+ }
}
public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
@@ -5024,7 +5058,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
makeDefault(newNetwork);
// Log 0 -> X and Y -> X default network transitions, where X is the new default.
metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
- newNetwork, oldDefaultNetwork);
+ now, newNetwork, oldDefaultNetwork);
// Have a new default network, release the transition wakelock in
scheduleReleaseNetworkTransitionWakelock();
}
@@ -5225,14 +5259,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
notifyLockdownVpn(networkAgent);
- if (oldInfo != null && oldInfo.getState() == state) {
- if (oldInfo.isRoaming() != newInfo.isRoaming()) {
- if (VDBG) log("roaming status changed, notifying NetworkStatsService");
- notifyIfacesChangedForNetworkStats();
- } else if (VDBG) log("ignoring duplicate network state non-change");
- // In either case, no further work should be needed.
- return;
- }
if (DBG) {
log(networkAgent.name() + " EVENT_NETWORK_INFO_CHANGED, going from " +
(oldInfo == null ? "null" : oldInfo.getState()) +
@@ -5590,7 +5616,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
- mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
+ int[] transports = nai.networkCapabilities.getTransportTypes();
+ mMetricsLog.log(nai.network.netId, transports, new NetworkEvent(evtype));
}
private static boolean toBool(int encodedBoolean) {
diff --git a/com/android/server/MountServiceIdler.java b/com/android/server/MountServiceIdler.java
index d8bd0bb5..1891ba9b 100644
--- a/com/android/server/MountServiceIdler.java
+++ b/com/android/server/MountServiceIdler.java
@@ -72,7 +72,7 @@ public class MountServiceIdler extends JobService {
synchronized (mFinishCallback) {
mStarted = true;
}
- ms.runIdleMaintenance(mFinishCallback);
+ ms.runIdleMaint(mFinishCallback);
}
return ms != null;
}
@@ -82,8 +82,12 @@ public class MountServiceIdler extends JobService {
// Once we kick off the fstrim we aren't actually interruptible; just note
// that we don't need to call jobFinished(), and let everything happen in
// the callback from the mount service.
- synchronized (mFinishCallback) {
- mStarted = false;
+ StorageManagerService ms = StorageManagerService.sSelf;
+ if (ms != null) {
+ ms.abortIdleMaint(mFinishCallback);
+ synchronized (mFinishCallback) {
+ mStarted = false;
+ }
}
return false;
}
diff --git a/com/android/server/NetworkManagementService.java b/com/android/server/NetworkManagementService.java
index c60d7b07..8a15ded2 100644
--- a/com/android/server/NetworkManagementService.java
+++ b/com/android/server/NetworkManagementService.java
@@ -20,6 +20,9 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
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_NONE;
@@ -92,6 +95,7 @@ import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -1946,9 +1950,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public void setDnsConfigurationForNetwork(int netId, String[] servers, String domains) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- ContentResolver resolver = mContext.getContentResolver();
+ final ContentResolver cr = mContext.getContentResolver();
- int sampleValidity = Settings.Global.getInt(resolver,
+ int sampleValidity = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
if (sampleValidity < 0 || sampleValidity > 65535) {
@@ -1957,7 +1961,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
sampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
}
- int successThreshold = Settings.Global.getInt(resolver,
+ int successThreshold = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
if (successThreshold < 0 || successThreshold > 100) {
@@ -1966,9 +1970,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
successThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
}
- int minSamples = Settings.Global.getInt(resolver,
+ int minSamples = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
- int maxSamples = Settings.Global.getInt(resolver,
+ int maxSamples = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
if (minSamples < 0 || minSamples > maxSamples || maxSamples > 64) {
Slog.w(TAG, "Invalid sample count (min, max)=(" + minSamples + ", " + maxSamples +
@@ -1980,8 +1984,24 @@ public class NetworkManagementService extends INetworkManagementService.Stub
final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
- final boolean useTls = Settings.Global.getInt(resolver,
- Settings.Global.DNS_TLS_DISABLED, 0) == 0;
+ final boolean useTls = shouldUseTls(cr);
+ // TODO: Populate tlsHostname once it's decided how the hostname's IP
+ // addresses will be resolved:
+ //
+ // [1] network-provided DNS servers are included here with the
+ // hostname and netd will use the network-provided servers to
+ // resolve the hostname and fix up its internal structures, or
+ //
+ // [2] network-provided DNS servers are included here without the
+ // hostname, the ConnectivityService layer resolves the given
+ // hostname, and then reconfigures netd with this information.
+ //
+ // In practice, there will always be a need for ConnectivityService or
+ // the captive portal app to use the network-provided services to make
+ // some queries. This argues in favor of [1], in concert with another
+ // mechanism, perhaps setting a high bit in the netid, to indicate
+ // via existing DNS APIs which set of servers (network-provided or
+ // non-network-provided private DNS) should be queried.
final String tlsHostname = "";
final String[] tlsFingerprints = new String[0];
try {
@@ -1992,6 +2012,15 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
}
+ private static boolean shouldUseTls(ContentResolver cr) {
+ String privateDns = Settings.Global.getString(cr, Settings.Global.PRIVATE_DNS_MODE);
+ if (TextUtils.isEmpty(privateDns)) {
+ privateDns = PRIVATE_DNS_DEFAULT_MODE;
+ }
+ return privateDns.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
+ privateDns.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+ }
+
@Override
public void addVpnUidRanges(int netId, UidRange[] ranges) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/com/android/server/RescueParty.java b/com/android/server/RescueParty.java
index 1924a861..a9f190c8 100644
--- a/com/android/server/RescueParty.java
+++ b/com/android/server/RescueParty.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
@@ -146,7 +148,7 @@ public class RescueParty {
SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level));
EventLogTags.writeRescueLevel(level, triggerUid);
- PackageManagerService.logCriticalInfo(Log.WARN, "Incremented rescue level to "
+ logCriticalInfo(Log.WARN, "Incremented rescue level to "
+ levelToString(level) + " triggered by UID " + triggerUid);
}
@@ -166,12 +168,12 @@ public class RescueParty {
try {
executeRescueLevelInternal(context, level);
EventLogTags.writeRescueSuccess(level);
- PackageManagerService.logCriticalInfo(Log.DEBUG,
+ logCriticalInfo(Log.DEBUG,
"Finished rescue level " + levelToString(level));
} catch (Throwable t) {
final String msg = ExceptionUtils.getCompleteMessage(t);
EventLogTags.writeRescueFailure(level, msg);
- PackageManagerService.logCriticalInfo(Log.ERROR,
+ logCriticalInfo(Log.ERROR,
"Failed rescue level " + levelToString(level) + ": " + msg);
}
}
diff --git a/com/android/server/ServiceThread.java b/com/android/server/ServiceThread.java
index bce64afd..26703c51 100644
--- a/com/android/server/ServiceThread.java
+++ b/com/android/server/ServiceThread.java
@@ -19,7 +19,6 @@ package com.android.server;
import android.os.HandlerThread;
import android.os.Process;
import android.os.StrictMode;
-import android.util.Slog;
/**
* Special handler thread that we create for system services that require their own loopers.
@@ -38,11 +37,10 @@ public class ServiceThread extends HandlerThread {
public void run() {
Process.setCanSelfBackground(false);
- // For debug builds, log event loop stalls to dropbox for analysis.
- if (!mAllowIo && StrictMode.conditionallyEnableDebugLogging()) {
- Slog.i(TAG, "Enabled StrictMode logging for " + getName() + " looper.");
+ if (!mAllowIo) {
+ StrictMode.initThreadDefaults(null);
}
super.run();
}
-} \ No newline at end of file
+}
diff --git a/com/android/server/ServiceWatcher.java b/com/android/server/ServiceWatcher.java
index 2ff036be..f20ca435 100644
--- a/com/android/server/ServiceWatcher.java
+++ b/com/android/server/ServiceWatcher.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -397,9 +398,29 @@ public class ServiceWatcher implements ServiceConnection {
}
}
- public @Nullable IBinder getBinder() {
+ /**
+ * The runner that runs on the binder retrieved from {@link ServiceWatcher}.
+ */
+ public interface BinderRunner {
+ /**
+ * Runs on the retrieved binder.
+ * @param binder the binder retrieved from the {@link ServiceWatcher}.
+ */
+ public void run(@NonNull IBinder binder);
+ }
+
+ /**
+ * Retrieves the binder from {@link ServiceWatcher} and runs it.
+ * @return whether a valid service exists.
+ */
+ public boolean runOnBinder(@NonNull BinderRunner runner) {
synchronized (mLock) {
- return mBoundService;
+ if (mBoundService == null) {
+ return false;
+ } else {
+ runner.run(mBoundService);
+ return true;
+ }
}
}
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index bfbce408..3c955eb0 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@ import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IProgressListener;
import android.os.IStoraged;
import android.os.IVold;
import android.os.IVoldListener;
@@ -541,6 +542,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
private static final int H_VOLUME_UNMOUNT = 8;
private static final int H_PARTITION_FORGET = 9;
private static final int H_RESET = 10;
+ private static final int H_RUN_IDLE_MAINT = 11;
+ private static final int H_ABORT_IDLE_MAINT = 12;
class StorageManagerServiceHandler extends Handler {
public StorageManagerServiceHandler(Looper looper) {
@@ -570,7 +573,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
// TODO: Reintroduce shouldBenchmark() test
- fstrim(0);
+ fstrim(0, null);
// invoke the completion callback, if any
// TODO: fstrim is non-blocking, so remove this useless callback
@@ -649,6 +652,17 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
resetIfReadyAndConnected();
break;
}
+ case H_RUN_IDLE_MAINT: {
+ Slog.i(TAG, "Running idle maintenance");
+ runIdleMaint((Runnable)msg.obj);
+ break;
+ }
+ case H_ABORT_IDLE_MAINT: {
+ Slog.i(TAG, "Aborting idle maintenance");
+ abortIdleMaint((Runnable)msg.obj);
+ break;
+ }
+
}
}
}
@@ -1576,21 +1590,19 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
@Override
- public long benchmark(String volId) {
+ public void benchmark(String volId, IVoldTaskListener listener) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- // TODO: refactor for callers to provide a listener
try {
- final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
mVold.benchmark(volId, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
- // Not currently used
+ dispatchOnStatus(listener, status, extras);
}
@Override
public void onFinished(int status, PersistableBundle extras) {
- result.complete(extras);
+ dispatchOnFinished(listener, status, extras);
final String path = extras.getString("path");
final String ident = extras.getString("ident");
@@ -1611,10 +1623,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
}
});
- return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE);
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return Long.MAX_VALUE;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -1742,13 +1752,15 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
@Override
- public void fstrim(int flags) {
+ public void fstrim(int flags, IVoldTaskListener listener) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
mVold.fstrim(flags, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
+ dispatchOnStatus(listener, status, extras);
+
// Ignore trim failures
if (status != 0) return;
@@ -1770,15 +1782,68 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
@Override
public void onFinished(int status, PersistableBundle extras) {
- // Not currently used
+ dispatchOnFinished(listener, status, extras);
+
// TODO: benchmark when desired
}
});
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void runIdleMaint(Runnable callback) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ try {
+ mVold.runIdleMaint(new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Not currently used
+ }
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ if (callback != null) {
+ BackgroundThread.getHandler().post(callback);
+ }
+ }
+ });
} catch (Exception e) {
Slog.wtf(TAG, e);
}
}
+ @Override
+ public void runIdleMaintenance() {
+ runIdleMaint(null);
+ }
+
+ void abortIdleMaint(Runnable callback) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+ try {
+ mVold.abortIdleMaint(new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Not currently used
+ }
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ if (callback != null) {
+ BackgroundThread.getHandler().post(callback);
+ }
+ }
+ });
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
+ @Override
+ public void abortIdleMaintenance() {
+ abortIdleMaint(null);
+ }
+
private void remountUidExternalStorage(int uid, int mode) {
try {
mVold.remountUid(uid, mode);
@@ -3239,6 +3304,26 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
}
+ private void dispatchOnStatus(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onStatus(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ private void dispatchOnFinished(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onFinished(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
private static class Callbacks extends Handler {
private static final int MSG_STORAGE_STATE_CHANGED = 1;
private static final int MSG_VOLUME_STATE_CHANGED = 2;
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index de5d879a..74a7bd4a 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -37,7 +37,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -52,7 +51,7 @@ import android.util.Slog;
import android.view.WindowManager;
import com.android.internal.R;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
@@ -69,7 +68,7 @@ import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.display.DisplayManagerService;
-import com.android.server.display.NightDisplayService;
+import com.android.server.display.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
import com.android.server.fingerprint.FingerprintService;
@@ -83,6 +82,7 @@ import com.android.server.media.MediaSessionService;
import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
+import com.android.server.net.watchlist.NetworkWatchlistService;
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
@@ -95,6 +95,7 @@ import com.android.server.pm.OtaDexoptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
+import com.android.server.pm.crossprofile.CrossProfileAppsService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
@@ -193,6 +194,8 @@ public final class SystemServer {
"com.google.android.clockwork.ThermalObserver";
private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
"com.google.android.clockwork.connectivity.WearConnectivityService";
+ private static final String WEAR_SIDEKICK_SERVICE_CLASS =
+ "com.google.android.clockwork.sidekick.SidekickService";
private static final String WEAR_DISPLAY_SERVICE_CLASS =
"com.google.android.clockwork.display.WearDisplayService";
private static final String WEAR_LEFTY_SERVICE_CLASS =
@@ -404,10 +407,8 @@ public final class SystemServer {
traceEnd();
}
- // For debug builds, log event loop stalls to dropbox for analysis.
- if (StrictMode.conditionallyEnableDebugLogging()) {
- Slog.i(TAG, "Enabled StrictMode for system server main thread.");
- }
+ StrictMode.initVmDefaults(null);
+
if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
int uptimeMillis = (int) SystemClock.elapsedRealtime();
MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
@@ -545,11 +546,9 @@ public final class SystemServer {
traceEnd();
// Bring up recovery system in case a rescue party needs a reboot
- if (!SystemProperties.getBoolean("config.disable_noncore", false)) {
- traceBeginAndSlog("StartRecoverySystemService");
- mSystemServiceManager.startService(RecoverySystemService.class);
- traceEnd();
- }
+ traceBeginAndSlog("StartRecoverySystemService");
+ mSystemServiceManager.startService(RecoverySystemService.class);
+ traceEnd();
// Now that we have the bare essentials of the OS up and running, take
// note that we just booted, which might send out a rescue party if
@@ -561,6 +560,13 @@ public final class SystemServer {
mSystemServiceManager.startService(LightsService.class);
traceEnd();
+ traceBeginAndSlog("StartSidekickService");
+ // Package manager isn't started yet; need to use SysProp not hardware feature
+ if (SystemProperties.getBoolean("config.enable_sidekick_graphics", false)) {
+ mSystemServiceManager.startService(WEAR_SIDEKICK_SERVICE_CLASS);
+ }
+ traceEnd();
+
// Display manager is needed to provide display metrics before package manager
// starts up.
traceBeginAndSlog("StartDisplayManager");
@@ -705,13 +711,7 @@ public final class SystemServer {
MmsServiceBroker mmsService = null;
HardwarePropertiesManagerService hardwarePropertiesService = null;
- boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
- boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
- boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
- boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
- boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false);
- boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime", false);
boolean disableRtt = SystemProperties.getBoolean("config.disable_rtt", false);
boolean disableMediaProjection = SystemProperties.getBoolean("config.disable_mediaproj",
false);
@@ -875,8 +875,6 @@ public final class SystemServer {
} else if (!context.getPackageManager().hasSystemFeature
(PackageManager.FEATURE_BLUETOOTH)) {
Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
- } else if (disableBluetooth) {
- Slog.i(TAG, "Bluetooth Service disabled by config");
} else {
traceBeginAndSlog("StartBluetoothService");
mSystemServiceManager.startService(BluetoothService.class);
@@ -887,6 +885,10 @@ public final class SystemServer {
mSystemServiceManager.startService(IpConnectivityMetrics.class);
traceEnd();
+ traceBeginAndSlog("NetworkWatchlistService");
+ mSystemServiceManager.startService(NetworkWatchlistService.Lifecycle.class);
+ traceEnd();
+
traceBeginAndSlog("PinnerService");
mSystemServiceManager.startService(PinnerService.class);
traceEnd();
@@ -927,8 +929,7 @@ public final class SystemServer {
traceEnd();
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
- if (!disableStorage &&
- !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+ if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
traceBeginAndSlog("StartStorageManagerService");
try {
/*
@@ -978,42 +979,40 @@ public final class SystemServer {
traceEnd();
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartLockSettingsService");
- try {
- mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
- lockSettings = ILockSettings.Stub.asInterface(
- ServiceManager.getService("lock_settings"));
- } catch (Throwable e) {
- reportWtf("starting LockSettingsService service", e);
- }
- traceEnd();
-
- final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
- if (hasPdb) {
- traceBeginAndSlog("StartPersistentDataBlock");
- mSystemServiceManager.startService(PersistentDataBlockService.class);
- traceEnd();
- }
-
- if (hasPdb || OemLockService.isHalPresent()) {
- // Implementation depends on pdb or the OemLock HAL
- traceBeginAndSlog("StartOemLockService");
- mSystemServiceManager.startService(OemLockService.class);
- traceEnd();
- }
+ traceBeginAndSlog("StartLockSettingsService");
+ try {
+ mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+ lockSettings = ILockSettings.Stub.asInterface(
+ ServiceManager.getService("lock_settings"));
+ } catch (Throwable e) {
+ reportWtf("starting LockSettingsService service", e);
+ }
+ traceEnd();
- traceBeginAndSlog("StartDeviceIdleController");
- mSystemServiceManager.startService(DeviceIdleController.class);
+ final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
+ if (hasPdb) {
+ traceBeginAndSlog("StartPersistentDataBlock");
+ mSystemServiceManager.startService(PersistentDataBlockService.class);
traceEnd();
+ }
- // Always start the Device Policy Manager, so that the API is compatible with
- // API8.
- traceBeginAndSlog("StartDevicePolicyManager");
- mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+ if (hasPdb || OemLockService.isHalPresent()) {
+ // Implementation depends on pdb or the OemLock HAL
+ traceBeginAndSlog("StartOemLockService");
+ mSystemServiceManager.startService(OemLockService.class);
traceEnd();
}
+ traceBeginAndSlog("StartDeviceIdleController");
+ mSystemServiceManager.startService(DeviceIdleController.class);
+ traceEnd();
+
+ // Always start the Device Policy Manager, so that the API is compatible with
+ // API8.
+ traceBeginAndSlog("StartDevicePolicyManager");
+ mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+ traceEnd();
+
if (!disableSystemUI) {
traceBeginAndSlog("StartStatusBarManagerService");
try {
@@ -1025,154 +1024,146 @@ public final class SystemServer {
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartClipboardService");
- mSystemServiceManager.startService(ClipboardService.class);
- traceEnd();
- }
+ traceBeginAndSlog("StartClipboardService");
+ mSystemServiceManager.startService(ClipboardService.class);
+ traceEnd();
- if (!disableNetwork) {
- traceBeginAndSlog("StartNetworkManagementService");
- try {
- networkManagement = NetworkManagementService.create(context);
- ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
- } catch (Throwable e) {
- reportWtf("starting NetworkManagement Service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartNetworkManagementService");
+ try {
+ networkManagement = NetworkManagementService.create(context);
+ ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkManagement Service", e);
+ }
+ traceEnd();
- traceBeginAndSlog("StartIpSecService");
- try {
- ipSecService = IpSecService.create(context);
- ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
- } catch (Throwable e) {
- reportWtf("starting IpSec Service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartIpSecService");
+ try {
+ ipSecService = IpSecService.create(context);
+ ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+ } catch (Throwable e) {
+ reportWtf("starting IpSec Service", e);
}
+ traceEnd();
- if (!disableNonCoreServices && !disableTextServices) {
+ if (!disableTextServices) {
traceBeginAndSlog("StartTextServicesManager");
mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
traceEnd();
}
- if (!disableNetwork) {
- traceBeginAndSlog("StartNetworkScoreService");
- try {
- networkScore = new NetworkScoreService(context);
- ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
- } catch (Throwable e) {
- reportWtf("starting Network Score Service", e);
- }
- traceEnd();
-
- traceBeginAndSlog("StartNetworkStatsService");
- try {
- networkStats = NetworkStatsService.create(context, networkManagement);
- ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
- } catch (Throwable e) {
- reportWtf("starting NetworkStats Service", e);
- }
- traceEnd();
-
- traceBeginAndSlog("StartNetworkPolicyManagerService");
- try {
- networkPolicy = new NetworkPolicyManagerService(context,
- mActivityManagerService, networkStats, networkManagement);
- ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
- } catch (Throwable e) {
- reportWtf("starting NetworkPolicy Service", e);
- }
- traceEnd();
-
- // Wifi Service must be started first for wifi-related services.
- traceBeginAndSlog("StartWifi");
- mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
- traceEnd();
- traceBeginAndSlog("StartWifiScanning");
- mSystemServiceManager.startService(
- "com.android.server.wifi.scanner.WifiScanningService");
- traceEnd();
+ traceBeginAndSlog("StartNetworkScoreService");
+ try {
+ networkScore = new NetworkScoreService(context);
+ ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
+ } catch (Throwable e) {
+ reportWtf("starting Network Score Service", e);
+ }
+ traceEnd();
- if (!disableRtt) {
- traceBeginAndSlog("StartWifiRtt");
- mSystemServiceManager.startService("com.android.server.wifi.RttService");
- traceEnd();
+ traceBeginAndSlog("StartNetworkStatsService");
+ try {
+ networkStats = NetworkStatsService.create(context, networkManagement);
+ ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkStats Service", e);
+ }
+ traceEnd();
- if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_RTT)) {
- traceBeginAndSlog("StartRttService");
- mSystemServiceManager.startService(
- "com.android.server.wifi.rtt.RttService");
- traceEnd();
- }
- }
+ traceBeginAndSlog("StartNetworkPolicyManagerService");
+ try {
+ networkPolicy = new NetworkPolicyManagerService(context,
+ mActivityManagerService, networkStats, networkManagement);
+ ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkPolicy Service", e);
+ }
+ traceEnd();
- if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_AWARE)) {
- traceBeginAndSlog("StartWifiAware");
- mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
- traceEnd();
- }
+ // Wifi Service must be started first for wifi-related services.
+ traceBeginAndSlog("StartWifi");
+ mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+ traceEnd();
+ traceBeginAndSlog("StartWifiScanning");
+ mSystemServiceManager.startService(
+ "com.android.server.wifi.scanner.WifiScanningService");
+ traceEnd();
- if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_DIRECT)) {
- traceBeginAndSlog("StartWifiP2P");
- mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
- traceEnd();
- }
+ if (!disableRtt) {
+ traceBeginAndSlog("StartWifiRtt");
+ mSystemServiceManager.startService("com.android.server.wifi.RttService");
+ traceEnd();
if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LOWPAN)) {
- traceBeginAndSlog("StartLowpan");
- mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
+ PackageManager.FEATURE_WIFI_RTT)) {
+ traceBeginAndSlog("StartRttService");
+ mSystemServiceManager.startService(
+ "com.android.server.wifi.rtt.RttService");
traceEnd();
}
+ }
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
- traceBeginAndSlog("StartEthernet");
- mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
- traceEnd();
- }
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_AWARE)) {
+ traceBeginAndSlog("StartWifiAware");
+ mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
+ traceEnd();
+ }
- traceBeginAndSlog("StartConnectivityService");
- try {
- connectivity = new ConnectivityService(
- context, networkManagement, networkStats, networkPolicy);
- ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
- /* allowIsolated= */ false,
- DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
- networkStats.bindConnectivityManager(connectivity);
- networkPolicy.bindConnectivityManager(connectivity);
- } catch (Throwable e) {
- reportWtf("starting Connectivity Service", e);
- }
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT)) {
+ traceBeginAndSlog("StartWifiP2P");
+ mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
traceEnd();
+ }
- traceBeginAndSlog("StartNsdService");
- try {
- serviceDiscovery = NsdService.create(context);
- ServiceManager.addService(
- Context.NSD_SERVICE, serviceDiscovery);
- } catch (Throwable e) {
- reportWtf("starting Service Discovery Service", e);
- }
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LOWPAN)) {
+ traceBeginAndSlog("StartLowpan");
+ mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartUpdateLockService");
- try {
- ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
- new UpdateLockService(context));
- } catch (Throwable e) {
- reportWtf("starting UpdateLockService", e);
- }
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+ traceBeginAndSlog("StartEthernet");
+ mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
traceEnd();
}
+ traceBeginAndSlog("StartConnectivityService");
+ try {
+ connectivity = new ConnectivityService(
+ context, networkManagement, networkStats, networkPolicy);
+ ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
+ /* allowIsolated= */ false,
+ DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+ networkStats.bindConnectivityManager(connectivity);
+ networkPolicy.bindConnectivityManager(connectivity);
+ } catch (Throwable e) {
+ reportWtf("starting Connectivity Service", e);
+ }
+ traceEnd();
+
+ traceBeginAndSlog("StartNsdService");
+ try {
+ serviceDiscovery = NsdService.create(context);
+ ServiceManager.addService(
+ Context.NSD_SERVICE, serviceDiscovery);
+ } catch (Throwable e) {
+ reportWtf("starting Service Discovery Service", e);
+ }
+ traceEnd();
+
+ traceBeginAndSlog("StartUpdateLockService");
+ try {
+ ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
+ new UpdateLockService(context));
+ } catch (Throwable e) {
+ reportWtf("starting UpdateLockService", e);
+ }
+ traceEnd();
+
traceBeginAndSlog("StartNotificationManager");
mSystemServiceManager.startService(NotificationManagerService.class);
SystemNotificationChannels.createAll(context);
@@ -1185,27 +1176,25 @@ public final class SystemServer {
mSystemServiceManager.startService(DeviceStorageMonitorService.class);
traceEnd();
- if (!disableLocation) {
- traceBeginAndSlog("StartLocationManagerService");
- try {
- location = new LocationManagerService(context);
- ServiceManager.addService(Context.LOCATION_SERVICE, location);
- } catch (Throwable e) {
- reportWtf("starting Location Manager", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartLocationManagerService");
+ try {
+ location = new LocationManagerService(context);
+ ServiceManager.addService(Context.LOCATION_SERVICE, location);
+ } catch (Throwable e) {
+ reportWtf("starting Location Manager", e);
+ }
+ traceEnd();
- traceBeginAndSlog("StartCountryDetectorService");
- try {
- countryDetector = new CountryDetectorService(context);
- ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
- } catch (Throwable e) {
- reportWtf("starting Country Detector", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartCountryDetectorService");
+ try {
+ countryDetector = new CountryDetectorService(context);
+ ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+ } catch (Throwable e) {
+ reportWtf("starting Country Detector", e);
}
+ traceEnd();
- if (!disableNonCoreServices && !disableSearchManager) {
+ if (!disableSearchManager) {
traceBeginAndSlog("StartSearchManagerService");
try {
mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
@@ -1215,8 +1204,7 @@ public final class SystemServer {
traceEnd();
}
- if (!disableNonCoreServices && context.getResources().getBoolean(
- R.bool.config_enableWallpaperService)) {
+ if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
traceBeginAndSlog("StartWallpaperManagerService");
mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
traceEnd();
@@ -1232,16 +1220,14 @@ public final class SystemServer {
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartDockObserver");
- mSystemServiceManager.startService(DockObserver.class);
- traceEnd();
+ traceBeginAndSlog("StartDockObserver");
+ mSystemServiceManager.startService(DockObserver.class);
+ traceEnd();
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- traceBeginAndSlog("StartThermalObserver");
- mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
- traceEnd();
- }
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ traceBeginAndSlog("StartThermalObserver");
+ mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
+ traceEnd();
}
traceBeginAndSlog("StartWiredAccessoryManager");
@@ -1254,53 +1240,51 @@ public final class SystemServer {
}
traceEnd();
- if (!disableNonCoreServices) {
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
- // Start MIDI Manager service
- traceBeginAndSlog("StartMidiManager");
- mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
- traceEnd();
- }
-
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
- || mPackageManager.hasSystemFeature(
- PackageManager.FEATURE_USB_ACCESSORY)) {
- // Manage USB host and device support
- traceBeginAndSlog("StartUsbService");
- mSystemServiceManager.startService(USB_SERVICE_CLASS);
- traceEnd();
- }
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ // Start MIDI Manager service
+ traceBeginAndSlog("StartMidiManager");
+ mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+ traceEnd();
+ }
- if (!disableSerial) {
- traceBeginAndSlog("StartSerialService");
- try {
- // Serial port support
- serial = new SerialService(context);
- ServiceManager.addService(Context.SERIAL_SERVICE, serial);
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting SerialService", e);
- }
- traceEnd();
- }
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
+ || mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_USB_ACCESSORY)) {
+ // Manage USB host and device support
+ traceBeginAndSlog("StartUsbService");
+ mSystemServiceManager.startService(USB_SERVICE_CLASS);
+ traceEnd();
+ }
- traceBeginAndSlog("StartHardwarePropertiesManagerService");
+ if (!disableSerial) {
+ traceBeginAndSlog("StartSerialService");
try {
- hardwarePropertiesService = new HardwarePropertiesManagerService(context);
- ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
- hardwarePropertiesService);
+ // Serial port support
+ serial = new SerialService(context);
+ ServiceManager.addService(Context.SERIAL_SERVICE, serial);
} catch (Throwable e) {
- Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+ Slog.e(TAG, "Failure starting SerialService", e);
}
traceEnd();
}
+ traceBeginAndSlog("StartHardwarePropertiesManagerService");
+ try {
+ hardwarePropertiesService = new HardwarePropertiesManagerService(context);
+ ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
+ hardwarePropertiesService);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+ }
+ traceEnd();
+
traceBeginAndSlog("StartTwilightService");
mSystemServiceManager.startService(TwilightService.class);
traceEnd();
- if (NightDisplayController.isAvailable(context)) {
+ if (ColorDisplayController.isAvailable(context)) {
traceBeginAndSlog("StartNightDisplay");
- mSystemServiceManager.startService(NightDisplayService.class);
+ mSystemServiceManager.startService(ColorDisplayService.class);
traceEnd();
}
@@ -1312,47 +1296,45 @@ public final class SystemServer {
mSystemServiceManager.startService(SoundTriggerService.class);
traceEnd();
- if (!disableNonCoreServices) {
- if (!disableTrustManager) {
- traceBeginAndSlog("StartTrustManager");
- mSystemServiceManager.startService(TrustManagerService.class);
- traceEnd();
- }
-
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
- traceBeginAndSlog("StartBackupManager");
- mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
- traceEnd();
- }
-
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
- || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
- traceBeginAndSlog("StartAppWidgerService");
- mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
- traceEnd();
- }
+ if (!disableTrustManager) {
+ traceBeginAndSlog("StartTrustManager");
+ mSystemServiceManager.startService(TrustManagerService.class);
+ traceEnd();
+ }
- // We need to always start this service, regardless of whether the
- // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
- // of initializing various settings. It will internally modify its behavior
- // based on that feature.
- traceBeginAndSlog("StartVoiceRecognitionManager");
- mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
+ traceBeginAndSlog("StartBackupManager");
+ mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
traceEnd();
+ }
- if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
- traceBeginAndSlog("StartGestureLauncher");
- mSystemServiceManager.startService(GestureLauncherService.class);
- traceEnd();
- }
- traceBeginAndSlog("StartSensorNotification");
- mSystemServiceManager.startService(SensorNotificationService.class);
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
+ || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
+ traceBeginAndSlog("StartAppWidgerService");
+ mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
traceEnd();
+ }
+
+ // We need to always start this service, regardless of whether the
+ // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
+ // of initializing various settings. It will internally modify its behavior
+ // based on that feature.
+ traceBeginAndSlog("StartVoiceRecognitionManager");
+ mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ traceEnd();
- traceBeginAndSlog("StartContextHubSystemService");
- mSystemServiceManager.startService(ContextHubSystemService.class);
+ if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
+ traceBeginAndSlog("StartGestureLauncher");
+ mSystemServiceManager.startService(GestureLauncherService.class);
traceEnd();
}
+ traceBeginAndSlog("StartSensorNotification");
+ mSystemServiceManager.startService(SensorNotificationService.class);
+ traceEnd();
+
+ traceBeginAndSlog("StartContextHubSystemService");
+ mSystemServiceManager.startService(ContextHubSystemService.class);
+ traceEnd();
traceBeginAndSlog("StartDiskStatsService");
try {
@@ -1375,16 +1357,14 @@ public final class SystemServer {
traceEnd();
}
- if (!disableNetwork && !disableNetworkTime) {
- traceBeginAndSlog("StartNetworkTimeUpdateService");
- try {
- networkTimeUpdater = new NetworkTimeUpdateService(context);
- ServiceManager.addService("network_time_update_service", networkTimeUpdater);
- } catch (Throwable e) {
- reportWtf("starting NetworkTimeUpdate service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartNetworkTimeUpdateService");
+ try {
+ networkTimeUpdater = new NetworkTimeUpdateService(context);
+ ServiceManager.addService("network_time_update_service", networkTimeUpdater);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkTimeUpdate service", e);
}
+ traceEnd();
traceBeginAndSlog("StartCommonTimeManagementService");
try {
@@ -1395,38 +1375,32 @@ public final class SystemServer {
}
traceEnd();
- if (!disableNetwork) {
- traceBeginAndSlog("CertBlacklister");
- try {
- CertBlacklister blacklister = new CertBlacklister(context);
- } catch (Throwable e) {
- reportWtf("starting CertBlacklister", e);
- }
- traceEnd();
+ traceBeginAndSlog("CertBlacklister");
+ try {
+ CertBlacklister blacklister = new CertBlacklister(context);
+ } catch (Throwable e) {
+ reportWtf("starting CertBlacklister", e);
}
+ traceEnd();
- if (!disableNetwork && !disableNonCoreServices && EmergencyAffordanceManager.ENABLED) {
+ if (EmergencyAffordanceManager.ENABLED) {
// EmergencyMode service
traceBeginAndSlog("StartEmergencyAffordanceService");
mSystemServiceManager.startService(EmergencyAffordanceService.class);
traceEnd();
}
- if (!disableNonCoreServices) {
- // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
- traceBeginAndSlog("StartDreamManager");
- mSystemServiceManager.startService(DreamManagerService.class);
- traceEnd();
- }
+ // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
+ traceBeginAndSlog("StartDreamManager");
+ mSystemServiceManager.startService(DreamManagerService.class);
+ traceEnd();
- if (!disableNonCoreServices) {
- traceBeginAndSlog("AddGraphicsStatsService");
- ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
- new GraphicsStatsService(context));
- traceEnd();
- }
+ traceBeginAndSlog("AddGraphicsStatsService");
+ ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
+ new GraphicsStatsService(context));
+ traceEnd();
- if (!disableNonCoreServices && CoverageService.ENABLED) {
+ if (CoverageService.ENABLED) {
traceBeginAndSlog("AddCoverageService");
ServiceManager.addService(CoverageService.COVERAGE_SERVICE, new CoverageService());
traceEnd();
@@ -1477,38 +1451,37 @@ public final class SystemServer {
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartMediaRouterService");
- try {
- mediaRouter = new MediaRouterService(context);
- ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
- } catch (Throwable e) {
- reportWtf("starting MediaRouterService", e);
- }
- traceEnd();
-
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
- traceBeginAndSlog("StartFingerprintSensor");
- mSystemServiceManager.startService(FingerprintService.class);
- traceEnd();
- }
+ traceBeginAndSlog("StartMediaRouterService");
+ try {
+ mediaRouter = new MediaRouterService(context);
+ ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+ } catch (Throwable e) {
+ reportWtf("starting MediaRouterService", e);
+ }
+ traceEnd();
- traceBeginAndSlog("StartBackgroundDexOptService");
- try {
- BackgroundDexOptService.schedule(context);
- } catch (Throwable e) {
- reportWtf("starting StartBackgroundDexOptService", e);
- }
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ traceBeginAndSlog("StartFingerprintSensor");
+ mSystemServiceManager.startService(FingerprintService.class);
traceEnd();
+ }
- traceBeginAndSlog("StartPruneInstantAppsJobService");
- try {
- PruneInstantAppsJobService.schedule(context);
- } catch (Throwable e) {
- reportWtf("StartPruneInstantAppsJobService", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartBackgroundDexOptService");
+ try {
+ BackgroundDexOptService.schedule(context);
+ } catch (Throwable e) {
+ reportWtf("starting StartBackgroundDexOptService", e);
}
+ traceEnd();
+
+ traceBeginAndSlog("StartPruneInstantAppsJobService");
+ try {
+ PruneInstantAppsJobService.schedule(context);
+ } catch (Throwable e) {
+ reportWtf("StartPruneInstantAppsJobService", e);
+ }
+ traceEnd();
+
// LauncherAppsService uses ShortcutService.
traceBeginAndSlog("StartShortcutServiceLifecycle");
mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
@@ -1517,9 +1490,13 @@ public final class SystemServer {
traceBeginAndSlog("StartLauncherAppsService");
mSystemServiceManager.startService(LauncherAppsService.class);
traceEnd();
+
+ traceBeginAndSlog("StartCrossProfileAppsService");
+ mSystemServiceManager.startService(CrossProfileAppsService.class);
+ traceEnd();
}
- if (!disableNonCoreServices && !disableMediaProjection) {
+ if (!disableMediaProjection) {
traceBeginAndSlog("StartMediaProjectionManager");
mSystemServiceManager.startService(MediaProjectionManagerService.class);
traceEnd();
@@ -1530,17 +1507,15 @@ public final class SystemServer {
mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
traceEnd();
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartWearTimeService");
- mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
- mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
- traceEnd();
+ traceBeginAndSlog("StartWearTimeService");
+ mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
+ mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
+ traceEnd();
- if (enableLeftyService) {
- traceBeginAndSlog("StartWearLeftyService");
- mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
- traceEnd();
- }
+ if (enableLeftyService) {
+ traceBeginAndSlog("StartWearLeftyService");
+ mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
+ traceEnd();
}
}
diff --git a/com/android/server/TelephonyRegistry.java b/com/android/server/TelephonyRegistry.java
index e609bd78..831c9cbc 100644
--- a/com/android/server/TelephonyRegistry.java
+++ b/com/android/server/TelephonyRegistry.java
@@ -1356,31 +1356,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
- if (!checkNotifyPermission("notifyOemHookRawEventForSubscriber")) {
- return;
- }
-
- synchronized (mRecords) {
- for (Record r : mRecords) {
- if (VDBG) {
- log("notifyOemHookRawEventForSubscriber: r=" + r + " subId=" + subId);
- }
- if ((r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT)) &&
- ((r.subId == subId) ||
- (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))) {
- try {
- r.callback.onOemHookRawEvent(rawData);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
- }
- }
- }
- handleRemoveListLocked();
- }
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1673,11 +1648,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
}
-
- if ((events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
- }
}
private void handleRemoveListLocked() {
diff --git a/com/android/server/VibratorService.java b/com/android/server/VibratorService.java
index 8b79b9dd..0e51fda0 100644
--- a/com/android/server/VibratorService.java
+++ b/com/android/server/VibratorService.java
@@ -27,6 +27,7 @@ import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.hardware.vibrator.V1_0.Constants.EffectStrength;
import android.media.AudioManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.os.BatteryStats;
import android.os.Handler;
@@ -56,7 +57,6 @@ import android.media.AudioAttributes;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/com/android/server/Watchdog.java b/com/android/server/Watchdog.java
index 8d46d1e2..35f83e41 100644
--- a/com/android/server/Watchdog.java
+++ b/com/android/server/Watchdog.java
@@ -60,9 +60,6 @@ public class Watchdog extends Thread {
// Set this to true to use debug default values.
static final boolean DB = false;
- // Set this to true to have the watchdog record kernel thread stacks when it fires
- static final boolean RECORD_KERNEL_THREADS = true;
-
// Note 1: Do not lower this value below thirty seconds without tightening the invoke-with
// timeout in com.android.internal.os.ZygoteConnection, or wrapped applications
// can trigger the watchdog.
@@ -509,11 +506,6 @@ public class Watchdog extends Thread {
// The system's been hanging for a minute, another second or two won't hurt much.
SystemClock.sleep(2000);
- // Pull our own kernel thread stacks as well if we're configured for that
- if (RECORD_KERNEL_THREADS) {
- dumpKernelStackTraces();
- }
-
// Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
doSysRq('w');
doSysRq('l');
@@ -591,18 +583,6 @@ public class Watchdog extends Thread {
}
}
- private File dumpKernelStackTraces() {
- String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
- if (tracesPath == null || tracesPath.length() == 0) {
- return null;
- }
-
- native_dumpKernelStacks(tracesPath);
- return new File(tracesPath);
- }
-
- private native void native_dumpKernelStacks(String tracesPath);
-
public static final class OpenFdMonitor {
/**
* Number of FDs below the soft limit that we trigger a runtime restart at. This was
diff --git a/com/android/server/accessibility/AccessibilityManagerService.java b/com/android/server/accessibility/AccessibilityManagerService.java
index 06c110de..d661754a 100644
--- a/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1318,7 +1318,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void unbindAllServicesLocked(UserState userState) {
List<AccessibilityServiceConnection> services = userState.mBoundServices;
- for (int count = services.size(); count > 1; count--) {
+ for (int count = services.size(); count > 0; count--) {
// When the service is unbound, it disappears from the list, so there's no need to
// keep track of the index
services.get(0).unbindLocked();
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index c04ddf8a..2289f857 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -276,17 +276,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
if (windowingMode == WINDOWING_MODE_PINNED) {
return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
}
- final T stack = (T) new ActivityStack(
+ return (T) new ActivityStack(
this, stackId, mSupervisor, windowingMode, activityType, onTop);
-
- if (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 on that.
- // TODO: Not sure if this is needed after we change to calculate visibility based on
- // stack z-order vs. id.
- getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
- }
- return stack;
}
/**
@@ -365,6 +356,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
+ " already exist on display=" + this + " stack=" + stack);
}
mSplitScreenPrimaryStack = stack;
+ onSplitScreenModeActivated();
}
}
@@ -377,6 +369,42 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
mPinnedStack = null;
} else if (stack == mSplitScreenPrimaryStack) {
mSplitScreenPrimaryStack = null;
+ // Inform the reset of the system that split-screen mode was dismissed so things like
+ // resizing all the other stacks can take place.
+ onSplitScreenModeDismissed();
+ }
+ }
+
+ private void onSplitScreenModeDismissed() {
+ mSupervisor.mWindowManager.deferSurfaceLayout();
+ try {
+ // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack otherStack = mStacks.get(i);
+ if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
+ continue;
+ }
+ otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ }
+ } finally {
+ mSupervisor.mWindowManager.continueSurfaceLayout();
+ }
+ }
+
+ private void onSplitScreenModeActivated() {
+ mSupervisor.mWindowManager.deferSurfaceLayout();
+ try {
+ // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack otherStack = mStacks.get(i);
+ if (otherStack == mSplitScreenPrimaryStack
+ || !otherStack.affectedBySplitScreenResize()) {
+ continue;
+ }
+ otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ }
+ } finally {
+ mSupervisor.mWindowManager.continueSurfaceLayout();
}
}
@@ -404,8 +432,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
|| windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
- windowingMode, activityType);
+ return supportsSplitScreen
+ && WindowConfiguration.supportSplitScreenWindowingMode(activityType);
}
if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
@@ -475,22 +503,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
supportsFreeform, supportsPip, activityType)) {
return windowingMode;
}
- // Return the display's windowing mode
- return getWindowingMode();
- }
-
- /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
- int getTopVisibleStackActivityType(int excludeWindowingMode) {
- for (int i = mStacks.size() - 1; i >= 0; --i) {
- final ActivityStack stack = mStacks.get(i);
- if (stack.getWindowingMode() == excludeWindowingMode) {
- continue;
- }
- if (stack.shouldBeVisible(null /* starting */)) {
- return stack.getActivityType();
- }
- }
- return ACTIVITY_TYPE_UNDEFINED;
+ // Try to use the display's windowing mode otherwise fallback to fullscreen.
+ windowingMode = getWindowingMode();
+ return windowingMode != WINDOWING_MODE_UNDEFINED
+ ? windowingMode : WINDOWING_MODE_FULLSCREEN;
}
/**
@@ -599,7 +615,20 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
}
public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "displayId=" + mDisplayId + " mStacks=" + mStacks);
+ pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size());
+ final String myPrefix = prefix + " ";
+ if (mHomeStack != null) {
+ pw.println(myPrefix + "mHomeStack=" + mHomeStack);
+ }
+ if (mRecentsStack != null) {
+ pw.println(myPrefix + "mRecentsStack=" + mRecentsStack);
+ }
+ if (mPinnedStack != null) {
+ pw.println(myPrefix + "mPinnedStack=" + mPinnedStack);
+ }
+ if (mSplitScreenPrimaryStack != null) {
+ pw.println(myPrefix + "mSplitScreenPrimaryStack=" + mSplitScreenPrimaryStack);
+ }
}
public void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/com/android/server/am/ActivityManagerDebugConfig.java b/com/android/server/am/ActivityManagerDebugConfig.java
index ceb2ad62..0a7d3fdb 100644
--- a/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/com/android/server/am/ActivityManagerDebugConfig.java
@@ -92,6 +92,7 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false;
static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false;
static final boolean DEBUG_WHITELISTS = DEBUG_ALL || false;
+ static final boolean DEBUG_METRICS = DEBUG_ALL || false;
static final String POSTFIX_ADD_REMOVE = (APPEND_CATEGORY_NAME) ? "_AddRemove" : "";
static final String POSTFIX_APP = (APPEND_CATEGORY_NAME) ? "_App" : "";
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index f2e04932..43618564 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.Manifest.permission.BIND_VOICE_INTERACTION;
import static android.Manifest.permission.CHANGE_CONFIGURATION;
import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
@@ -25,10 +26,16 @@ import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REMOVE_TASKS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.app.AppOpsManager.OP_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -37,6 +44,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT
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_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -214,6 +222,7 @@ import android.app.Dialog;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.app.IApplicationThread;
+import android.app.IAssistDataReceiver;
import android.app.IInstrumentationWatcher;
import android.app.INotificationManager;
import android.app.IProcessObserver;
@@ -331,7 +340,6 @@ import android.provider.Downloads;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
-import android.service.voice.VoiceInteractionSession;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -393,6 +401,7 @@ import com.android.server.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleController;
import com.android.server.IntentResolver;
+import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.NetworkManagementInternal;
@@ -782,7 +791,7 @@ public class ActivityManagerService extends IActivityManager.Stub
public final Bundle extras;
public final Intent intent;
public final String hint;
- public final IResultReceiver receiver;
+ public final IAssistDataReceiver receiver;
public final int userHandle;
public boolean haveResult = false;
public Bundle result = null;
@@ -791,7 +800,8 @@ public class ActivityManagerService extends IActivityManager.Stub
public Bundle receiverExtras;
public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
- String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
+ String _hint, IAssistDataReceiver _receiver, Bundle _receiverExtras,
+ int _userHandle) {
activity = _activity;
extras = _extras;
intent = _intent;
@@ -812,8 +822,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- final ArrayList<PendingAssistExtras> mPendingAssistExtras
- = new ArrayList<PendingAssistExtras>();
+ final ArrayList<PendingAssistExtras> mPendingAssistExtras = new ArrayList<>();
/**
* Process management.
@@ -1003,15 +1012,6 @@ public class ActivityManagerService extends IActivityManager.Stub
private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
/**
- * Strict Mode background batched logging state.
- *
- * The string buffer is guarded by itself, and its lock is also
- * used to determine if another batched write is already
- * in-flight.
- */
- private final StringBuilder mStrictModeBuffer = new StringBuilder();
-
- /**
* Keeps track of all IIntentReceivers that have been registered for broadcasts.
* Hash keys are the receiver IBinder, hash value is a ReceiverList.
*/
@@ -2779,12 +2779,12 @@ public class ActivityManagerService extends IActivityManager.Stub
mConfigurationSeq = mTempConfig.seq = 1;
mStackSupervisor = createStackSupervisor();
mStackSupervisor.onConfigurationChanged(mTempConfig);
- mKeyguardController = mStackSupervisor.mKeyguardController;
+ mKeyguardController = mStackSupervisor.getKeyguardController();
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
- mActivityStarter = new ActivityStarter(this);
+ mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -2828,7 +2828,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
protected ActivityStackSupervisor createStackSupervisor() {
- return new ActivityStackSupervisor(this, mHandler.getLooper());
+ final ActivityStackSupervisor supervisor = new ActivityStackSupervisor(this, mHandler.getLooper());
+ supervisor.initialize();
+ return supervisor;
}
protected RecentTasks createRecentTasks() {
@@ -3061,7 +3063,7 @@ public class ActivityManagerService extends IActivityManager.Stub
public void batterySendBroadcast(Intent intent) {
synchronized (this) {
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false,
+ OP_NONE, null, false, false,
-1, SYSTEM_UID, UserHandle.USER_ALL);
}
}
@@ -3840,6 +3842,10 @@ public class ActivityManagerService extends IActivityManager.Stub
gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
+
+ // Replace any invalid GIDs
+ if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2];
+ if (gids[1] == UserHandle.ERR_GID) gids[1] = gids[2];
}
checkTime(startTime, "startProcess: building args");
if (mFactoryTest != FactoryTest.FACTORY_TEST_OFF) {
@@ -3889,7 +3895,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (app.info.isPrivilegedApp() &&
- !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
runtimeFlags |= Zygote.DISABLE_VERIFIER;
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
@@ -4035,10 +4041,14 @@ public class ActivityManagerService extends IActivityManager.Stub
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
"updateUsageStats: comp=" + component + "res=" + resumed);
final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED,
+ component.userId, component.realActivity.getPackageName(),
+ component.realActivity.getShortClassName(), resumed ? 1 : 0);
if (resumed) {
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(component.realActivity, component.userId,
UsageEvents.Event.MOVE_TO_FOREGROUND);
+
}
synchronized (stats) {
stats.noteActivityResumedLocked(component.app.uid);
@@ -4083,7 +4093,7 @@ public class ActivityManagerService extends IActivityManager.Stub
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid, true);
if (app == null || app.instr == null) {
- intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
// For ANR debugging to verify if the user activity is the one that actually
// launched.
@@ -4155,7 +4165,7 @@ public class ActivityManagerService extends IActivityManager.Stub
String lastVers = Settings.Secure.getString(
resolver, Settings.Secure.LAST_SETUP_SHOWN);
if (vers != null && !vers.equals(lastVers)) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name));
mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/,
@@ -4678,15 +4688,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Intent intent, String resolvedType, IVoiceInteractionSession session,
IVoiceInteractor interactor, int startFlags, ProfilerInfo profilerInfo,
Bundle bOptions, int userId) {
- if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: startVoiceActivity() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
+ enforceCallingPermission(BIND_VOICE_INTERACTION, "startVoiceActivity()");
if (session == null || interactor == null) {
throw new NullPointerException("null session or interactor");
}
@@ -4701,15 +4703,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public int startAssistantActivity(String callingPackage, int callingPid, int callingUid,
Intent intent, String resolvedType, Bundle bOptions, int userId) {
- if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
- != PackageManager.PERMISSION_GRANTED) {
- final String msg = "Permission Denial: startAssistantActivity() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + Manifest.permission.BIND_VOICE_INTERACTION;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
+ enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()");
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
ALLOW_FULL_ONLY, "startAssistantActivity", null);
return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
@@ -4718,11 +4712,57 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public int startRecentsActivity(IAssistDataReceiver assistDataReceiver, Bundle options,
+ Bundle activityOptions, int userId) {
+ if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+ String msg = "Permission Denial: startRecentsActivity() from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " not recent tasks package";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ final int recentsUid = mRecentTasks.getRecentsComponentUid();
+ final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
+ final String recentsPackage = recentsComponent.getPackageName();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ // If provided, kick off the request for the assist data in the background before
+ // starting the activity
+ if (assistDataReceiver != null) {
+ final AppOpsManager appOpsManager = (AppOpsManager)
+ mContext.getSystemService(Context.APP_OPS_SERVICE);
+ final AssistDataReceiverProxy proxy = new AssistDataReceiverProxy(
+ assistDataReceiver, recentsPackage);
+ final AssistDataRequester requester = new AssistDataRequester(mContext, this,
+ mWindowManager, appOpsManager, proxy, this,
+ OP_ASSIST_STRUCTURE, OP_NONE);
+ requester.requestAssistData(mStackSupervisor.getTopVisibleActivities(),
+ true /* fetchData */, false /* fetchScreenshots */,
+ true /* allowFetchData */, false /* alloweFetchScreenshots */,
+ recentsUid, recentsPackage);
+ }
+
+ final Intent intent = new Intent();
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(recentsComponent);
+ intent.putExtras(options);
+ return mActivityStarter.startActivityMayWait(null, recentsUid, recentsPackage,
+ intent, null, null, null, null, null, 0, 0, null, null, null, activityOptions,
+ false, userId, null, "startRecentsActivity");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options)
throws RemoteException {
Slog.i(TAG, "Activity tried to startVoiceInteraction");
synchronized (this) {
- ActivityRecord activity = getFocusedStack().topActivity();
+ ActivityRecord activity = getFocusedStack().getTopActivity();
if (ActivityRecord.forTokenLocked(callingActivity) != activity) {
throw new SecurityException("Only focused activity can call startVoiceInteraction");
}
@@ -4863,7 +4903,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Intent.FLAG_ACTIVITY_FORWARD_RESULT|
Intent.FLAG_ACTIVITY_CLEAR_TOP|
Intent.FLAG_ACTIVITY_MULTIPLE_TASK|
- Intent.FLAG_ACTIVITY_NEW_TASK));
+ FLAG_ACTIVITY_NEW_TASK));
// Okay now we need to start the new activity, replacing the
// currently running activity. This is a little tricky because
@@ -5317,8 +5357,7 @@ public class ActivityManagerService extends IActivityManager.Stub
return -1;
}
- final ProcessRecord getRecordForAppLocked(
- IApplicationThread thread) {
+ ProcessRecord getRecordForAppLocked(IApplicationThread thread) {
if (thread == null) {
return null;
}
@@ -6292,7 +6331,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mStackSupervisor.closeSystemDialogsLocked();
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false,
+ OP_NONE, null, false, false,
-1, SYSTEM_UID, UserHandle.USER_ALL);
}
@@ -6394,7 +6433,7 @@ public class ActivityManagerService extends IActivityManager.Stub
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, null, 0, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, UserHandle.getUserId(uid));
}
@@ -7042,10 +7081,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
// We deprecated Build.SERIAL and it is not accessible to
- // apps that target the v2 security sandbox. Since access to
- // the serial is now behind a permission we push down the value.
- String buildSerial = appInfo.targetSandboxVersion < 2
- ? sTheRealBuildSerial : Build.UNKNOWN;
+ // apps that target the v2 security sandbox and to apps that
+ // target APIs higher than O MR1. Since access to the serial
+ // is now behind a permission we push down the value.
+ final String buildSerial = (appInfo.targetSandboxVersion < 2
+ && appInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1)
+ ? sTheRealBuildSerial : Build.UNKNOWN;
// Check if this is a secondary process that should be incorporated into some
// currently active instrumentation. (Note we do this AFTER all of the profiling
@@ -7082,7 +7123,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
- mStackSupervisor.mActivityMetricsLogger.notifyBindApplication(app);
+ mStackSupervisor.getActivityMetricsLogger().notifyBindApplication(app);
if (app.isolatedEntryPoint != null) {
// This is an isolated process which should just call an entry point instead of
// being bound to an application.
@@ -10396,13 +10437,7 @@ public class ActivityManagerService extends IActivityManager.Stub
"exitFreeformMode: You can only go fullscreen from freeform.");
}
- final ActivityStack fullscreenStack = stack.getDisplay().getOrCreateStack(
- WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
-
- if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
- // TODO: Should just change windowing mode vs. re-parenting...
- r.getTask().reparent(fullscreenStack, ON_TOP,
- REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "exitFreeformMode");
+ stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10411,6 +10446,11 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
+ toTop, ANIMATE, null /* initialBounds */);
+ return;
+ }
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
@@ -10423,20 +10463,16 @@ public class ActivityManagerService extends IActivityManager.Stub
if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
+ " to windowingMode=" + windowingMode + " toTop=" + toTop);
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
- null /* initialBounds */);
- }
if (!task.isActivityTypeStandardOrUndefined()) {
- throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move task "
- + taskId + " to non-standard windowin mode=" + windowingMode);
+ throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
+ + " 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: We should just change the windowing mode for the task vs. creating and
- // moving it to a stack.
+ // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
"moveTaskToStack");
} finally {
@@ -10445,6 +10481,55 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /**
+ * Moves the specified task to the primary-split-screen stack.
+ *
+ * @param taskId Id of task to move.
+ * @param createMode The mode the primary split screen stack should be created in if it doesn't
+ * exist already. See
+ * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
+ * and
+ * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
+ * @param toTop If the task and stack should be moved to the top.
+ * @param animate Whether we should play an animation for the moving the task.
+ * @param initialBounds If the primary stack gets created, it will use these bounds for the
+ * stack. Pass {@code null} to use default bounds.
+ */
+ @Override
+ public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+ boolean animate, Rect initialBounds) {
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "setTaskWindowingModeSplitScreenPrimary()");
+ synchronized (this) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
+ return false;
+ }
+ if (DEBUG_STACK) Slog.d(TAG_STACK,
+ "setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId
+ + " to createMode=" + createMode + " toTop=" + toTop);
+ if (!task.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
+ + " non-standard task " + taskId + " to split-screen windowing mode");
+ }
+
+ mWindowManager.setDockedStackCreateState(createMode, initialBounds);
+ final int windowingMode = task.getWindowingMode();
+ final ActivityStack stack = task.getStack();
+ if (toTop) {
+ stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);
+ }
+ stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate);
+ return windowingMode != task.getWindowingMode();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
@Override
public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
@@ -10471,7 +10556,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (stack.inSplitScreenPrimaryWindowingMode()) {
mWindowManager.setDockedStackCreateState(
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
}
task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
"moveTaskToStack");
@@ -10482,56 +10567,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
- * Moves the input task to the docked stack.
- *
- * @param taskId Id of task to move.
- * @param createMode The mode the docked stack should be created in if it doesn't exist
- * already. See
- * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT}
- * and
- * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
- * @param toTop If the task and stack should be moved to the top.
- * @param animate Whether we should play an animation for the moving the task
- * @param initialBounds If the docked stack gets created, it will use these bounds for the
- * docked stack. Pass {@code null} to use default bounds.
- */
- @Override
- public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
- Rect initialBounds) {
- enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
- synchronized (this) {
- long ident = Binder.clearCallingIdentity();
- try {
- final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
- if (task == null) {
- Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
- return false;
- }
- if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
- + " to createMode=" + createMode + " toTop=" + toTop);
- mWindowManager.setDockedStackCreateState(createMode, initialBounds);
-
- final ActivityDisplay display = task.getStack().getDisplay();
- final ActivityStack stack = display.getOrCreateStack(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, task.getStack().getActivityType(),
- toTop);
-
- // Defer resuming until we move the home stack to the front below
- // TODO: Should just change windowing mode vs. re-parenting...
- final boolean moved = task.reparent(stack, toTop,
- REPARENT_KEEP_STACK_AT_FRONT, animate, !DEFER_RESUME,
- "moveTaskToDockedStack");
- if (moved) {
- mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
- }
- return moved;
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- /**
* Dismisses split-screen multi-window mode.
* @param toTop If true the current primary split-screen stack will be placed or left on top.
*/
@@ -10547,14 +10582,11 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
return;
}
+
if (toTop) {
- mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
- null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- true /* preserveWindows */, true /* allowResizeInDockedMode */,
- !DEFER_RESUME);
- } else {
- mStackSupervisor.moveTasksToFullscreenStackLocked(stack, false /* onTop */);
+ stack.moveToFront("dismissSplitScreenMode");
}
+ stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10806,7 +10838,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isAppPinning) {
+ private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isSystemCaller) {
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
return;
@@ -10820,13 +10852,16 @@ public class ActivityManagerService extends IActivityManager.Stub
// When a task is locked, dismiss the pinned stack if it exists
mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
- // isAppPinning is used to distinguish between locked and pinned mode, as pinned mode
- // is initiated by system after the pinning request was shown and locked mode is initiated
- // by an authorized app directly
+ // {@code isSystemCaller} is used to distinguish whether this request is initiated by the
+ // system or a specific app.
+ // * System-initiated requests will only start the pinned mode (screen pinning)
+ // * App-initiated requests
+ // - will put the device in fully locked mode (LockTask), if the app is whitelisted
+ // - will start the pinned mode, otherwise
final int callingUid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
- mLockTaskController.startLockTaskMode(task, isAppPinning, callingUid);
+ mLockTaskController.startLockTaskMode(task, isSystemCaller, callingUid);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10839,7 +10874,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (r == null) {
return;
}
- startLockTaskModeLocked(r.getTask(), false /* not system initiated */);
+ startLockTaskModeLocked(r.getTask(), false /* isSystemCaller */);
}
}
@@ -10851,7 +10886,7 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
synchronized (this) {
startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId),
- true /* system initiated */);
+ true /* isSystemCaller */);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10859,8 +10894,14 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void stopLockTaskMode() {
- stopLockTaskModeInternal(false /* not system initiated */);
+ public void stopLockTaskModeByToken(IBinder token) {
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ return;
+ }
+ stopLockTaskModeInternal(r.getTask(), false /* isSystemCaller */);
+ }
}
/**
@@ -10870,15 +10911,15 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void stopSystemLockTaskMode() throws RemoteException {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "stopSystemLockTaskMode");
- stopLockTaskModeInternal(true /* system initiated */);
+ stopLockTaskModeInternal(null, true /* isSystemCaller */);
}
- private void stopLockTaskModeInternal(boolean isSystemRequest) {
+ private void stopLockTaskModeInternal(@Nullable TaskRecord task, boolean isSystemCaller) {
final int callingUid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
- mLockTaskController.stopLockTaskMode(isSystemRequest, callingUid);
+ mLockTaskController.stopLockTaskMode(task, isSystemCaller, callingUid);
}
// Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
// task and jumping straight into a call in the case of emergency call back.
@@ -11629,7 +11670,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, cpi.packageName);
@@ -12901,7 +12942,7 @@ public class ActivityManagerService extends IActivityManager.Stub
return false;
}
- final ActivityRecord activity = focusedStack.topActivity();
+ final ActivityRecord activity = focusedStack.getTopActivity();
if (activity == null) {
return false;
}
@@ -12918,7 +12959,7 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
synchronized (this) {
ActivityRecord caller = ActivityRecord.forTokenLocked(token);
- ActivityRecord top = getFocusedStack().topActivity();
+ ActivityRecord top = getFocusedStack().getTopActivity();
if (top != caller) {
Slog.w(TAG, "showAssistFromActivity failed: caller " + caller
+ " is not current top " + top);
@@ -12938,7 +12979,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
+ public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
@@ -12946,7 +12987,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public boolean requestAutofillData(IResultReceiver receiver, Bundle receiverExtras,
+ public boolean requestAutofillData(IAssistDataReceiver receiver, Bundle receiverExtras,
IBinder activityToken, int flags) {
return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null,
receiver, receiverExtras, activityToken, true, true, UserHandle.getCallingUserId(),
@@ -12954,14 +12995,14 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
- IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
+ IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
int flags) {
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"enqueueAssistContext()");
synchronized (this) {
- ActivityRecord activity = getFocusedStack().topActivity();
+ ActivityRecord activity = getFocusedStack().getTopActivity();
if (activity == null) {
Slog.w(TAG, "getAssistContextExtras failed: no top activity");
return null;
@@ -13022,7 +13063,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
void pendingAssistExtrasTimedOut(PendingAssistExtras pae) {
- IResultReceiver receiver;
+ IAssistDataReceiver receiver;
synchronized (this) {
mPendingAssistExtras.remove(pae);
receiver = pae.receiver;
@@ -13031,10 +13072,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// Caller wants result sent back to them.
Bundle sendBundle = new Bundle();
// At least return the receiver extras
- sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
- pae.receiverExtras);
+ sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
try {
- pae.receiver.send(0, sendBundle);
+ pae.receiver.onHandleAssistData(sendBundle);
} catch (RemoteException e) {
}
}
@@ -13072,7 +13112,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
// We are now ready to launch the assist activity.
- IResultReceiver sendReceiver = null;
+ IAssistDataReceiver sendReceiver = null;
Bundle sendBundle = null;
synchronized (this) {
buildAssistBundleLocked(pae, extras);
@@ -13085,16 +13125,15 @@ public class ActivityManagerService extends IActivityManager.Stub
if ((sendReceiver=pae.receiver) != null) {
// Caller wants result sent back to them.
sendBundle = new Bundle();
- sendBundle.putBundle(VoiceInteractionSession.KEY_DATA, pae.extras);
- sendBundle.putParcelable(VoiceInteractionSession.KEY_STRUCTURE, pae.structure);
- sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
- sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
- pae.receiverExtras);
+ sendBundle.putBundle(ASSIST_KEY_DATA, pae.extras);
+ sendBundle.putParcelable(ASSIST_KEY_STRUCTURE, pae.structure);
+ sendBundle.putParcelable(ASSIST_KEY_CONTENT, pae.content);
+ sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
}
}
if (sendReceiver != null) {
try {
- sendReceiver.send(0, sendBundle);
+ sendReceiver.onHandleAssistData(sendBundle);
} catch (RemoteException e) {
}
return;
@@ -13108,7 +13147,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mContext.startServiceAsUser(pae.intent, new UserHandle(pae.userHandle));
} else {
pae.intent.replaceExtras(pae.extras);
- pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ pae.intent.setFlags(FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
closeSystemDialogs("assist");
@@ -13875,7 +13914,7 @@ public class ActivityManagerService extends IActivityManager.Stub
.setPackage("android")
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
broadcastIntent(null, intent, null, null, 0, null, null, null,
- android.app.AppOpsManager.OP_NONE, null, true, false, UserHandle.USER_ALL);
+ OP_NONE, null, true, false, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -14129,7 +14168,7 @@ public class ActivityManagerService extends IActivityManager.Stub
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, null, 0, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID,
currentUserId);
intent = new Intent(Intent.ACTION_USER_STARTING);
@@ -14143,7 +14182,7 @@ public class ActivityManagerService extends IActivityManager.Stub
throws RemoteException {
}
}, 0, null, null,
- new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+ new String[] {INTERACT_ACROSS_USERS}, OP_NONE,
null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
} catch (Throwable t) {
Slog.wtf(TAG, "Failed sending first user broadcasts", t);
@@ -14223,10 +14262,9 @@ public class ActivityManagerService extends IActivityManager.Stub
IBinder app,
int violationMask,
StrictMode.ViolationInfo info) {
- ProcessRecord r = findAppProcess(app, "StrictMode");
- if (r == null) {
- return;
- }
+ // We're okay if the ProcessRecord is missing; it probably means that
+ // we're reporting a violation from the system process itself.
+ final ProcessRecord r = findAppProcess(app, "StrictMode");
if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
Integer stackFingerprint = info.hashCode();
@@ -14288,18 +14326,15 @@ public class ActivityManagerService extends IActivityManager.Stub
(process.info.flags & (ApplicationInfo.FLAG_SYSTEM |
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0;
final String processName = process == null ? "unknown" : process.processName;
- final String dropboxTag = isSystemApp ? "system_app_strictmode" : "data_app_strictmode";
final DropBoxManager dbox = (DropBoxManager)
mContext.getSystemService(Context.DROPBOX_SERVICE);
// Exit early if the dropbox isn't configured to accept this report type.
+ final String dropboxTag = processClass(process) + "_strictmode";
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
- boolean bufferWasEmpty;
- boolean needsFlush;
- final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024);
+ final StringBuilder sb = new StringBuilder(1024);
synchronized (sb) {
- bufferWasEmpty = sb.length() == 0;
appendDropBoxProcessHeaders(process, processName, sb);
sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
sb.append("System-App: ").append(isSystemApp).append("\n");
@@ -14325,75 +14360,18 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
sb.append("\n");
- if (info.hasStackTrace()) {
- sb.append(info.getStackTrace());
- sb.append("\n");
- }
+ sb.append(info.getStackTrace());
+ sb.append("\n");
if (info.getViolationDetails() != null) {
sb.append(info.getViolationDetails());
sb.append("\n");
}
-
- // Only buffer up to ~64k. Various logging bits truncate
- // things at 128k.
- needsFlush = (sb.length() > 64 * 1024);
- }
-
- // Flush immediately if the buffer's grown too large, or this
- // is a non-system app. Non-system apps are isolated with a
- // different tag & policy and not batched.
- //
- // Batching is useful during internal testing with
- // StrictMode settings turned up high. Without batching,
- // thousands of separate files could be created on boot.
- if (!isSystemApp || needsFlush) {
- new Thread("Error dump: " + dropboxTag) {
- @Override
- public void run() {
- String report;
- synchronized (sb) {
- report = sb.toString();
- sb.delete(0, sb.length());
- sb.trimToSize();
- }
- if (report.length() != 0) {
- dbox.addText(dropboxTag, report);
- }
- }
- }.start();
- return;
- }
-
- // System app batching:
- if (!bufferWasEmpty) {
- // An existing dropbox-writing thread is outstanding, so
- // we don't need to start it up. The existing thread will
- // catch the buffer appends we just did.
- return;
}
- // Worker thread to both batch writes and to avoid blocking the caller on I/O.
- // (After this point, we shouldn't access AMS internal data structures.)
- new Thread("Error dump: " + dropboxTag) {
- @Override
- public void run() {
- // 5 second sleep to let stacks arrive and be batched together
- try {
- Thread.sleep(5000); // 5 seconds
- } catch (InterruptedException e) {}
-
- String errorReport;
- synchronized (mStrictModeBuffer) {
- errorReport = mStrictModeBuffer.toString();
- if (errorReport.length() == 0) {
- return;
- }
- mStrictModeBuffer.delete(0, mStrictModeBuffer.length());
- mStrictModeBuffer.trimToSize();
- }
- dbox.addText(dropboxTag, errorReport);
- }
- }.start();
+ final String res = sb.toString();
+ IoThread.getHandler().post(() -> {
+ dbox.addText(dropboxTag, res);
+ });
}
/**
@@ -14660,7 +14638,12 @@ public class ActivityManagerService extends IActivityManager.Stub
if (process == null) {
// If process is null, we are being called from some internal code
// and may be about to die -- run this synchronously.
- worker.run();
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try {
+ worker.run();
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
} else {
worker.start();
}
@@ -17043,22 +17026,39 @@ public class ActivityManagerService extends IActivityManager.Stub
return stringifySize(size * 1024, 1024);
}
- // Update this version number in case you change the 'compact' format
+ // Update this version number if you change the 'compact' format.
private static final int MEMINFO_COMPACT_VERSION = 1;
+ private static class MemoryUsageDumpOptions {
+ boolean dumpDetails;
+ boolean dumpFullDetails;
+ boolean dumpDalvik;
+ boolean dumpSummaryOnly;
+ boolean dumpUnreachable;
+ boolean oomOnly;
+ boolean isCompact;
+ boolean localOnly;
+ boolean packages;
+ boolean isCheckinRequest;
+ boolean dumpSwapPss;
+ boolean dumpProto;
+ }
+
final void dumpApplicationMemoryUsage(FileDescriptor fd,
PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
- boolean dumpDetails = false;
- boolean dumpFullDetails = false;
- boolean dumpDalvik = false;
- boolean dumpSummaryOnly = false;
- boolean dumpUnreachable = false;
- boolean oomOnly = false;
- boolean isCompact = false;
- boolean localOnly = false;
- boolean packages = false;
- boolean isCheckinRequest = false;
- boolean dumpSwapPss = false;
+ MemoryUsageDumpOptions opts = new MemoryUsageDumpOptions();
+ opts.dumpDetails = false;
+ opts.dumpFullDetails = false;
+ opts.dumpDalvik = false;
+ opts.dumpSummaryOnly = false;
+ opts.dumpUnreachable = false;
+ opts.oomOnly = false;
+ opts.isCompact = false;
+ opts.localOnly = false;
+ opts.packages = false;
+ opts.isCheckinRequest = false;
+ opts.dumpSwapPss = false;
+ opts.dumpProto = false;
int opti = 0;
while (opti < args.length) {
@@ -17068,29 +17068,31 @@ public class ActivityManagerService extends IActivityManager.Stub
}
opti++;
if ("-a".equals(opt)) {
- dumpDetails = true;
- dumpFullDetails = true;
- dumpDalvik = true;
- dumpSwapPss = true;
+ opts.dumpDetails = true;
+ opts.dumpFullDetails = true;
+ opts.dumpDalvik = true;
+ opts.dumpSwapPss = true;
} else if ("-d".equals(opt)) {
- dumpDalvik = true;
+ opts.dumpDalvik = true;
} else if ("-c".equals(opt)) {
- isCompact = true;
+ opts.isCompact = true;
} else if ("-s".equals(opt)) {
- dumpDetails = true;
- dumpSummaryOnly = true;
+ opts.dumpDetails = true;
+ opts.dumpSummaryOnly = true;
} else if ("-S".equals(opt)) {
- dumpSwapPss = true;
+ opts.dumpSwapPss = true;
} else if ("--unreachable".equals(opt)) {
- dumpUnreachable = true;
+ opts.dumpUnreachable = true;
} else if ("--oom".equals(opt)) {
- oomOnly = true;
+ opts.oomOnly = true;
} else if ("--local".equals(opt)) {
- localOnly = true;
+ opts.localOnly = true;
} else if ("--package".equals(opt)) {
- packages = true;
+ opts.packages = true;
} else if ("--checkin".equals(opt)) {
- isCheckinRequest = true;
+ opts.isCheckinRequest = true;
+ } else if ("--proto".equals(opt)) {
+ opts.dumpProto = true;
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
@@ -17104,6 +17106,7 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println(" --package: interpret process arg as package, dumping all");
pw.println(" processes that have loaded that package.");
pw.println(" --checkin: dump data for a checkin");
+ pw.println(" --proto: dump data to proto");
pw.println("If [process] is specified it can be the name or ");
pw.println("pid of a specific process to dump.");
return;
@@ -17112,21 +17115,33 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ String[] innerArgs = new String[args.length-opti];
+ System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+
+ ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, opts.packages, args);
+ if (opts.dumpProto) {
+ dumpApplicationMemoryUsage(fd, pw, opts, innerArgs, brief, procs);
+ } else {
+ dumpApplicationMemoryUsage(fd, pw, prefix, opts, innerArgs, brief, procs, categoryPw);
+ }
+ }
+
+ final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix,
+ MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+ ArrayList<ProcessRecord> procs, PrintWriter categoryPw) {
long uptime = SystemClock.uptimeMillis();
long realtime = SystemClock.elapsedRealtime();
final long[] tmpLong = new long[1];
- ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, packages, args);
if (procs == null) {
// No Java processes. Maybe they want to print a native process.
- if (args != null && args.length > opti
- && args[opti].charAt(0) != '-') {
+ if (innerArgs.length > 0 && innerArgs[0].charAt(0) != '-') {
ArrayList<ProcessCpuTracker.Stats> nativeProcs
= new ArrayList<ProcessCpuTracker.Stats>();
updateCpuStatsNow();
int findPid = -1;
try {
- findPid = Integer.parseInt(args[opti]);
+ findPid = Integer.parseInt(innerArgs[0]);
} catch (NumberFormatException e) {
}
synchronized (mProcessCpuTracker) {
@@ -17134,51 +17149,48 @@ public class ActivityManagerService extends IActivityManager.Stub
for (int i=0; i<N; i++) {
ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
if (st.pid == findPid || (st.baseName != null
- && st.baseName.equals(args[opti]))) {
+ && st.baseName.equals(innerArgs[0]))) {
nativeProcs.add(st);
}
}
}
if (nativeProcs.size() > 0) {
- dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest,
- isCompact);
+ dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest,
+ opts.isCompact);
Debug.MemoryInfo mi = null;
for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) {
final ProcessCpuTracker.Stats r = nativeProcs.get(i);
final int pid = r.pid;
- if (!isCheckinRequest && dumpDetails) {
+ if (!opts.isCheckinRequest && opts.dumpDetails) {
pw.println("\n** MEMINFO in pid " + pid + " [" + r.baseName + "] **");
}
if (mi == null) {
mi = new Debug.MemoryInfo();
}
- if (dumpDetails || (!brief && !oomOnly)) {
+ if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
Debug.getMemoryInfo(pid, mi);
} else {
mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
mi.dalvikPrivateDirty = (int)tmpLong[0];
}
- ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
- if (isCheckinRequest) {
+ ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+ opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
+ if (opts.isCheckinRequest) {
pw.println();
}
}
return;
}
}
- pw.println("No process found for: " + args[opti]);
+ pw.println("No process found for: " + innerArgs[0]);
return;
}
- if (!brief && !oomOnly && (procs.size() == 1 || isCheckinRequest || packages)) {
- dumpDetails = true;
+ if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
+ opts.dumpDetails = true;
}
- dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest, isCompact);
-
- String[] innerArgs = new String[args.length-opti];
- System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+ dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact);
ArrayList<MemItem> procMems = new ArrayList<MemItem>();
final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
@@ -17186,9 +17198,9 @@ public class ActivityManagerService extends IActivityManager.Stub
long nativeSwapPss = 0;
long dalvikPss = 0;
long dalvikSwapPss = 0;
- long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
EmptyArray.LONG;
- long[] dalvikSubitemSwapPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
EmptyArray.LONG;
long otherPss = 0;
long otherSwapPss = 0;
@@ -17220,24 +17232,24 @@ public class ActivityManagerService extends IActivityManager.Stub
hasActivities = r.activities.size() > 0;
}
if (thread != null) {
- if (!isCheckinRequest && dumpDetails) {
+ if (!opts.isCheckinRequest && opts.dumpDetails) {
pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
}
if (mi == null) {
mi = new Debug.MemoryInfo();
}
- if (dumpDetails || (!brief && !oomOnly)) {
+ if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
Debug.getMemoryInfo(pid, mi);
hasSwapPss = mi.hasSwappedOutPss;
} else {
mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
mi.dalvikPrivateDirty = (int)tmpLong[0];
}
- if (dumpDetails) {
- if (localOnly) {
- ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
- if (isCheckinRequest) {
+ if (opts.dumpDetails) {
+ if (opts.localOnly) {
+ ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+ opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
+ if (opts.isCheckinRequest) {
pw.println();
}
} else {
@@ -17246,19 +17258,19 @@ public class ActivityManagerService extends IActivityManager.Stub
TransferPipe tp = new TransferPipe();
try {
thread.dumpMemInfo(tp.getWriteFd(),
- mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, dumpSummaryOnly, dumpUnreachable, innerArgs);
+ mi, opts.isCheckinRequest, opts.dumpFullDetails,
+ opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
tp.go(fd);
} finally {
tp.kill();
}
} catch (IOException e) {
- if (!isCheckinRequest) {
+ if (!opts.isCheckinRequest) {
pw.println("Got IoException! " + e);
pw.flush();
}
} catch (RemoteException e) {
- if (!isCheckinRequest) {
+ if (!opts.isCheckinRequest) {
pw.println("Got RemoteException! " + e);
pw.flush();
}
@@ -17277,7 +17289,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- if (!isCheckinRequest && mi != null) {
+ if (!opts.isCheckinRequest && mi != null) {
totalPss += myTotalPss;
totalSwapPss += myTotalSwapPss;
MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
@@ -17330,7 +17342,7 @@ public class ActivityManagerService extends IActivityManager.Stub
long nativeProcTotalPss = 0;
- if (!isCheckinRequest && procs.size() > 1 && !packages) {
+ if (!opts.isCheckinRequest && procs.size() > 1 && !opts.packages) {
// If we are showing aggregations, also look for native processes to
// include so that our aggregations are more accurate.
updateCpuStatsNow();
@@ -17343,7 +17355,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mi == null) {
mi = new Debug.MemoryInfo();
}
- if (!brief && !oomOnly) {
+ if (!brief && !opts.oomOnly) {
Debug.getMemoryInfo(st.pid, mi);
} else {
mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
@@ -17430,7 +17442,7 @@ public class ActivityManagerService extends IActivityManager.Stub
ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
for (int j=0; j<oomPss.length; j++) {
if (oomPss[j] != 0) {
- String label = isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
+ String label = opts.isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
: DUMP_MEM_OOM_LABEL[j];
MemItem item = new MemItem(label, label, oomPss[j], oomSwapPss[j],
DUMP_MEM_OOM_ADJ[j]);
@@ -17439,26 +17451,26 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- dumpSwapPss = dumpSwapPss && hasSwapPss && totalSwapPss != 0;
- if (!brief && !oomOnly && !isCompact) {
+ opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0;
+ if (!brief && !opts.oomOnly && !opts.isCompact) {
pw.println();
pw.println("Total PSS by process:");
- dumpMemItems(pw, " ", "proc", procMems, true, isCompact, dumpSwapPss);
+ dumpMemItems(pw, " ", "proc", procMems, true, opts.isCompact, opts.dumpSwapPss);
pw.println();
}
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.println("Total PSS by OOM adjustment:");
}
- dumpMemItems(pw, " ", "oom", oomMems, false, isCompact, dumpSwapPss);
- if (!brief && !oomOnly) {
+ dumpMemItems(pw, " ", "oom", oomMems, false, opts.isCompact, opts.dumpSwapPss);
+ if (!brief && !opts.oomOnly) {
PrintWriter out = categoryPw != null ? categoryPw : pw;
- if (!isCompact) {
+ if (!opts.isCompact) {
out.println();
out.println("Total PSS by category:");
}
- dumpMemItems(out, " ", "cat", catMems, true, isCompact, dumpSwapPss);
+ dumpMemItems(out, " ", "cat", catMems, true, opts.isCompact, opts.dumpSwapPss);
}
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.println();
}
MemInfoReader memInfo = new MemInfoReader();
@@ -17476,7 +17488,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
if (!brief) {
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.print("Total RAM: "); pw.print(stringifyKBSize(memInfo.getTotalSizeKb()));
pw.print(" (status ");
switch (mLastMemoryLevel) {
@@ -17517,7 +17529,7 @@ public class ActivityManagerService extends IActivityManager.Stub
long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.print(" Used RAM: "); pw.print(stringifyKBSize(totalPss - cachedPss
+ memInfo.getKernelUsedSizeKb())); pw.print(" (");
pw.print(stringifyKBSize(totalPss - cachedPss)); pw.print(" used pss + ");
@@ -17528,7 +17540,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (!brief) {
if (memInfo.getZramTotalSizeKb() != 0) {
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.print(" ZRAM: ");
pw.print(stringifyKBSize(memInfo.getZramTotalSizeKb()));
pw.print(" physical used for ");
@@ -17544,7 +17556,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
final long[] ksm = getKsmInfo();
- if (!isCompact) {
+ if (!opts.isCompact) {
if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
|| ksm[KSM_VOLATILE] != 0) {
pw.print(" KSM: "); pw.print(stringifyKBSize(ksm[KSM_SHARING]));
@@ -17593,6 +17605,17 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw,
+ MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+ ArrayList<ProcessRecord> procs) {
+ ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ // TODO: implement
+ pw.println("Not yet implemented. Have a cookie instead! :]");
+
+ proto.flush();
+ }
+
private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
long memtrack, String name) {
sb.append(" ");
@@ -18816,7 +18839,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
- null, -1, -1, false, null, null, AppOpsManager.OP_NONE, null, receivers,
+ null, -1, -1, false, null, null, OP_NONE, null, receivers,
null, 0, null, null, false, true, true, -1);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
@@ -19812,7 +19835,7 @@ public class ActivityManagerService extends IActivityManager.Stub
: new String[] {requiredPermission};
int res = broadcastIntentLocked(null, packageName, intent, resolvedType,
resultTo, resultCode, resultData, resultExtras,
- requiredPermissions, AppOpsManager.OP_NONE, bOptions, serialized,
+ requiredPermissions, OP_NONE, bOptions, serialized,
sticky, -1, uid, userId);
Binder.restoreCallingIdentity(origId);
return res;
@@ -20435,7 +20458,7 @@ public class ActivityManagerService extends IActivityManager.Stub
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
@@ -20446,7 +20469,7 @@ public class ActivityManagerService extends IActivityManager.Stub
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
UserHandle.USER_ALL);
}
@@ -20691,9 +20714,10 @@ public class ActivityManagerService extends IActivityManager.Stub
// the current [or imminent] receiver on.
private boolean isReceivingBroadcastLocked(ProcessRecord app,
ArraySet<BroadcastQueue> receivingQueues) {
- if (!app.curReceivers.isEmpty()) {
- for (BroadcastRecord r : app.curReceivers) {
- receivingQueues.add(r.queue);
+ final int N = app.curReceivers.size();
+ if (N > 0) {
+ for (int i = 0; i < N; i++) {
+ receivingQueues.add(app.curReceivers.valueAt(i).queue);
}
return true;
}
@@ -23857,7 +23881,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) {
synchronized (ActivityManagerService.this) {
- mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(
+ mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
reasons, timestamp);
}
}
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index f9422656..7eb922c9 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -373,7 +373,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
if (mProfileFile != null || mAgent != null) {
ParcelFileDescriptor fd = null;
if (mProfileFile != null) {
- fd = openOutputFileForSystem(mProfileFile);
+ fd = openFileForSystem(mProfileFile, "w");
if (fd == null) {
return 1;
}
@@ -668,7 +668,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
File file = new File(filename);
file.delete();
- ParcelFileDescriptor fd = openOutputFileForSystem(filename);
+ ParcelFileDescriptor fd = openFileForSystem(filename, "w");
if (fd == null) {
return -1;
}
@@ -756,7 +756,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
if (start) {
profileFile = getNextArgRequired();
- fd = openOutputFileForSystem(profileFile);
+ fd = openFileForSystem(profileFile, "w");
if (fd == null) {
return -1;
}
@@ -820,7 +820,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
File file = new File(heapFile);
file.delete();
- ParcelFileDescriptor fd = openOutputFileForSystem(heapFile);
+ ParcelFileDescriptor fd = openFileForSystem(heapFile, "w");
if (fd == null) {
return -1;
}
@@ -2476,7 +2476,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" -e <NAME> <VALUE>: set argument <NAME> to <VALUE>. For test runners a");
pw.println(" common form is [-e <testrunner_flag> <value>[,<value>...]].");
pw.println(" -p <FILE>: write profiling data to <FILE>");
- pw.println(" -m: Write output as protobuf (machine readable)");
+ pw.println(" -m: Write output as protobuf to stdout (machine readable)");
+ pw.println(" -f <Optional PATH/TO/FILE>: Write output as protobuf to a file (machine");
+ pw.println(" readable). If path is not specified, default directory and file name will");
+ pw.println(" be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto");
pw.println(" -w: wait for instrumentation to finish before returning. Required for");
pw.println(" test runners.");
pw.println(" --user <USER_ID> | current: Specify user instrumentation runs in;");
diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java
index 93c0f772..eb022b78 100644
--- a/com/android/server/am/ActivityMetricsLogger.java
+++ b/com/android/server/am/ActivityMetricsLogger.java
@@ -13,6 +13,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_BIND_APPLICATION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CANCELLED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -28,16 +29,22 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.content.Context;
import android.metrics.LogMaker;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.SystemClock;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
@@ -58,6 +65,8 @@ class ActivityMetricsLogger {
private static final long INVALID_START_TIME = -1;
+ private static final int MSG_CHECK_VISIBILITY = 0;
+
// Preallocated strings we are sending to tron, so we don't have to allocate a new one every
// time we log.
private static final String[] TRON_WINDOW_STATE_VARZ_STRINGS = {
@@ -78,6 +87,23 @@ class ActivityMetricsLogger {
private final SparseArray<StackTransitionInfo> mStackTransitionInfo = new SparseArray<>();
private final SparseArray<StackTransitionInfo> mLastStackTransitionInfo = new SparseArray<>();
+ private final H mHandler;
+ private final class H extends Handler {
+
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CHECK_VISIBILITY:
+ final SomeArgs args = (SomeArgs) msg.obj;
+ checkVisibility((TaskRecord) args.arg1, (ActivityRecord) args.arg2);
+ break;
+ }
+ }
+ };
private final class StackTransitionInfo {
private ActivityRecord launchedActivity;
@@ -91,10 +117,11 @@ class ActivityMetricsLogger {
private boolean loggedStartingWindowDrawn;
}
- ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context) {
+ ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context, Looper looper) {
mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000;
mSupervisor = supervisor;
mContext = context;
+ mHandler = new H(looper);
}
void logWindowState() {
@@ -145,6 +172,7 @@ class ActivityMetricsLogger {
*/
void notifyActivityLaunching() {
if (!isAnyTransitionActive()) {
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunching");
mCurrentTransitionStartTime = SystemClock.uptimeMillis();
mLastTransitionStartTime = mCurrentTransitionStartTime;
}
@@ -202,6 +230,12 @@ class ActivityMetricsLogger {
private void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity,
boolean processRunning, boolean processSwitch) {
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched"
+ + " resultCode=" + resultCode
+ + " launchedActivity=" + launchedActivity
+ + " processRunning=" + processRunning
+ + " processSwitch=" + processSwitch);
+
// If we are already in an existing transition, only update the activity name, but not the
// other attributes.
final int stackId = launchedActivity != null && launchedActivity.getStack() != null
@@ -230,6 +264,8 @@ class ActivityMetricsLogger {
return;
}
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched successful");
+
final StackTransitionInfo newInfo = new StackTransitionInfo();
newInfo.launchedActivity = launchedActivity;
newInfo.currentTransitionProcessRunning = processRunning;
@@ -243,6 +279,8 @@ class ActivityMetricsLogger {
* Notifies the tracker that all windows of the app have been drawn.
*/
void notifyWindowsDrawn(int stackId, long timestamp) {
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn stackId=" + stackId);
+
final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
if (info == null || info.loggedWindowsDrawn) {
return;
@@ -276,6 +314,7 @@ class ActivityMetricsLogger {
if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
return;
}
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
mCurrentTransitionDelayMs = calculateDelay(timestamp);
mLoggedTransitionStarting = true;
for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
@@ -295,17 +334,37 @@ class ActivityMetricsLogger {
* Notifies the tracker that the visibility of an app is changing.
*
* @param activityRecord the app that is changing its visibility
- * @param visible whether it's going to be visible or not
*/
- void notifyVisibilityChanged(ActivityRecord activityRecord, boolean visible) {
+ void notifyVisibilityChanged(ActivityRecord activityRecord) {
final StackTransitionInfo info = mStackTransitionInfo.get(activityRecord.getStackId());
+ if (info == null) {
+ return;
+ }
+ if (info.launchedActivity != activityRecord) {
+ return;
+ }
+ final TaskRecord t = activityRecord.getTask();
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = t;
+ args.arg2 = activityRecord;
+ mHandler.obtainMessage(MSG_CHECK_VISIBILITY, args).sendToTarget();
+ }
- // If we have an active transition that's waiting on a certain activity that will be
- // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
- if (info != null && !visible && info.launchedActivity == activityRecord) {
- mStackTransitionInfo.remove(activityRecord.getStackId());
- if (mStackTransitionInfo.size() == 0) {
- reset(true /* abort */);
+ private void checkVisibility(TaskRecord t, ActivityRecord r) {
+ synchronized (mSupervisor.mService) {
+
+ final StackTransitionInfo info = mStackTransitionInfo.get(r.getStackId());
+
+ // If we have an active transition that's waiting on a certain activity that will be
+ // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
+ if (info != null && !t.isVisible()) {
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible"
+ + " activity=" + r);
+ logAppTransitionCancel(info);
+ mStackTransitionInfo.remove(r.getStackId());
+ if (mStackTransitionInfo.size() == 0) {
+ reset(true /* abort */);
+ }
}
}
}
@@ -341,6 +400,7 @@ class ActivityMetricsLogger {
}
private void reset(boolean abort) {
+ if (DEBUG_METRICS) Slog.i(TAG, "reset abort=" + abort);
if (!abort && isAnyTransitionActive()) {
logAppTransitionMultiEvents();
}
@@ -361,7 +421,20 @@ class ActivityMetricsLogger {
return (int) (timestamp - mCurrentTransitionStartTime);
}
+ private void logAppTransitionCancel(StackTransitionInfo info) {
+ final int type = getTransitionType(info);
+ if (type == -1) {
+ return;
+ }
+ final LogMaker builder = new LogMaker(APP_TRANSITION_CANCELLED);
+ builder.setPackageName(info.launchedActivity.packageName);
+ builder.setType(type);
+ builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
+ mMetricsLogger.write(builder);
+ }
+
private void logAppTransitionMultiEvents() {
+ if (DEBUG_METRICS) Slog.i(TAG, "logging transition events");
for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
final int type = getTransitionType(info);
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 2f0b6491..9a16745c 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -77,7 +77,6 @@ import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
-import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -202,6 +201,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
private static final String TAG_STATES = TAG + POSTFIX_STATES;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
+ // TODO(b/67864419): Remove once recents component is overridden
+ private static final String LEGACY_RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
private static final boolean SHOW_ACTIVITY_START_TIME = true;
@@ -886,6 +887,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
Entry ent = AttributeCache.instance().get(packageName,
realTheme, com.android.internal.R.styleable.Window, userId);
+
if (ent != null) {
fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
@@ -1057,7 +1059,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
// We only allow home activities to be resizeable if they explicitly requested it.
info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
}
- } else if (service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
+ } else if (realActivity.getClassName().contains(LEGACY_RECENTS_PACKAGE_NAME) ||
+ service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
activityType = ACTIVITY_TYPE_RECENTS;
} else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
&& canLaunchAssistActivity(launchedFromPackage)) {
@@ -1526,7 +1529,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
void setVisibility(boolean visible) {
mWindowContainerController.setVisibility(visible, mDeferHidingClient);
- mStackSupervisor.mActivityMetricsLogger.notifyVisibilityChanged(this, visible);
+ mStackSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
}
// TODO: Look into merging with #setVisibility()
@@ -1807,7 +1810,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
stack.mFullyDrawnStartTime = 0;
}
- mStackSupervisor.mActivityMetricsLogger.logAppTransitionReportedDrawn(this,
+ mStackSupervisor.getActivityMetricsLogger().logAppTransitionReportedDrawn(this,
restoredFromBundle);
fullyDrawnStartTime = 0;
}
@@ -1849,7 +1852,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
@Override
public void onStartingWindowDrawn(long timestamp) {
synchronized (service) {
- mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(
+ mStackSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(
getStackId(), timestamp);
}
}
@@ -1857,7 +1860,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
@Override
public void onWindowsDrawn(long timestamp) {
synchronized (service) {
- mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId(), timestamp);
+ mStackSupervisor.getActivityMetricsLogger().notifyWindowsDrawn(getStackId(), timestamp);
if (displayStartTime != 0) {
reportLaunchTimeLocked(timestamp);
}
@@ -2119,11 +2122,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
void setRequestedOrientation(int requestedOrientation) {
- if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen
- && appInfo.targetSdkVersion >= O_MR1) {
- throw new IllegalStateException("Only fullscreen activities can request orientation");
- }
-
final int displayId = getDisplayId();
final Configuration displayConfig =
mStackSupervisor.getDisplayOverrideConfiguration(displayId);
@@ -2184,26 +2182,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
mTmpConfig.unset();
computeBounds(mTmpBounds);
if (mTmpBounds.equals(mBounds)) {
- final ActivityStack stack = getStack();
- if (!mBounds.isEmpty() || task == null || stack == null || !task.mFullscreen) {
- // We don't want to influence the override configuration here if our task is in
- // multi-window mode or there is a bounds specified to calculate the override
- // config. In both of this cases the app should be compatible with whatever the
- // current configuration is or will be.
- return;
- }
-
- // Currently limited to the top activity for now to avoid situations where non-top
- // visible activity and top might have conflicting requests putting the non-top activity
- // windows in an odd state.
- final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
- final Configuration parentConfig = getParent().getConfiguration();
- if (top != this || isConfigurationCompatible(parentConfig)) {
- onOverrideConfigurationChanged(mTmpConfig);
- } else if (isConfigurationCompatible(
- mLastReportedConfiguration.getMergedConfiguration())) {
- onOverrideConfigurationChanged(mLastReportedConfiguration.getMergedConfiguration());
- }
return;
}
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index ba41bd4c..c086c529 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,10 +16,14 @@
package com.android.server.am;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -94,7 +98,6 @@ import static java.lang.Integer.MAX_VALUE;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityController;
@@ -344,6 +347,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
private final Rect mTmpRect2 = new Rect();
+ private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
/** Run all ActivityStacks through this */
protected final ActivityStackSupervisor mStackSupervisor;
@@ -451,8 +455,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mStackId = stackId;
mCurrentUser = mService.mUserController.getCurrentUserId();
mTmpRect2.setEmpty();
- setWindowingMode(windowingMode);
setActivityType(activityType);
+ setWindowingMode(windowingMode);
mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
mTmpRect2);
postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -478,6 +482,125 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
@Override
+ public void setWindowingMode(int windowingMode) {
+ setWindowingMode(windowingMode, false /* animate */);
+ }
+
+ void setWindowingMode(int preferredWindowingMode, boolean animate) {
+ final int currentMode = getWindowingMode();
+ final ActivityDisplay display = getDisplay();
+ final TaskRecord topTask = topTask();
+ final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
+ mTmpOptions.setLaunchWindowingMode(preferredWindowingMode);
+
+ // Need to make sure windowing mode is supported.
+ int windowingMode = display.resolveWindowingMode(
+ 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;
+ }
+
+ // Take any required action due to us not supporting the preferred windowing mode.
+ if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
+ if (display.hasSplitScreenPrimaryStack()
+ && (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
+ // and display a warning toast about it.
+ mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+ display.getSplitScreenPrimaryStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ }
+ }
+
+ if (currentMode == windowingMode) {
+ // You are already in the window mode silly...
+ return;
+ }
+
+ final WindowManagerService wm = mService.mWindowManager;
+ final ActivityRecord topActivity = getTopActivity();
+
+ if (windowingMode != WINDOWING_MODE_FULLSCREEN && topActivity != null
+ && topActivity.isNonResizableOrForcedResizable() && !topActivity.noDisplay) {
+ // Inform the user that they are starting an app that may not work correctly in
+ // multi-window mode.
+ final String packageName = topActivity.appInfo.packageName;
+ mService.mTaskChangeNotificationController.notifyActivityForcedResizable(
+ topTask.taskId, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN, packageName);
+ }
+
+ wm.deferSurfaceLayout();
+ try {
+ if (!animate && topActivity != null) {
+ mNoAnimActivities.add(topActivity);
+ }
+ super.setWindowingMode(windowingMode);
+
+ if (mWindowContainerController == null) {
+ // Nothing else to do if we don't have a window container yet. E.g. call from ctor.
+ return;
+ }
+
+ if (windowingMode == WINDOWING_MODE_PINNED || currentMode == WINDOWING_MODE_PINNED) {
+ // TODO: Need to remove use of PinnedActivityStack for this to be supported.
+ // NOTE: Need to ASS.scheduleUpdatePictureInPictureModeIfNeeded() in
+ // setWindowModeUnchecked() when this support is added. See TaskRecord.reparent()
+ throw new IllegalArgumentException(
+ "Changing pinned windowing mode not currently supported");
+ }
+
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && splitScreenStack != null) {
+ // We already have a split-screen stack in this display, so just move the tasks over.
+ // TODO: Figure-out how to do all the stuff in
+ // AMS.setTaskWindowingModeSplitScreenPrimary
+ throw new IllegalArgumentException("Setting primary split-screen windowing mode"
+ + " while there is already one isn't currently supported");
+ //return;
+ }
+
+ mTmpRect2.setEmpty();
+ if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
+ mWindowContainerController.getRawBounds(mTmpRect2);
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ if (topTask != null) {
+ // TODO: Can we consolidate this and other sites that call this methods?
+ Rect bounds = topTask().getLaunchBounds();
+ if (bounds != null) {
+ mTmpRect2.set(bounds);
+ }
+ }
+ }
+ }
+
+ if (!Objects.equals(mBounds, mTmpRect2)) {
+ resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+ }
+ } finally {
+ if (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
+ // on that.
+ // TODO: This is only here to help out with the case where recents stack doesn't
+ // exist yet. For that case the initial size of the split-screen stack will be the
+ // the one where the home stack is visible since recents isn't visible yet, but the
+ // divider will be off. I think we should just make the initial bounds that of home
+ // so that the divider matches and remove this logic.
+ display.getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ ACTIVITY_TYPE_RECENTS, true /* onTop */);
+ // If task moved to docked stack - show recents if needed.
+ mService.mWindowManager.showRecentApps(false /* fromHome */);
+ }
+ wm.continueSurfaceLayout();
+ }
+
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+ mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
+
+ @Override
public boolean isCompatible(int windowingMode, int activityType) {
// TODO: Should we just move this to ConfigurationContainer?
if (activityType == ACTIVITY_TYPE_UNDEFINED) {
@@ -537,12 +660,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
* either destroyed completely or re-parented.
*/
private void removeFromDisplay() {
- if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- // If we removed a docked stack we want to resize it so it resizes all other stacks
- // in the system to fullscreen.
- mStackSupervisor.resizeDockedStackLocked(
- null, null, null, null, null, PRESERVE_WINDOWS);
- }
final ActivityDisplay display = getDisplay();
if (display != null) {
display.removeChild(this);
@@ -729,14 +846,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return null;
}
- final ActivityRecord topActivity() {
+ ActivityRecord getTopActivity() {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
- if (!r.finishing) {
- return r;
- }
+ final ActivityRecord r = mTaskHistory.get(taskNdx).getTopActivity();
+ if (r != null) {
+ return r;
}
}
return null;
@@ -750,7 +864,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return null;
}
- final TaskRecord bottomTask() {
+ private TaskRecord bottomTask() {
if (mTaskHistory.isEmpty()) {
return null;
}
@@ -870,6 +984,29 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
}
+ /**
+ * @param reason The reason for moving the stack to the back.
+ * @param task If non-null, the task will be moved to the bottom of the stack.
+ **/
+ void moveToBack(String reason, TaskRecord task) {
+ if (!isAttached()) {
+ return;
+ }
+
+ getDisplay().positionChildAtBottom(this);
+ mStackSupervisor.setFocusStackUnchecked(reason, getDisplay().getTopStack());
+ if (task != null) {
+ insertTaskAtBottom(task);
+ return;
+ } else {
+ task = bottomTask();
+ if (task != null) {
+ mWindowContainerController.positionChildAtBottom(
+ task.getWindowContainerController(), true /* includingParents */);
+ }
+ }
+ }
+
boolean isFocusable() {
if (getWindowConfiguration().canReceiveKeys()) {
return true;
@@ -1583,8 +1720,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
final int otherWindowingMode = other.getWindowingMode();
- // TODO: Can be removed once we are no longer using returnToType for back functionality
- final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
if (other.isStackTranslucent(starting)) {
@@ -1645,7 +1780,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
boolean preserveWindows) {
mTopActivityOccludesKeyguard = false;
mTopDismissingKeyguardActivity = null;
- mStackSupervisor.mKeyguardController.beginActivityVisibilityUpdate();
+ mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
try {
ActivityRecord top = topRunningActivityLocked();
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + top
@@ -1753,7 +1888,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
notifyActivityDrawnLocked(null);
}
} finally {
- mStackSupervisor.mKeyguardController.endActivityVisibilityUpdate();
+ mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
}
}
@@ -1778,6 +1913,22 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return inPinnedWindowingMode();
}
+ @Override
+ public boolean supportsSplitScreenWindowingMode() {
+ final TaskRecord topTask = topTask();
+ return super.supportsSplitScreenWindowingMode()
+ && (topTask == null || topTask.supportsSplitScreenWindowingMode());
+ }
+
+ /** @return True if the resizing of the primary-split-screen stack affects this stack size. */
+ boolean affectedBySplitScreenResize() {
+ if (!supportsSplitScreenWindowingMode()) {
+ return false;
+ }
+ final int windowingMode = getWindowingMode();
+ return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+ }
+
/**
* @return the top most visible activity that wants to dismiss Keyguard
*/
@@ -1795,9 +1946,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible,
boolean isTop) {
final boolean isInPinnedStack = r.inPinnedWindowingMode();
- final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
+ final boolean keyguardShowing = mStackSupervisor.getKeyguardController().isKeyguardShowing(
mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
- final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
+ final boolean keyguardLocked = mStackSupervisor.getKeyguardController().isKeyguardLocked();
final boolean showWhenLocked = r.canShowWhenLocked() && !isInPinnedStack;
final boolean dismissKeyguard = r.hasDismissKeyguardWindows();
if (shouldBeVisible) {
@@ -1812,7 +1963,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
final boolean canShowWithKeyguard = canShowWithInsecureKeyguard()
- && mStackSupervisor.mKeyguardController.canDismissKeyguard();
+ && mStackSupervisor.getKeyguardController().canDismissKeyguard();
if (canShowWithKeyguard) {
return true;
}
@@ -1821,10 +1972,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// If keyguard is showing, nothing is visible, except if we are able to dismiss Keyguard
// right away.
- return shouldBeVisible && mStackSupervisor.mKeyguardController
+ return shouldBeVisible && mStackSupervisor.getKeyguardController()
.canShowActivityWhileKeyguardShowing(r, dismissKeyguard);
} else if (keyguardLocked) {
- return shouldBeVisible && mStackSupervisor.mKeyguardController.canShowWhileOccluded(
+ return shouldBeVisible && mStackSupervisor.getKeyguardController().canShowWhileOccluded(
dismissKeyguard, showWhenLocked);
} else {
return shouldBeVisible;
@@ -2388,7 +2539,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// if needed to get the correct rotation behavior.
// TODO: Remove this once visibilities are set correctly immediately when
// starting an activity.
- if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
+ if (mStackSupervisor.getKeyguardController().isKeyguardLocked()) {
mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
0 /* configChanges */, false /* preserveWindows */);
}
@@ -2581,6 +2732,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (position >= mTaskHistory.size()) {
insertTaskAtTop(task, null);
return;
+ } else if (position <= 0) {
+ insertTaskAtBottom(task);
+ return;
}
position = getAdjustedPositionForTask(task, position, null /* starting */);
mTaskHistory.remove(task);
@@ -2601,6 +2755,16 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
true /* includingParents */);
}
+ private void insertTaskAtBottom(TaskRecord task) {
+ // Unlike insertTaskAtPosition, this will also position parents of the windowcontroller.
+ mTaskHistory.remove(task);
+ final int position = getAdjustedPositionForTask(task, 0, null);
+ mTaskHistory.add(position, task);
+ updateTaskMovement(task, true);
+ mWindowContainerController.positionChildAtBottom(task.getWindowContainerController(),
+ true /* includingParents */);
+ }
+
final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
TaskRecord rTask = r.getTask();
@@ -3474,7 +3638,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
if (endTask) {
- mService.mLockTaskController.removeLockedTask(task);
+ mService.mLockTaskController.clearLockedTask(task);
}
} else if (r.state != ActivityState.PAUSING) {
// If the activity is PAUSING, we will complete the finish once
@@ -4253,7 +4417,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
final ActivityStack topStack = getDisplay().getTopStack();
- final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null;
+ final ActivityRecord topActivity = topStack != null ? topStack.getTopActivity() : null;
final int numTasks = mTaskHistory.size();
final int index = mTaskHistory.indexOf(tr);
if (numTasks == 0 || index < 0) {
@@ -4334,8 +4498,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
Slog.i(TAG, "moveTaskToBack: " + tr);
- // If the task is locked, then show the lock task toast
- if (mService.mLockTaskController.checkLockedTask(tr)) {
+ // In LockTask mode, moving a locked task to the back of the stack may expose unlocked
+ // ones. Therefore we need to check if this operation is allowed.
+ if (!mService.mLockTaskController.canMoveTaskToBack(tr)) {
return false;
}
@@ -4369,8 +4534,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
updateTaskMovement(tr, false);
mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
- mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
- true /* includingParents */);
+ moveToBack("moveTaskToBackLocked", tr);
if (inPinnedWindowingMode()) {
mStackSupervisor.removeStack(this);
@@ -4456,6 +4620,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
final TaskRecord task = mTaskHistory.get(i);
if (task.isResizeable()) {
if (inFreeformWindowingMode()) {
+ // TODO: Can be removed now since each freeform task is in its own stack.
// 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.
@@ -4843,7 +5008,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
voiceInteractor);
// add the task to stack first, mTaskPositioner might need the stack association
addTask(task, toTop, "createTaskRecord");
- final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
+ 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) {
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index 6ec158ef..745e9fbc 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.ACTIVITY_EMBEDDING;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
@@ -83,6 +84,7 @@ import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
@@ -293,7 +295,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
WindowManagerService mWindowManager;
DisplayManager mDisplayManager;
- private final LaunchingBoundsController mLaunchingBoundsController;
+ private LaunchingBoundsController mLaunchingBoundsController;
/** Counter for next free stack ID to use for dynamic activity stacks. */
private int mNextFreeStackId = 0;
@@ -393,7 +395,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
private DisplayManagerInternal mDisplayManagerInternal;
- private InputManagerInternal mInputManagerInternal;
/** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
boolean inResumeTopActivity;
@@ -412,7 +413,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// Whether tasks have moved and we need to rank the tasks before next OOM scoring
private boolean mTaskLayersChanged = true;
- final ActivityMetricsLogger mActivityMetricsLogger;
+ private ActivityMetricsLogger mActivityMetricsLogger;
private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>();
@@ -532,11 +533,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
*/
boolean mIsDockMinimized;
- final KeyguardController mKeyguardController;
+ private KeyguardController mKeyguardController;
private PowerManager mPowerManager;
private int mDeferResumeCount;
+ private boolean mInitialized;
+
/**
* Description of a request to start a new activity, which has been held
* due to app switches being disabled.
@@ -572,14 +575,32 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
mService = service;
mHandler = new ActivityStackSupervisorHandler(looper);
+ }
+
+ public void initialize() {
+ if (mInitialized) {
+ return;
+ }
+
+ mInitialized = true;
mRunningTasks = createRunningTasks();
- mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
- mKeyguardController = new KeyguardController(service, this);
+ mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext,
+ mHandler.getLooper());
+ mKeyguardController = new KeyguardController(mService, this);
mLaunchingBoundsController = new LaunchingBoundsController();
mLaunchingBoundsController.registerDefaultPositioners(this);
}
+
+ public ActivityMetricsLogger getActivityMetricsLogger() {
+ return mActivityMetricsLogger;
+ }
+
+ public KeyguardController getKeyguardController() {
+ return mKeyguardController;
+ }
+
void setRecentTasks(RecentTasks recentTasks) {
mRecentTasks = recentTasks;
mRecentTasks.registerCallback(this);
@@ -622,8 +643,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mHomeStack = mFocusedStack = mLastFocusedStack = getDefaultDisplay().getOrCreateStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
-
- mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
}
}
@@ -1306,8 +1325,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mService.updateLruProcessLocked(app, true, null);
mService.updateOomAdjLocked();
- if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE ||
- task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) {
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+ || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
+ || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
+ && mService.mLockTaskController.getLockTaskModeState()
+ == LOCK_TASK_MODE_LOCKED)) {
mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
}
@@ -2436,7 +2458,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
mWindowManager.deferSurfaceLayout();
try {
- if (stack.supportsSplitScreenWindowingMode()) {
+ if (stack.affectedBySplitScreenResize()) {
if (bounds == null && stack.inSplitScreenWindowingMode()) {
// null bounds = fullscreen windowing mode...at least for now.
stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -2536,8 +2558,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
null, mTmpOptions, task, task.getActivityType(), onTop);
if (onTop) {
- final int returnToType =
- toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
final boolean isTopTask = i == (size - 1);
// Defer resume until all the tasks have been moved to the fullscreen stack
task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
@@ -2626,7 +2646,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
continue;
}
- if (!current.supportsSplitScreenWindowingMode()) {
+ if (!current.affectedBySplitScreenResize()) {
continue;
}
// Need to set windowing mode here before we try to get the dock bounds.
@@ -2769,6 +2789,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
if (tr != null) {
tr.removeTaskActivitiesLocked(pauseImmediately);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
+ mService.mLockTaskController.clearLockedTask(tr);
if (tr.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
@@ -2941,6 +2962,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
* Returns the reparent target stack, creating the stack if necessary. This call also enforces
* the various checks on tasks that are going to be reparented from one stack to another.
*/
+ // TODO: Look into changing users to this method to ActivityDisplay.resolveWindowingMode()
ActivityStack getReparentTargetStack(TaskRecord task, ActivityStack stack, boolean toTop) {
final ActivityStack prevStack = task.getStack();
final int stackId = stack.mStackId;
@@ -2967,8 +2989,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
+ " reparent task=" + task + " to stackId=" + stackId);
}
- // Ensure that we aren't trying to move into a freeform stack without freeform
- // support
+ // Ensure that we aren't trying to move into a freeform stack without freeform support
if (stack.getWindowingMode() == WINDOWING_MODE_FREEFORM
&& !mService.mSupportsFreeformWindowManagement) {
throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
@@ -3372,7 +3393,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// When launching tasks behind, update the last active time of the top task after the new
// task has been shown briefly
- final ActivityRecord top = stack.topActivity();
+ final ActivityRecord top = stack.getTopActivity();
if (top != null) {
top.getTask().touchActiveTime();
}
@@ -4171,8 +4192,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
// Handle incorrect launch/move to secondary display if needed.
- final boolean launchOnSecondaryDisplayFailed;
if (isSecondaryDisplayPreferred) {
+ final boolean launchOnSecondaryDisplayFailed;
final int actualDisplayId = task.getStack().mDisplayId;
if (!task.canBeLaunchedOnDisplay(actualDisplayId)) {
// The task landed on an inappropriate display somehow, move it to the default
@@ -4187,34 +4208,34 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
|| (preferredDisplayId != INVALID_DISPLAY
&& preferredDisplayId != actualDisplayId);
}
- } else {
- // The task wasn't requested to be on a secondary display.
- launchOnSecondaryDisplayFailed = false;
- }
-
- final ActivityRecord topActivity = task.getTopActivity();
- if (launchOnSecondaryDisplayFailed
- || !task.supportsSplitScreenWindowingMode() || forceNonResizable) {
if (launchOnSecondaryDisplayFailed) {
// Display a warning toast that we tried to put a non-resizeable task on a secondary
// display with config different from global config.
mService.mTaskChangeNotificationController
.notifyActivityLaunchOnSecondaryDisplayFailed();
- } else {
- // Display a warning toast that we tried to put a non-dockable task in the docked
- // stack.
- mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+ return;
}
+ }
+
+ if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
+ // Display a warning toast that we tried to put an app that doesn't support split-screen
+ // in split-screen.
+ mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
// Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
// we need to move it to top of fullscreen stack, otherwise it will be covered.
- final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenPrimaryStack();
+ final ActivityStack dockedStack =
+ task.getStack().getDisplay().getSplitScreenPrimaryStack();
if (dockedStack != null) {
moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
}
- } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
- && !topActivity.noDisplay) {
+ return;
+ }
+
+ final ActivityRecord topActivity = task.getTopActivity();
+ if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
+ && !topActivity.noDisplay) {
final String packageName = topActivity.appInfo.packageName;
final int reason = isSecondaryDisplayPreferred
? FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY
@@ -4465,7 +4486,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
try {
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
mWindowManager.setDockedStackCreateState(
- activityOptions.getDockCreateMode(), null /* initialBounds */);
+ activityOptions.getSplitScreenCreateMode(), null /* initialBounds */);
// Defer updating the stack in which recents is until the app transition is done, to
// not run into issues where we still need to draw the task in recents but the
@@ -4483,9 +4504,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
"startActivityFromRecents: Task " + taskId + " not found.");
}
- // We always want to return to the home activity instead of the recents activity from
- // whatever is started from the recents activity, so move the home stack forward.
- moveHomeStackToFront("startActivityFromRecents");
+ if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // We always want to return to the home activity instead of the recents activity
+ // from whatever is started from the recents activity, so move the home stack
+ // forward.
+ moveHomeStackToFront("startActivityFromRecents");
+ }
// If the user must confirm credentials (e.g. when first launching a work app and the
// Work Challenge is present) let startActivityInPackage handle the intercepting.
@@ -4496,9 +4520,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */,
targetActivity);
mActivityMetricsLogger.notifyActivityLaunching();
- mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */);
- mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
- targetActivity);
+ try {
+ mService.moveTaskToFrontLocked(task.taskId, 0, bOptions,
+ true /* fromRecents */);
+ } finally {
+ mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
+ targetActivity);
+ }
// If we are launching the task in the docked stack, put it into resizing mode so
// the window renders full-screen with the background filling the void. Also only
@@ -4541,7 +4569,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
final ActivityStack stack = display.getChildAt(j);
// Get top activity from a visible stack and add it to the list.
if (stack.shouldBeVisible(null /* starting */)) {
- final ActivityRecord top = stack.topActivity();
+ final ActivityRecord top = stack.getTopActivity();
if (top != null) {
if (stack == mFocusedStack) {
topActivityTokens.add(0, top.appToken);
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 1c802827..9b8cbc10 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -90,6 +90,7 @@ import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
@@ -133,6 +134,7 @@ 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;
@@ -232,8 +234,9 @@ class ActivityStarter {
mIntentDelivered = false;
}
- ActivityStarter(ActivityManagerService service) {
+ ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
mService = service;
+ mPackageManager = packageManager;
mSupervisor = mService.mStackSupervisor;
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
}
@@ -264,8 +267,12 @@ class ActivityStarter {
outActivity[0] = mLastStartActivityRecord[0];
}
+ return getExternalResult(mLastStartActivityResult);
+ }
+
+ public static int getExternalResult(int result) {
// Aborted results are treated as successes externally, but we must track them internally.
- return mLastStartActivityResult != START_ABORTED ? mLastStartActivityResult : START_SUCCESS;
+ return result != START_ABORTED ? result : START_SUCCESS;
}
/** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */
@@ -295,7 +302,8 @@ class ActivityStarter {
}
}
- final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+ final int userId = aInfo != null && aInfo.applicationInfo != null
+ ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
if (err == ActivityManager.START_SUCCESS) {
Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
@@ -371,7 +379,7 @@ class ActivityStarter {
&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
try {
intent.addCategory(Intent.CATEGORY_VOICE);
- if (!AppGlobals.getPackageManager().activitySupportsIntent(
+ if (!mPackageManager.activitySupportsIntent(
intent.getComponent(), intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in current voice task does not support voice: "
@@ -389,7 +397,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 (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+ if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in new voice task does not support: "
@@ -665,7 +673,7 @@ class ActivityStarter {
if (intent != null && intent.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- mSupervisor.mActivityMetricsLogger.notifyActivityLaunching();
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunching();
boolean componentSpecified = intent.getComponent() != null;
// Save a copy in case ephemeral needs it
@@ -859,7 +867,7 @@ class ActivityStarter {
}
}
- mSupervisor.mActivityMetricsLogger.notifyActivityLaunched(res, outRecord[0]);
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outRecord[0]);
return res;
}
}
@@ -1119,7 +1127,7 @@ class ActivityStarter {
// If the activity being launched is the same as the one currently at the top, then
// we need to check if it should only be launched once.
final ActivityStack topStack = mSupervisor.mFocusedStack;
- final ActivityRecord topFocused = topStack.topActivity();
+ final ActivityRecord topFocused = topStack.getTopActivity();
final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
final boolean dontStart = top != null && mStartActivity.resultTo == null
&& top.realActivity.equals(mStartActivity.realActivity)
@@ -1565,8 +1573,8 @@ class ActivityStarter {
&& (topTask != intentActivity.getTask() || topTask != focusStack.topTask())
&& !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- if (mSourceRecord == null || (mSourceStack.topActivity() != null &&
- mSourceStack.topActivity().getTask() == mSourceRecord.getTask())) {
+ if (mSourceRecord == null || (mSourceStack.getTopActivity() != null &&
+ mSourceStack.getTopActivity().getTask() == mSourceRecord.getTask())) {
// We really do want to push this one into the user's face, right now.
if (mLaunchTaskBehind && mSourceRecord != null) {
intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
@@ -1956,7 +1964,7 @@ class ActivityStarter {
if (mDoResume) {
mTargetStack.moveToFront("addingToTopTask");
}
- final ActivityRecord prev = mTargetStack.topActivity();
+ final ActivityRecord prev = mTargetStack.getTopActivity();
final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord(
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
mIntent, null, null, true);
diff --git a/com/android/server/am/AssistDataReceiverProxy.java b/com/android/server/am/AssistDataReceiverProxy.java
new file mode 100644
index 00000000..9991ce17
--- /dev/null
+++ b/com/android/server/am/AssistDataReceiverProxy.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.IAssistDataReceiver;
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+/**
+ * Proxies assist data to the given receiver, skipping all callbacks if the receiver dies.
+ */
+class AssistDataReceiverProxy implements AssistDataRequesterCallbacks,
+ Binder.DeathRecipient {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "AssistDataReceiverProxy" : TAG_AM;
+
+ private String mCallerPackage;
+ private IAssistDataReceiver mReceiver;
+
+ public AssistDataReceiverProxy(IAssistDataReceiver receiver, String callerPackage) {
+ mReceiver = receiver;
+ mCallerPackage = callerPackage;
+ linkToDeath();
+ }
+
+ @Override
+ public boolean canHandleReceivedAssistDataLocked() {
+ // We are forwarding, so we can always receive this data
+ return true;
+ }
+
+ @Override
+ public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+ if (mReceiver != null) {
+ try {
+ mReceiver.onHandleAssistData(data);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to proxy assist data to receiver in package="
+ + mCallerPackage, e);
+ }
+ }
+ }
+
+ @Override
+ public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+ if (mReceiver != null) {
+ try {
+ mReceiver.onHandleAssistScreenshot(screenshot);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to proxy assist screenshot to receiver in package="
+ + mCallerPackage, e);
+ }
+ }
+ }
+
+ @Override
+ public void onAssistRequestCompleted() {
+ unlinkToDeath();
+ }
+
+ @Override
+ public void binderDied() {
+ unlinkToDeath();
+ }
+
+ private void linkToDeath() {
+ try {
+ mReceiver.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not link to client death", e);
+ }
+ }
+
+ private void unlinkToDeath() {
+ if (mReceiver != null) {
+ mReceiver.asBinder().unlinkToDeath(this, 0);
+ }
+ mReceiver = null;
+ }
+} \ No newline at end of file
diff --git a/com/android/server/am/AssistDataRequester.java b/com/android/server/am/AssistDataRequester.java
new file mode 100644
index 00000000..9f7621f2
--- /dev/null
+++ b/com/android/server/am/AssistDataRequester.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.ASSIST_CONTEXT_FULL;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_NONE;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.IAssistDataReceiver;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to asynchronously fetch the assist data and screenshot from the current running
+ * activities. It manages received data and calls back to the owner when the owner is ready to
+ * receive the data itself.
+ */
+public class AssistDataRequester extends IAssistDataReceiver.Stub {
+
+ public static final String KEY_RECEIVER_EXTRA_COUNT = "count";
+ public static final String KEY_RECEIVER_EXTRA_INDEX = "index";
+
+ private IActivityManager mService;
+ private IWindowManager mWindowManager;
+ private Context mContext;
+ private AppOpsManager mAppOpsManager;
+
+ private AssistDataRequesterCallbacks mCallbacks;
+ private Object mCallbacksLock;
+
+ private int mRequestStructureAppOps;
+ private int mRequestScreenshotAppOps;
+ private boolean mCanceled;
+ private int mPendingDataCount;
+ private int mPendingScreenshotCount;
+ private final ArrayList<Bundle> mAssistData = new ArrayList<>();
+ private final ArrayList<Bitmap> mAssistScreenshot = new ArrayList<>();
+
+
+ /**
+ * Interface to handle the events from the fetcher.
+ */
+ public interface AssistDataRequesterCallbacks {
+ /**
+ * @return whether the currently received assist data can be handled by the callbacks.
+ */
+ @GuardedBy("mCallbacksLock")
+ boolean canHandleReceivedAssistDataLocked();
+
+ /**
+ * Called when we receive asynchronous assist data. This call is only made if the
+ * {@param fetchData} argument to requestAssistData() is true, and if the current activity
+ * allows assist data to be fetched. In addition, the callback will be made with the
+ * {@param mCallbacksLock} held, and only if {@link #canHandleReceivedAssistDataLocked()}
+ * is true.
+ */
+ @GuardedBy("mCallbacksLock")
+ void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount);
+
+ /**
+ * Called when we receive asynchronous assist screenshot. This call is only made if
+ * {@param fetchScreenshot} argument to requestAssistData() is true, and if the current
+ * activity allows assist data to be fetched. In addition, the callback will be made with
+ * the {@param mCallbacksLock} held, and only if
+ * {@link #canHandleReceivedAssistDataLocked()} is true.
+ */
+ @GuardedBy("mCallbacksLock")
+ void onAssistScreenshotReceivedLocked(Bitmap screenshot);
+
+ /**
+ * Called when there is no more pending assist data or screenshots for the last request.
+ * If the request was canceled, then this callback will not be made. In addition, the
+ * callback will be made with the {@param mCallbacksLock} held, and only if
+ * {@link #canHandleReceivedAssistDataLocked()} is true.
+ */
+ @GuardedBy("mCallbacksLock")
+ default void onAssistRequestCompleted() {
+ // Do nothing
+ }
+ }
+
+ /**
+ * @param callbacks The callbacks to handle the asynchronous reply with the assist data.
+ * @param callbacksLock The lock for the requester to hold when calling any of the
+ * {@param callbacks}. The owner should also take care in locking
+ * appropriately when calling into this requester.
+ * @param requestStructureAppOps The app ops to check before requesting the assist structure
+ * @param requestScreenshotAppOps The app ops to check before requesting the assist screenshot.
+ * This can be {@link AppOpsManager#OP_NONE} to indicate that
+ * screenshots should never be fetched.
+ */
+ public AssistDataRequester(Context context, IActivityManager service,
+ IWindowManager windowManager, AppOpsManager appOpsManager,
+ AssistDataRequesterCallbacks callbacks, Object callbacksLock,
+ int requestStructureAppOps, int requestScreenshotAppOps) {
+ mCallbacks = callbacks;
+ mCallbacksLock = callbacksLock;
+ mWindowManager = windowManager;
+ mService = service;
+ mContext = context;
+ mAppOpsManager = appOpsManager;
+ mRequestStructureAppOps = requestStructureAppOps;
+ mRequestScreenshotAppOps = requestScreenshotAppOps;
+ }
+
+ /**
+ * Request that assist data be loaded asynchronously. The resulting data will be provided
+ * through the {@link AssistDataRequesterCallbacks}.
+ *
+ * @param activityTokens the list of visible activities
+ * @param fetchData whether or not to fetch the assist data, only applies if the caller is
+ * allowed to fetch the assist data, and the current activity allows assist data to be
+ * fetched from it
+ * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is
+ * true, the caller is allowed to fetch the assist data, and the current activity allows
+ * assist data to be fetched from it
+ * @param allowFetchData to be joined with other checks, determines whether or not the requester
+ * is allowed to fetch the assist data
+ * @param allowFetchScreenshot to be joined with other checks, determines whether or not the
+ * requester is allowed to fetch the assist screenshot
+ */
+ public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+ final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
+ int callingUid, String callingPackage) {
+ // TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
+ // then no assist data is requested for any of the other activities
+
+ // Early exit if there are no activity to fetch for
+ if (activityTokens.isEmpty()) {
+ // No activities, just dispatch request-complete
+ tryDispatchRequestComplete();
+ return;
+ }
+
+ // Ensure that the current activity supports assist data
+ boolean isAssistDataAllowed = false;
+ try {
+ isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity();
+ } catch (RemoteException e) {
+ // Should never happen
+ }
+ allowFetchData &= isAssistDataAllowed;
+ allowFetchScreenshot &= fetchData && isAssistDataAllowed
+ && (mRequestScreenshotAppOps != OP_NONE);
+
+ mCanceled = false;
+ mPendingDataCount = 0;
+ mPendingScreenshotCount = 0;
+ mAssistData.clear();
+ mAssistScreenshot.clear();
+
+ if (fetchData) {
+ if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage)
+ == MODE_ALLOWED && allowFetchData) {
+ final int numActivities = activityTokens.size();
+ for (int i = 0; i < numActivities; i++) {
+ IBinder topActivity = activityTokens.get(i);
+ try {
+ MetricsLogger.count(mContext, "assist_with_context", 1);
+ Bundle receiverExtras = new Bundle();
+ receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
+ receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities);
+ if (mService.requestAssistContextExtras(ASSIST_CONTEXT_FULL, this,
+ receiverExtras, topActivity, /* focused= */ i == 0,
+ /* newSessionId= */ i == 0)) {
+ mPendingDataCount++;
+ } else if (i == 0) {
+ // Wasn't allowed... given that, let's not do the screenshot either.
+ if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+ dispatchAssistDataReceived(null);
+ } else {
+ mAssistData.add(null);
+ }
+ allowFetchScreenshot = false;
+ break;
+ }
+ } catch (RemoteException e) {
+ // Can't happen
+ }
+ }
+ } else {
+ // Wasn't allowed... given that, let's not do the screenshot either.
+ if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+ dispatchAssistDataReceived(null);
+ } else {
+ mAssistData.add(null);
+ }
+ allowFetchScreenshot = false;
+ }
+ }
+
+ if (fetchScreenshot) {
+ if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
+ == MODE_ALLOWED && allowFetchScreenshot) {
+ try {
+ MetricsLogger.count(mContext, "assist_with_screen", 1);
+ mPendingScreenshotCount++;
+ mWindowManager.requestAssistScreenshot(this);
+ } catch (RemoteException e) {
+ // Can't happen
+ }
+ } else {
+ if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+ dispatchAssistScreenshotReceived(null);
+ } else {
+ mAssistScreenshot.add(null);
+ }
+ }
+ }
+ // For the cases where we dispatch null data/screenshot due to permissions, just dispatch
+ // request-complete after those are made
+ tryDispatchRequestComplete();
+ }
+
+ /**
+ * This call should only be made when the callbacks are capable of handling the received assist
+ * data. The owner is also responsible for locking before calling this method.
+ */
+ public void processPendingAssistData() {
+ flushPendingAssistData();
+ tryDispatchRequestComplete();
+ }
+
+ private void flushPendingAssistData() {
+ final int dataCount = mAssistData.size();
+ for (int i = 0; i < dataCount; i++) {
+ dispatchAssistDataReceived(mAssistData.get(i));
+ }
+ mAssistData.clear();
+ final int screenshotsCount = mAssistScreenshot.size();
+ for (int i = 0; i < screenshotsCount; i++) {
+ dispatchAssistScreenshotReceived(mAssistScreenshot.get(i));
+ }
+ mAssistScreenshot.clear();
+ }
+
+ public int getPendingDataCount() {
+ return mPendingDataCount;
+ }
+
+ public int getPendingScreenshotCount() {
+ return mPendingScreenshotCount;
+ }
+
+ /**
+ * Cancels the current request for the assist data.
+ */
+ public void cancel() {
+ // Reset the pending data count, if we receive new assist data after this point, it will
+ // be ignored
+ mCanceled = true;
+ mPendingDataCount = 0;
+ mPendingScreenshotCount = 0;
+ mAssistData.clear();
+ mAssistScreenshot.clear();
+ }
+
+ @Override
+ public void onHandleAssistData(Bundle data) {
+ synchronized (mCallbacksLock) {
+ if (mCanceled) {
+ return;
+ }
+ mPendingDataCount--;
+
+ if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+ // Process any pending data and dispatch the new data as well
+ flushPendingAssistData();
+ dispatchAssistDataReceived(data);
+ tryDispatchRequestComplete();
+ } else {
+ // Queue up the data for processing later
+ mAssistData.add(data);
+ }
+ }
+ }
+
+ @Override
+ public void onHandleAssistScreenshot(Bitmap screenshot) {
+ synchronized (mCallbacksLock) {
+ if (mCanceled) {
+ return;
+ }
+ mPendingScreenshotCount--;
+
+ if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+ // Process any pending data and dispatch the new data as well
+ flushPendingAssistData();
+ dispatchAssistScreenshotReceived(screenshot);
+ tryDispatchRequestComplete();
+ } else {
+ // Queue up the data for processing later
+ mAssistScreenshot.add(screenshot);
+ }
+ }
+ }
+
+ private void dispatchAssistDataReceived(Bundle data) {
+ int activityIndex = 0;
+ int activityCount = 0;
+ final Bundle receiverExtras = data != null
+ ? data.getBundle(ASSIST_KEY_RECEIVER_EXTRAS) : null;
+ if (receiverExtras != null) {
+ activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
+ activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
+ }
+ mCallbacks.onAssistDataReceivedLocked(data, activityIndex, activityCount);
+ }
+
+ private void dispatchAssistScreenshotReceived(Bitmap screenshot) {
+ mCallbacks.onAssistScreenshotReceivedLocked(screenshot);
+ }
+
+ private void tryDispatchRequestComplete() {
+ if (mPendingDataCount == 0 && mPendingScreenshotCount == 0 &&
+ mAssistData.isEmpty() && mAssistScreenshot.isEmpty()) {
+ mCallbacks.onAssistRequestCompleted();
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mPendingDataCount="); pw.println(mPendingDataCount);
+ pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
+ pw.print(prefix); pw.print("mPendingScreenshotCount="); pw.println(mPendingScreenshotCount);
+ pw.print(prefix); pw.print("mAssistScreenshot="); pw.println(mAssistScreenshot);
+ }
+} \ No newline at end of file
diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java
index db12ae25..a035bd02 100644
--- a/com/android/server/am/BatteryStatsService.java
+++ b/com/android/server/am/BatteryStatsService.java
@@ -21,8 +21,10 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -52,7 +54,6 @@ import com.android.internal.os.PowerProfile;
import com.android.internal.os.RpmStats;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
import android.util.StatsLog;
import java.io.File;
@@ -177,9 +178,22 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
public void publish() {
+ LocalServices.addService(BatteryStatsInternal.class, new LocalService());
ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}
+ private final class LocalService extends BatteryStatsInternal {
+ @Override
+ public String[] getWifiIfaces() {
+ return mStats.getWifiIfaces().clone();
+ }
+
+ @Override
+ public String[] getMobileIfaces() {
+ return mStats.getMobileIfaces().clone();
+ }
+ }
+
private static void awaitUninterruptibly(Future<?> future) {
while (true) {
try {
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index c3fed171..76b46796 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -41,10 +41,8 @@ import android.os.RemoteException;
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.wm.WindowManagerService;
-
import java.io.PrintWriter;
/**
@@ -236,24 +234,33 @@ class KeyguardController {
final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
mOccluded = false;
mDismissingKeyguardActivity = null;
- final ActivityDisplay display = mStackSupervisor.getDefaultDisplay();
- for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getChildAt(stackNdx);
-
- // Only the focused stack top activity may control occluded state
- if (mStackSupervisor.isFocusedStack(stack)) {
-
- // A dismissing activity occludes Keyguard in the insecure case for legacy reasons.
- final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
- mOccluded = stack.topActivityOccludesKeyguard()
- || (topDismissing != null
- && stack.topRunningActivityLocked() == topDismissing
- && canShowWhileOccluded(true /* dismissKeyguard */,
- false /* showWhenLocked */));
- }
- if (mDismissingKeyguardActivity == null
- && stack.getTopDismissingKeyguardActivity() != null) {
- mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+
+ for (int displayNdx = mStackSupervisor.getChildCount() - 1; displayNdx >= 0; displayNdx--) {
+ final ActivityDisplay display = mStackSupervisor.getChildAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+
+ // Only the top activity of the focused stack on the default display may control
+ // occluded state.
+ if (display.mDisplayId == DEFAULT_DISPLAY
+ && mStackSupervisor.isFocusedStack(stack)) {
+
+ // A dismissing activity occludes Keyguard in the insecure case for legacy
+ // reasons.
+ final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+ mOccluded =
+ stack.topActivityOccludesKeyguard()
+ || (topDismissing != null
+ && stack.topRunningActivityLocked() == topDismissing
+ && canShowWhileOccluded(
+ true /* dismissKeyguard */,
+ false /* showWhenLocked */));
+ }
+
+ if (mDismissingKeyguardActivity == null
+ && stack.getTopDismissingKeyguardActivity() != null) {
+ mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+ }
}
}
mOccluded |= mWindowManager.isShowingDream();
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index 4b2a0843..e87b4e63 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -143,9 +143,17 @@ public class LockTaskController {
LockTaskNotify mLockTaskNotify;
/**
- * The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks
- * may be finished until there is only one entry left. If this is empty the system is not
- * in lockTask mode.
+ * The chain of tasks in LockTask mode, in the order of when they first entered LockTask mode.
+ *
+ * The first task in the list, which started the current LockTask session, is called the root
+ * task. It coincides with the Home task in a typical multi-app kiosk deployment. When there are
+ * more than one locked tasks, the root task can't be finished. Nor can it be moved to the back
+ * of the stack by {@link ActivityStack#moveTaskToBackLocked(int)};
+ *
+ * Calling {@link Activity#stopLockTask()} on the root task will finish all tasks but itself in
+ * this list, and the device will exit LockTask mode.
+ *
+ * The list is empty if LockTask is inactive.
*/
private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
@@ -164,7 +172,7 @@ public class LockTaskController {
* {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
* {@link ActivityManager#LOCK_TASK_MODE_PINNED}
*/
- private int mLockTaskModeState;
+ private int mLockTaskModeState = LOCK_TASK_MODE_NONE;
/**
* This is ActivityStackSupervisor's Handler.
@@ -199,24 +207,28 @@ public class LockTaskController {
* @return whether the given task is locked at the moment. Locked tasks cannot be moved to the
* back of the stack.
*/
- boolean checkLockedTask(TaskRecord task) {
- if (mLockTaskModeTasks.contains(task)) {
- showLockTaskToast();
- return true;
- }
- return false;
+ @VisibleForTesting
+ boolean isTaskLocked(TaskRecord task) {
+ return mLockTaskModeTasks.contains(task);
}
/**
- * @return whether the given activity is blocked from finishing, because it is the root activity
+ * @return {@code true} whether this task first started the current LockTask session.
+ */
+ private boolean isRootTask(TaskRecord task) {
+ return mLockTaskModeTasks.indexOf(task) == 0;
+ }
+
+ /**
+ * @return whether the given activity is blocked from finishing, because it is the only activity
* of the last locked task and finishing it would mean that lock task mode is ended illegally.
*/
boolean activityBlockedFromFinish(ActivityRecord activity) {
- TaskRecord task = activity.getTask();
+ final TaskRecord task = activity.getTask();
if (activity == task.getRootActivity()
+ && activity == task.getTopActivity()
&& task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
- && mLockTaskModeTasks.size() == 1
- && mLockTaskModeTasks.contains(task)) {
+ && isRootTask(task)) {
Slog.i(TAG, "Not finishing task in lock task mode");
showLockTaskToast();
return true;
@@ -225,6 +237,19 @@ public class LockTaskController {
}
/**
+ * @return whether the given task can be moved to the back of the stack with
+ * {@link ActivityStack#moveTaskToBackLocked(int)}
+ * @see #mLockTaskModeTasks
+ */
+ boolean canMoveTaskToBack(TaskRecord task) {
+ if (isRootTask(task)) {
+ showLockTaskToast();
+ return false;
+ }
+ return true;
+ }
+
+ /**
* @return whether the requested task is allowed to be launched.
*/
boolean isLockTaskModeViolation(TaskRecord task) {
@@ -246,7 +271,7 @@ public class LockTaskController {
private boolean isLockTaskModeViolationInternal(TaskRecord task, boolean isNewClearTask) {
// TODO: Double check what's going on here. If the task is already in lock task mode, it's
// likely whitelisted, so will return false below.
- if (getLockedTask() == task && !isNewClearTask) {
+ if (isTaskLocked(task) && !isNewClearTask) {
// If the task is already at the top and won't be cleared, then allow the operation
return false;
}
@@ -270,80 +295,116 @@ public class LockTaskController {
/**
* Stop the current lock task mode.
*
- * @param isSystemInitiated indicates whether this request was initiated by the system via
- * {@link ActivityManagerService#stopSystemLockTaskMode()}.
+ * This is called by {@link ActivityManagerService} and performs various checks before actually
+ * finishing the locked task.
+ *
+ * @param task the task that requested the end of lock task mode ({@code null} for quitting app
+ * pinning mode)
+ * @param isSystemCaller indicates whether this request comes from the system via
+ * {@link ActivityManagerService#stopSystemLockTaskMode()}. If
+ * {@code true}, it means the user intends to stop pinned mode through UI;
+ * otherwise, it's called by an app and we need to stop locked or pinned
+ * mode, subject to checks.
* @param callingUid the caller that requested the end of lock task mode.
+ * @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in
+ * foreground)
* @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if
* they differ from the one that launched lock task mode.
*/
- void stopLockTaskMode(boolean isSystemInitiated, int callingUid) {
- final TaskRecord lockTask = getLockedTask();
- if (lockTask == null || mLockTaskModeState == LOCK_TASK_MODE_NONE) {
- // Our work here is done.
+ void stopLockTaskMode(@Nullable TaskRecord task, boolean isSystemCaller, int callingUid) {
+ if (mLockTaskModeState == LOCK_TASK_MODE_NONE) {
return;
}
- if (isSystemInitiated && mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
- // As system can only start app pinning, we also only let it unlock in this mode.
- showLockTaskToast();
- return;
+ if (isSystemCaller) {
+ if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ clearLockedTasks("stopAppPinning");
+ } else {
+ Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true");
+ showLockTaskToast();
+ }
+
+ } else {
+ // Ensure calling activity is not null
+ if (task == null) {
+ throw new IllegalArgumentException("can't stop LockTask for null task");
+ }
+
+ // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
+ // It is possible lockTaskMode was started by the system process because
+ // android:lockTaskMode is set to a locking value in the application manifest
+ // instead of the app calling startLockTaskMode. In this case
+ // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
+ // {@link TaskRecord.effectiveUid} instead. Also caller with
+ // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
+ if (callingUid != task.mLockTaskUid
+ && (task.mLockTaskUid != 0 || callingUid != task.effectiveUid)) {
+ throw new SecurityException("Invalid uid, expected " + task.mLockTaskUid
+ + " callingUid=" + callingUid + " effectiveUid=" + task.effectiveUid);
+ }
+
+ // We don't care if it's pinned or locked mode; this will stop it anyways.
+ clearLockedTask(task);
}
+ }
- // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
- // It is possible lockTaskMode was started by the system process because
- // android:lockTaskMode is set to a locking value in the application manifest
- // instead of the app calling startLockTaskMode. In this case
- // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
- // {@link TaskRecord.effectiveUid} instead. Also caller with
- // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
- if (!isSystemInitiated && callingUid != lockTask.mLockTaskUid
- && (lockTask.mLockTaskUid != 0 || callingUid != lockTask.effectiveUid)) {
- throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid
- + " callingUid=" + callingUid + " effectiveUid=" + lockTask.effectiveUid);
+ /**
+ * Clear all locked tasks and request the end of LockTask mode.
+ *
+ * This method is called by {@link UserController} when starting a new foreground user, and,
+ * unlike {@link #stopLockTaskMode(TaskRecord, boolean, int)}, it doesn't perform the checks.
+ */
+ void clearLockedTasks(String reason) {
+ if (DEBUG_LOCKTASK) Slog.i(TAG_LOCKTASK, "clearLockedTasks: " + reason);
+ if (!mLockTaskModeTasks.isEmpty()) {
+ clearLockedTask(mLockTaskModeTasks.get(0));
+ }
+ }
+
+ /**
+ * Clear one locked task from LockTask mode.
+ *
+ * If the requested task is the root task (see {@link #mLockTaskModeTasks}), then all locked
+ * tasks are cleared. Otherwise, only the requested task is cleared. LockTask mode is stopped
+ * when the last locked task is cleared.
+ *
+ * @param task the task to be cleared from LockTask mode.
+ */
+ void clearLockedTask(final TaskRecord task) {
+ if (task == null || mLockTaskModeTasks.isEmpty()) return;
+
+ if (task == mLockTaskModeTasks.get(0)) {
+ // We're removing the root task while there are other locked tasks. Therefore we should
+ // clear all locked tasks in reverse order.
+ for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx > 0; --taskNdx) {
+ clearLockedTask(mLockTaskModeTasks.get(taskNdx));
+ }
}
- clearLockTaskMode("stopLockTask");
+ removeLockedTask(task);
+ if (mLockTaskModeTasks.isEmpty()) {
+ return;
+ }
+ task.performClearTaskLocked();
+ mSupervisor.resumeFocusedStackTopActivityLocked();
}
/**
* Remove the given task from the locked task list. If this was the last task in the list,
* lock task mode is stopped.
*/
- void removeLockedTask(final TaskRecord task) {
+ private void removeLockedTask(final TaskRecord task) {
if (!mLockTaskModeTasks.remove(task)) {
return;
}
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "removeLockedTask: removed " + task);
+ if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: removed " + task);
if (mLockTaskModeTasks.isEmpty()) {
- // Last one.
if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task +
" last task, reverting locktask mode. Callers=" + Debug.getCallers(3));
mHandler.post(() -> performStopLockTask(task.userId));
}
}
- /**
- * Remove the topmost task from the locked task list. If this is the last task in the list, it
- * will result in the end of locked task mode.
- */
- void clearLockTaskMode(String reason) {
- // Take out of lock task mode if necessary
- final TaskRecord lockedTask = getLockedTask();
- if (lockedTask != null) {
- removeLockedTask(lockedTask);
- if (!mLockTaskModeTasks.isEmpty()) {
- // There are locked tasks remaining, can only finish this task, not unlock it.
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
- "setLockTaskMode: Tasks remaining, can't unlock");
- lockedTask.performClearTaskLocked();
- mSupervisor.resumeFocusedStackTopActivityLocked();
- return;
- }
- }
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
- "setLockTaskMode: No tasks to unlock. Callers=" + Debug.getCallers(4));
- }
-
// This method should only be called on the handler thread
private void performStopLockTask(int userId) {
// When lock task ends, we enable the status bars.
@@ -382,17 +443,18 @@ public class LockTaskController {
* Method to start lock task mode on a given task.
*
* @param task the task that should be locked.
- * @param isSystemInitiated indicates whether this request was initiated by the system via
- * {@link ActivityManagerService#startSystemLockTaskMode(int)}.
+ * @param isSystemCaller indicates whether this request was initiated by the system via
+ * {@link ActivityManagerService#startSystemLockTaskMode(int)}. If
+ * {@code true}, this intends to start pinned mode; otherwise, we look
+ * at the calling task's mLockTaskAuth to decide which mode to start.
* @param callingUid the caller that requested the launch of lock task mode.
*/
- void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemInitiated,
- int callingUid) {
- if (!isSystemInitiated) {
+ void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemCaller, int callingUid) {
+ if (!isSystemCaller) {
task.mLockTaskUid = callingUid;
if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
// startLockTask() called by app, but app is not part of lock task whitelist. Show
- // app pinning request. We will come back here with isSystemInitiated true.
+ // app pinning request. We will come back here with isSystemCaller true.
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user");
StatusBarManagerInternal statusBarManager = LocalServices.getService(
StatusBarManagerInternal.class);
@@ -404,8 +466,9 @@ public class LockTaskController {
}
// System can only initiate screen pinning, not full lock task mode
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, isSystemInitiated ? "Locking pinned" : "Locking fully");
- setLockTaskMode(task, isSystemInitiated ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
+ if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
+ isSystemCaller ? "Locking pinned" : "Locking fully");
+ setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
"startLockTask", true);
}
@@ -434,12 +497,12 @@ public class LockTaskController {
task.userId,
lockTaskModeState));
}
-
- // Add it or move it to the top.
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task +
" Callers=" + Debug.getCallers(4));
- mLockTaskModeTasks.remove(task);
- mLockTaskModeTasks.add(task);
+
+ if (!mLockTaskModeTasks.contains(task)) {
+ mLockTaskModeTasks.add(task);
+ }
if (task.mLockTaskUid == -1) {
task.mLockTaskUid = task.effectiveUid;
@@ -556,8 +619,7 @@ public class LockTaskController {
}
mLockTaskFeatures.put(userId, flags);
- TaskRecord lockedTask = getLockedTask();
- if (lockedTask != null && userId == lockedTask.userId) {
+ if (!mLockTaskModeTasks.isEmpty() && userId == mLockTaskModeTasks.get(0).userId) {
mHandler.post(() -> {
if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
setStatusBarState(mLockTaskModeState, userId);
@@ -672,17 +734,6 @@ public class LockTaskController {
return mLockTaskFeatures.get(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
}
- /**
- * @return the topmost locked task
- */
- private TaskRecord getLockedTask() {
- final int top = mLockTaskModeTasks.size() - 1;
- if (top >= 0) {
- return mLockTaskModeTasks.get(top);
- }
- return null;
- }
-
// Should only be called on the handler thread
@Nullable
private IStatusBarService getStatusBarService() {
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index 0b9e0a23..d35c37b5 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -264,6 +264,20 @@ class RecentTasks {
return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid);
}
+ /**
+ * @return the recents component.
+ */
+ ComponentName getRecentsComponent() {
+ return mRecentsComponent;
+ }
+
+ /**
+ * @return the uid for the recents component.
+ */
+ int getRecentsComponentUid() {
+ return mRecentsUid;
+ }
+
void registerCallback(Callbacks callback) {
mCallbacks.add(callback);
}
diff --git a/com/android/server/am/RunningTasks.java b/com/android/server/am/RunningTasks.java
index 400b03a9..c860df89 100644
--- a/com/android/server/am/RunningTasks.java
+++ b/com/android/server/am/RunningTasks.java
@@ -47,8 +47,10 @@ class RunningTasks {
void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
@WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
int callingUid, boolean allowed) {
- // For each stack on each display, add the tasks into the sorted set and then pull the first
- // {@param maxNum} from the set
+ // Return early if there are no tasks to fetch
+ if (maxNum <= 0) {
+ return;
+ }
// Gather all of the tasks across all of the tasks, and add them to the sorted set
mTmpSortedSet.clear();
diff --git a/com/android/server/am/TaskChangeNotificationController.java b/com/android/server/am/TaskChangeNotificationController.java
index 5a7e7ced..7896e2dd 100644
--- a/com/android/server/am/TaskChangeNotificationController.java
+++ b/com/android/server/am/TaskChangeNotificationController.java
@@ -95,8 +95,8 @@ class TaskChangeNotificationController {
};
private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
- final ActivityRecord r = (ActivityRecord) m.obj;
- l.onActivityPinned(r.packageName, r.userId, r.getTask().taskId, r.getStackId());
+ l.onActivityPinned((String) m.obj /* packageName */, m.sendingUid /* userId */,
+ m.arg1 /* taskId */, m.arg2 /* stackId */);
};
private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
@@ -281,7 +281,9 @@ class TaskChangeNotificationController {
/** Notifies all listeners when an Activity is pinned. */
void notifyActivityPinned(ActivityRecord r) {
mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
- final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, r);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
+ r.getTask().taskId, r.getStackId(), r.packageName);
+ msg.sendingUid = r.userId;
forAllLocalListeners(mNotifyActivityPinned, msg);
msg.sendToTarget();
}
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 1b5a1ce3..949f51fe 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -438,7 +438,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
void removeWindowContainer() {
- mService.mLockTaskController.removeLockedTask(this);
+ mService.mLockTaskController.clearLockedTask(this);
mWindowContainerController.removeContainer();
if (!getWindowConfiguration().persistTaskBounds()) {
// Reset current bounds for task whose bounds shouldn't be persisted so it uses
@@ -606,9 +606,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
final int toStackWindowingMode = toStack.getWindowingMode();
final ActivityRecord topActivity = getTopActivity();
- final boolean mightReplaceWindow =
- replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode)
- && topActivity != null;
+ final boolean mightReplaceWindow = topActivity != null
+ && replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode);
if (mightReplaceWindow) {
// We are about to relaunch the activity because its configuration changed due to
// being maximized, i.e. size change. The activity will first remove the old window
@@ -722,7 +721,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
// TODO: Handle incorrect request to move before the actual move, not after.
- final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenPrimaryStack();
supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
DEFAULT_DISPLAY, toStack);
@@ -735,10 +733,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
/**
- * Returns true if the windows of tasks being moved to the target stack from the source
- * stack should be replaced, meaning that window manager will keep the old window around
- * until the new is ready.
- * @hide
+ * @return True if the windows of tasks being moved to the target stack from the source stack
+ * should be replaced, meaning that window manager will keep the old window around until the new
+ * is ready.
*/
private static boolean replaceWindowsOnTaskMove(
int sourceWindowingMode, int targetWindowingMode) {
@@ -1036,6 +1033,16 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return null;
}
+ boolean isVisible() {
+ for (int i = mActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord r = mActivities.get(i);
+ if (r.visible) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
if (mStack != null) {
for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
@@ -1129,6 +1136,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mActivities.remove(newTop);
mActivities.add(newTop);
+
+ // Make sure window manager is aware of the position change.
+ mWindowContainerController.positionChildAtTop(newTop.mWindowContainerController);
updateEffectiveIntent();
setFrontOfTask();
@@ -2049,11 +2059,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
static Rect validateBounds(Rect bounds) {
- if (bounds != null && bounds.isEmpty()) {
- Slog.wtf(TAG, "Received strange task bounds: " + bounds, new Throwable());
- return null;
- }
- return 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
@@ -2082,7 +2089,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
/** Returns the bounds that should be used to launch this task. */
- private Rect getLaunchBounds() {
+ Rect getLaunchBounds() {
if (mStack == null) {
return null;
}
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index 44f83b0e..2df5dc93 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -872,9 +872,7 @@ class UserController implements Handler.Callback {
}
if (foreground) {
- // TODO: I don't think this does what the caller think it does. Seems to only
- // remove one locked task and won't work if multiple locked tasks are present.
- mInjector.clearLockTaskMode("startUser");
+ mInjector.clearAllLockedTasks("startUser");
}
final UserInfo userInfo = getUserInfo(userId);
@@ -2053,9 +2051,9 @@ class UserController implements Handler.Callback {
}
}
- protected void clearLockTaskMode(String reason) {
+ protected void clearAllLockedTasks(String reason) {
synchronized (mService) {
- mService.mLockTaskController.clearLockTaskMode(reason);
+ mService.mLockTaskController.clearLockedTasks(reason);
}
}
}
diff --git a/com/android/server/audio/AudioService.java b/com/android/server/audio/AudioService.java
index 5eb2a8d2..15a418dc 100644
--- a/com/android/server/audio/AudioService.java
+++ b/com/android/server/audio/AudioService.java
@@ -122,6 +122,7 @@ import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
@@ -398,8 +399,9 @@ public class AudioService extends IAudioService.Stub
* {@link AudioManager#RINGER_MODE_SILENT}, or
* {@link AudioManager#RINGER_MODE_VIBRATE}.
*/
- // protected by mSettingsLock
+ @GuardedBy("mSettingsLock")
private int mRingerMode; // internal ringer mode, affects muting of underlying streams
+ @GuardedBy("mSettingsLock")
private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager)
/** @see System#MODE_RINGER_STREAMS_AFFECTED */
@@ -929,8 +931,11 @@ public class AudioService extends IAudioService.Stub
mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
"onAudioServerDied"));
AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
- final int forSys = mCameraSoundForced ?
- AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+ final int forSys;
+ synchronized (mSettingsLock) {
+ forSys = mCameraSoundForced ?
+ AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+ }
mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
"onAudioServerDied"));
AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
@@ -1340,8 +1345,9 @@ public class AudioService extends IAudioService.Stub
} else {
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
- if (maybeActiveStreamType == AudioSystem.STREAM_MUSIC) {
- activeForReal = isAfMusicActiveRecently(0);
+ if (maybeActiveStreamType == AudioSystem.STREAM_RING
+ || maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) {
+ activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0);
} else {
activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);
}
@@ -3797,6 +3803,7 @@ public class AudioService extends IAudioService.Stub
return (mRingerModeMutedStreams & (1 << streamType)) != 0;
}
+ @GuardedBy("mSettingsLock")
private boolean updateRingerModeAffectedStreams() {
int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -3810,12 +3817,10 @@ public class AudioService extends IAudioService.Stub
ringerModeAffectedStreams = mRingerModeDelegate
.getRingerModeAffectedStreams(ringerModeAffectedStreams);
}
- synchronized (mCameraSoundForced) {
- if (mCameraSoundForced) {
- ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- } else {
- ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- }
+ if (mCameraSoundForced) {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ } else {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
}
if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
@@ -3879,13 +3884,13 @@ public class AudioService extends IAudioService.Stub
/**
* For code clarity for getActiveStreamType(int)
- * @param delay_ms max time since last STREAM_MUSIC activity to consider
- * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or
+ * @param delay_ms max time since last stream activity to consider
+ * @return true if stream is active in streams handled by AudioFlinger now or
* in the last "delay_ms" ms.
*/
- private boolean isAfMusicActiveRecently(int delay_ms) {
- return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms)
- || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms);
+ private boolean wasStreamActiveRecently(int stream, int delay_ms) {
+ return AudioSystem.isStreamActive(stream, delay_ms)
+ || AudioSystem.isStreamActiveRemotely(stream, delay_ms);
}
private int getActiveStreamType(int suggestedStreamType) {
@@ -3906,21 +3911,30 @@ public class AudioService extends IAudioService.Stub
return AudioSystem.STREAM_VOICE_CALL;
}
} else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
+ if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active");
+ return AudioSystem.STREAM_RING;
+ } else if (wasStreamActiveRecently(
+ AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
+ return AudioSystem.STREAM_NOTIFICATION;
+ } else {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC b/c default");
return AudioSystem.STREAM_MUSIC;
- } else {
- if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
- return AudioSystem.STREAM_RING;
}
- } else if (isAfMusicActiveRecently(0)) {
+ } else if (
+ wasStreamActiveRecently(AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
- return AudioSystem.STREAM_MUSIC;
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
+ return AudioSystem.STREAM_NOTIFICATION;
+ } else if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active");
+ return AudioSystem.STREAM_RING;
}
- break;
default:
if (isInCommunication()) {
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
@@ -3931,20 +3945,26 @@ public class AudioService extends IAudioService.Stub
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL");
return AudioSystem.STREAM_VOICE_CALL;
}
- } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION,
- sStreamOverrideDelayMs) ||
- AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
- sStreamOverrideDelayMs)) {
+ } else if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
return AudioSystem.STREAM_NOTIFICATION;
+ } else if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING");
+ return AudioSystem.STREAM_RING;
} else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
- if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
- return AudioSystem.STREAM_MUSIC;
- } else {
- if (DEBUG_VOL) Log.v(TAG,
- "getActiveStreamType: using STREAM_NOTIFICATION as default");
+ if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
return AudioSystem.STREAM_NOTIFICATION;
+ } else if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING");
+ return AudioSystem.STREAM_RING;
+ } else {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default");
+ return AudioSystem.STREAM_MUSIC;
}
}
break;
@@ -4185,7 +4205,6 @@ public class AudioService extends IAudioService.Stub
// 2 mSetModeDeathHandlers
// 3 mSettingsLock
// 4 VolumeStreamState.class
- // 5 mCameraSoundForced
public class VolumeStreamState {
private final int mStreamType;
private final int mIndexMin;
@@ -4252,27 +4271,28 @@ public class AudioService extends IAudioService.Stub
}
public void readSettings() {
- synchronized (VolumeStreamState.class) {
- // force maximum volume on all streams if fixed volume property is set
- if (mUseFixedVolume) {
- mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
- return;
- }
- // do not read system stream volume from settings: this stream is always aliased
- // to another stream type and its volume is never persisted. Values in settings can
- // only be stale values
- if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
- (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
- int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
+ synchronized (VolumeStreamState.class) {
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ // do not read system stream volume from settings: this stream is always aliased
+ // to another stream type and its volume is never persisted. Values in settings can
+ // only be stale values
+ if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+ (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+ int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
if (mCameraSoundForced) {
index = mIndexMax;
}
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+ return;
}
- mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
- return;
}
-
+ }
+ synchronized (VolumeStreamState.class) {
int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
for (int i = 0; remainingDevices != 0; i++) {
@@ -4385,34 +4405,34 @@ public class AudioService extends IAudioService.Stub
public boolean setIndex(int index, int device, String caller) {
boolean changed = false;
int oldIndex;
- synchronized (VolumeStreamState.class) {
- oldIndex = getIndex(device);
- index = getValidIndex(index);
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
+ synchronized (VolumeStreamState.class) {
+ oldIndex = getIndex(device);
+ index = getValidIndex(index);
if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
index = mIndexMax;
}
- }
- mIndexMap.put(device, index);
-
- changed = oldIndex != index;
- // Apply change to all streams using this one as alias if:
- // - the index actually changed OR
- // - there is no volume index stored for this device on alias stream.
- // If changing volume of current device, also change volume of current
- // device on aliased stream
- final boolean currentDevice = (device == getDeviceForStream(mStreamType));
- final int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- final VolumeStreamState aliasStreamState = mStreamStates[streamType];
- if (streamType != mStreamType &&
- mStreamVolumeAlias[streamType] == mStreamType &&
- (changed || !aliasStreamState.hasIndexForDevice(device))) {
- final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
- aliasStreamState.setIndex(scaledIndex, device, caller);
- if (currentDevice) {
- aliasStreamState.setIndex(scaledIndex,
- getDeviceForStream(streamType), caller);
+ mIndexMap.put(device, index);
+
+ changed = oldIndex != index;
+ // Apply change to all streams using this one as alias if:
+ // - the index actually changed OR
+ // - there is no volume index stored for this device on alias stream.
+ // If changing volume of current device, also change volume of current
+ // device on aliased stream
+ final boolean currentDevice = (device == getDeviceForStream(mStreamType));
+ final int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ final VolumeStreamState aliasStreamState = mStreamStates[streamType];
+ if (streamType != mStreamType &&
+ mStreamVolumeAlias[streamType] == mStreamType &&
+ (changed || !aliasStreamState.hasIndexForDevice(device))) {
+ final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+ aliasStreamState.setIndex(scaledIndex, device, caller);
+ if (currentDevice) {
+ aliasStreamState.setIndex(scaledIndex,
+ getDeviceForStream(streamType), caller);
+ }
}
}
}
@@ -6022,13 +6042,8 @@ public class AudioService extends IAudioService.Stub
boolean cameraSoundForced = readCameraSoundForced();
synchronized (mSettingsLock) {
- boolean cameraSoundForcedChanged = false;
- synchronized (mCameraSoundForced) {
- if (cameraSoundForced != mCameraSoundForced) {
- mCameraSoundForced = cameraSoundForced;
- cameraSoundForcedChanged = true;
- }
- }
+ final boolean cameraSoundForcedChanged = (cameraSoundForced != mCameraSoundForced);
+ mCameraSoundForced = cameraSoundForced;
if (cameraSoundForcedChanged) {
if (!mIsSingleVolume) {
VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
@@ -6350,10 +6365,10 @@ public class AudioService extends IAudioService.Stub
// stream override timeout when adjusting volume
//---------------------------------------------------------------------------------
- // AudioService.getActiveStreamType() will return:
// - STREAM_NOTIFICATION on tablets during this period after a notification stopped
- // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt
- // stopped
+ // - STREAM_RING on phones during this period after a notification stopped
+ // - STREAM_MUSIC otherwise
+
private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0;
private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000;
@@ -6408,11 +6423,12 @@ public class AudioService extends IAudioService.Stub
//==========================================================================================
// cached value of com.android.internal.R.bool.config_camera_sound_forced
- private Boolean mCameraSoundForced;
+ @GuardedBy("mSettingsLock")
+ private boolean mCameraSoundForced;
// called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
public boolean isCameraSoundForced() {
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
return mCameraSoundForced;
}
}
@@ -6734,7 +6750,9 @@ public class AudioService extends IAudioService.Stub
public void setRingerModeDelegate(RingerModeDelegate delegate) {
mRingerModeDelegate = delegate;
if (mRingerModeDelegate != null) {
- updateRingerModeAffectedStreams();
+ synchronized (mSettingsLock) {
+ updateRingerModeAffectedStreams();
+ }
setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
}
}
diff --git a/com/android/server/autofill/AutofillManagerService.java b/com/android/server/autofill/AutofillManagerService.java
index 6c3eb200..23e4f504 100644
--- a/com/android/server/autofill/AutofillManagerService.java
+++ b/com/android/server/autofill/AutofillManagerService.java
@@ -447,7 +447,6 @@ public final class AutofillManagerService extends SystemService {
android.view.autofill.Helper.sDebug = debug;
}
-
private void setVerboseLocked(boolean verbose) {
com.android.server.autofill.Helper.sVerbose = verbose;
android.view.autofill.Helper.sVerbose = verbose;
@@ -513,6 +512,16 @@ public final class AutofillManagerService extends SystemService {
}
@Override
+ public void removeClient(IAutoFillManagerClient client, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.removeClientLocked(client);
+ }
+ }
+ }
+
+ @Override
public void setAuthenticationResult(Bundle data, int sessionId, int authenticationId,
int userId) {
synchronized (mLock) {
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index 2ed5eeee..21e27220 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -273,6 +273,12 @@ final class AutofillManagerServiceImpl {
return isEnabled();
}
+ void removeClientLocked(IAutoFillManagerClient client) {
+ if (mClients != null) {
+ mClients.unregister(client);
+ }
+ }
+
void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
if (!isEnabled()) {
return;
@@ -548,6 +554,10 @@ final class AutofillManagerServiceImpl {
}
sendStateToClients(true);
+ if (mClients != null) {
+ mClients.kill();
+ mClients = null;
+ }
}
@NonNull
@@ -602,7 +612,7 @@ final class AutofillManagerServiceImpl {
if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
- null, null, null, null));
+ null, null, null, null, null, -1));
}
}
}
@@ -616,7 +626,7 @@ final class AutofillManagerServiceImpl {
if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
- clientState, null, null, null, null, null, null));
+ clientState, null, null, null, null, null, null, null, -1));
}
}
}
@@ -628,7 +638,7 @@ final class AutofillManagerServiceImpl {
synchronized (mLock) {
if (isValidEventLocked("logSaveShown()", sessionId)) {
mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
- null, null, null, null, null));
+ null, null, null, null, null, null, -1));
}
}
}
@@ -642,7 +652,7 @@ final class AutofillManagerServiceImpl {
if (isValidEventLocked("logDatasetSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
- null, null, null, null, null));
+ null, null, null, null, null, null, -1));
}
}
}
@@ -656,13 +666,15 @@ final class AutofillManagerServiceImpl {
@Nullable ArrayList<AutofillId> changedFieldIds,
@Nullable ArrayList<String> changedDatasetIds,
@Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
- @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+ @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
+ @Nullable String detectedRemoteId, int detectedFieldScore) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
clientState, selectedDatasets, ignoredDatasets,
changedFieldIds, changedDatasetIds,
- manuallyFilledFieldIds, manuallyFilledDatasetIds));
+ manuallyFilledFieldIds, manuallyFilledDatasetIds,
+ detectedRemoteId, detectedFieldScore));
}
}
}
@@ -695,6 +707,7 @@ final class AutofillManagerServiceImpl {
pw.print(prefix); pw.print("Default component: ");
pw.println(mContext.getString(R.string.config_defaultAutofillService));
pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+ pw.print(prefix); pw.print("Field detection: "); pw.println(isFieldDetectionEnabled());
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
@@ -749,6 +762,9 @@ final class AutofillManagerServiceImpl {
}
}
+ pw.print(prefix); pw.println("Clients");
+ mClients.dump(pw, prefix2);
+
if (mEventHistory == null || mEventHistory.getEvents() == null
|| mEventHistory.getEvents().size() == 0) {
pw.print(prefix); pw.println("No event on last fill response");
@@ -813,7 +829,23 @@ final class AutofillManagerServiceImpl {
synchronized (mLock) {
resetSession = resetClient || isClientSessionDestroyedLocked(client);
}
- client.setState(isEnabled(), resetSession, resetClient);
+ int flags = 0;
+ if (isEnabled()) {
+ flags |= AutofillManager.SET_STATE_FLAG_ENABLED;
+ }
+ if (resetSession) {
+ flags |= AutofillManager.SET_STATE_FLAG_RESET_SESSION;
+ }
+ if (resetClient) {
+ flags |= AutofillManager.SET_STATE_FLAG_RESET_CLIENT;
+ }
+ if (sDebug) {
+ flags |= AutofillManager.SET_STATE_FLAG_DEBUG;
+ }
+ if (sVerbose) {
+ flags |= AutofillManager.SET_STATE_FLAG_VERBOSE;
+ }
+ client.setState(flags);
} catch (RemoteException re) {
/* ignore */
}
@@ -919,6 +951,13 @@ final class AutofillManagerServiceImpl {
return false;
}
+ // TODO(b/67867469): remove once feature is finished
+ boolean isFieldDetectionEnabled() {
+ return Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION, 0,
+ mUserId) == 1;
+ }
+
@Override
public String toString() {
return "AutofillManagerServiceImpl: [userId=" + mUserId
diff --git a/com/android/server/autofill/Helper.java b/com/android/server/autofill/Helper.java
index 236fbfd9..02a62e10 100644
--- a/com/android/server/autofill/Helper.java
+++ b/com/android/server/autofill/Helper.java
@@ -28,6 +28,7 @@ import android.view.autofill.AutofillValue;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@@ -112,4 +113,12 @@ public final class Helper {
}
return log;
}
+
+ public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable String text) {
+ if (text == null) {
+ pw.println("null");
+ } else {
+ pw.print(text.length()); pw.println("_chars");
+ }
+ }
}
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index 010995f2..af4668a6 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -16,10 +16,10 @@
package com.android.server.autofill;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
-import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
@@ -36,6 +36,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.IAssistDataReceiver;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
@@ -43,6 +44,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Binder;
@@ -53,6 +55,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
+import android.service.autofill.FieldsDetection;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
@@ -78,7 +81,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
import com.android.internal.util.ArrayUtils;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
@@ -203,16 +205,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* Receiver of assist data from the app's {@link Activity}.
*/
- private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+ private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
@Override
- public void send(int resultCode, Bundle resultData) throws RemoteException {
- final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
+ public void onHandleAssistData(Bundle resultData) throws RemoteException {
+ final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE);
if (structure == null) {
Slog.e(TAG, "No assist structure - app might have crashed providing it");
return;
}
- final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
+ final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
if (receiverExtras == null) {
Slog.e(TAG, "No receiver extras - app might have crashed providing it");
return;
@@ -261,6 +263,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mRemoteFillService.onFillRequest(request);
}
+
+ @Override
+ public void onHandleAssistScreenshot(Bitmap screenshot) {
+ // Do nothing
+ }
};
/**
@@ -486,6 +493,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ // TODO(b/67867469): remove once feature is finished
+ if (response.getFieldsDetection() != null && !mService.isFieldDetectionEnabled()) {
+ Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
+ processNullResponseLocked(requestFlags);
+ return;
+ }
+
mService.setLastResponse(serviceUid, id, response);
int sessionFinishedState = 0;
@@ -907,11 +921,29 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
}
- if (!hasAtLeastOneDataset) {
- if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets)");
+ final FieldsDetection fieldsDetection = lastResponse.getFieldsDetection();
+
+ if (!hasAtLeastOneDataset && fieldsDetection == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields "
+ + "detection)");
+ }
return;
}
+ final AutofillId detectableFieldId;
+ final String detectableRemoteId;
+ String detectedRemoteId = null;
+ if (fieldsDetection == null) {
+ detectableFieldId = null;
+ detectableRemoteId = null;
+ } else {
+ detectableFieldId = fieldsDetection.getFieldId();
+ detectableRemoteId = fieldsDetection.getRemoteId();
+ }
+
+ int detectedFieldScore = -1;
+
for (int i = 0; i < mViewStates.size(); i++) {
final ViewState viewState = mViewStates.valueAt(i);
final int state = viewState.getState();
@@ -920,7 +952,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// - autofilled -> changedDatasetIds
// - not autofilled but matches a dataset value -> manuallyFilledIds
if ((state & ViewState.STATE_CHANGED) != 0) {
-
// Check if autofilled value was changed
if ((state & ViewState.STATE_AUTOFILLED) != 0) {
final String datasetId = viewState.getDatasetId();
@@ -952,7 +983,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
changedFieldIds.add(viewState.id);
changedDatasetIds.add(datasetId);
} else {
- // Check if value match a dataset.
final AutofillValue currentValue = viewState.getCurrentValue();
if (currentValue == null) {
if (sDebug) {
@@ -961,58 +991,78 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
continue;
}
- for (int j = 0; j < responseCount; j++) {
- final FillResponse response = mResponses.valueAt(j);
- final List<Dataset> datasets = response.getDatasets();
- if (datasets == null || datasets.isEmpty()) {
- if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + j);
- } else {
- for (int k = 0; k < datasets.size(); k++) {
- final Dataset dataset = datasets.get(k);
- final String datasetId = dataset.getId();
- if (datasetId == null) {
- if (sVerbose) {
- Slog.v(TAG, "logContextCommitted() skipping idless dataset "
- + dataset);
- }
- } else {
- final ArrayList<AutofillValue> values = dataset.getFieldValues();
- for (int l = 0; l < values.size(); l++) {
- final AutofillValue candidate = values.get(l);
- if (currentValue.equals(candidate)) {
- if (sDebug) {
- Slog.d(TAG, "field " + viewState.id
- + " was manually filled with value set by "
- + "dataset " + datasetId);
+ // Check if value match a dataset.
+ if (hasAtLeastOneDataset) {
+ for (int j = 0; j < responseCount; j++) {
+ final FillResponse response = mResponses.valueAt(j);
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets == null || datasets.isEmpty()) {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommitted() no datasets at " + j);
+ }
+ } else {
+ for (int k = 0; k < datasets.size(); k++) {
+ final Dataset dataset = datasets.get(k);
+ final String datasetId = dataset.getId();
+ if (datasetId == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommitted() skipping idless "
+ + "dataset " + dataset);
+ }
+ } else {
+ final ArrayList<AutofillValue> values =
+ dataset.getFieldValues();
+ for (int l = 0; l < values.size(); l++) {
+ final AutofillValue candidate = values.get(l);
+ if (currentValue.equals(candidate)) {
+ if (sDebug) {
+ Slog.d(TAG, "field " + viewState.id + " was "
+ + "manually filled with value set by "
+ + "dataset " + datasetId);
+ }
+ if (manuallyFilledIds == null) {
+ manuallyFilledIds = new ArrayMap<>();
+ }
+ ArraySet<String> datasetIds =
+ manuallyFilledIds.get(viewState.id);
+ if (datasetIds == null) {
+ datasetIds = new ArraySet<>(1);
+ manuallyFilledIds.put(viewState.id, datasetIds);
+ }
+ datasetIds.add(datasetId);
}
- if (manuallyFilledIds == null) {
- manuallyFilledIds = new ArrayMap<>();
+ } // for l
+ if (mSelectedDatasetIds == null
+ || !mSelectedDatasetIds.contains(datasetId)) {
+ if (sVerbose) {
+ Slog.v(TAG, "adding ignored dataset " + datasetId);
}
- ArraySet<String> datasetIds =
- manuallyFilledIds.get(viewState.id);
- if (datasetIds == null) {
- datasetIds = new ArraySet<>(1);
- manuallyFilledIds.put(viewState.id, datasetIds);
+ if (ignoredDatasets == null) {
+ ignoredDatasets = new ArraySet<>();
}
- datasetIds.add(datasetId);
- }
- }
- if (mSelectedDatasetIds == null
- || !mSelectedDatasetIds.contains(datasetId)) {
- if (sVerbose) {
- Slog.v(TAG, "adding ignored dataset " + datasetId);
- }
- if (ignoredDatasets == null) {
- ignoredDatasets = new ArraySet<>();
- }
- ignoredDatasets.add(datasetId);
- }
- }
- }
+ ignoredDatasets.add(datasetId);
+ } // if
+ } // if
+ } // for k
+ } // else
+ } // for j
+ }
+
+ // Check if detectable field changed.
+ if (detectableFieldId != null && detectableFieldId.equals(viewState.id)
+ && currentValue.isText() && currentValue.getTextValue() != null) {
+ final String actualValue = currentValue.getTextValue().toString();
+ final String expectedValue = fieldsDetection.getValue();
+ if (actualValue.equalsIgnoreCase(expectedValue)) {
+ detectedRemoteId = detectableRemoteId;
+ detectedFieldScore = 0;
+ } else if (sVerbose) {
+ Slog.v(TAG, "Detection mismatch for field " + detectableFieldId);
}
+ // TODO(b/67867469): set score on partial hits
}
- }
- }
+ } // else
+ } // else
}
if (sVerbose) {
@@ -1021,7 +1071,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
+ ", ignoredDatasetIds=" + ignoredDatasets
+ ", changedAutofillIds=" + changedFieldIds
+ ", changedDatasetIds=" + changedDatasetIds
- + ", manuallyFilledIds=" + manuallyFilledIds);
+ + ", manuallyFilledIds=" + manuallyFilledIds
+ + ", detectableFieldId=" + detectableFieldId
+ + ", detectedFieldScore=" + detectedFieldScore
+ );
}
ArrayList<AutofillId> manuallyFilledFieldIds = null;
@@ -1039,9 +1092,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
}
}
+
mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
changedFieldIds, changedDatasetIds,
- manuallyFilledFieldIds, manuallyFilledDatasetIds);
+ manuallyFilledFieldIds, manuallyFilledDatasetIds,
+ detectedRemoteId, detectedFieldScore);
}
/**
@@ -1529,6 +1584,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
viewState = new ViewState(this, id, this,
isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
mViewStates.put(id, viewState);
+
+ // TODO(b/67867469): for optimization purposes, should also ignore if change is
+ // detectable, and batch-send them when the session is finished (but that will
+ // require tracking detectable fields on AutofillManager)
if (isIgnored) {
if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + id);
return;
diff --git a/com/android/server/autofill/ViewState.java b/com/android/server/autofill/ViewState.java
index 1d8110f0..832a66b2 100644
--- a/com/android/server/autofill/ViewState.java
+++ b/com/android/server/autofill/ViewState.java
@@ -134,7 +134,11 @@ final class ViewState {
}
String getStateAsString() {
- return DebugUtils.flagsToString(ViewState.class, "STATE_", mState);
+ return getStateAsString(mState);
+ }
+
+ static String getStateAsString(int state) {
+ return DebugUtils.flagsToString(ViewState.class, "STATE_", state);
}
void setState(int state) {
diff --git a/com/android/server/autofill/ui/FillUi.java b/com/android/server/autofill/ui/FillUi.java
index 6d3d792e..dac4586f 100644
--- a/com/android/server/autofill/ui/FillUi.java
+++ b/com/android/server/autofill/ui/FillUi.java
@@ -49,6 +49,8 @@ import android.widget.RemoteViews;
import com.android.internal.R;
import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
+
import libcore.util.Objects;
import java.io.PrintWriter;
@@ -466,7 +468,8 @@ final class FillUi {
pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);
- pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText);
+ pw.print(prefix); pw.print("mFilterText: ");
+ Helper.printlnRedactedText(pw, mFilterText);
pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index 622b8423..e92a5647 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -80,6 +80,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.os.Binder;
import android.os.Build;
@@ -126,7 +127,6 @@ import com.android.server.EventLogTags;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
import libcore.io.IoUtils;
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index 20f23690..a45a4f0c 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -71,6 +71,7 @@ import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.os.Process;
import android.os.RemoteException;
@@ -120,7 +121,6 @@ import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
import com.android.server.backup.utils.SparseArrayUtils;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
import com.google.android.collect.Sets;
diff --git a/com/android/server/backup/restore/ActiveRestoreSession.java b/com/android/server/backup/restore/ActiveRestoreSession.java
index 8d8a0135..a08c19e9 100644
--- a/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -158,16 +158,19 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
MSG_RESTORE_SESSION_TIMEOUT);
long oldId = Binder.clearCallingIdentity();
- backupManagerService.getWakelock().acquire();
- if (MORE_DEBUG) {
- Slog.d(TAG, "restoreAll() kicking off");
+ try {
+ backupManagerService.getWakelock().acquire();
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "restoreAll() kicking off");
+ }
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(mRestoreTransport, dirName,
+ observer, monitor, token);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
- Message msg = backupManagerService.getBackupHandler().obtainMessage(
- MSG_RUN_RESTORE);
- msg.obj = new RestoreParams(mRestoreTransport, dirName,
- observer, monitor, token);
- backupManagerService.getBackupHandler().sendMessage(msg);
- Binder.restoreCallingIdentity(oldId);
return 0;
}
}
diff --git a/com/android/server/connectivity/DefaultNetworkMetrics.java b/com/android/server/connectivity/DefaultNetworkMetrics.java
index 8981db11..28c35858 100644
--- a/com/android/server/connectivity/DefaultNetworkMetrics.java
+++ b/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -18,9 +18,11 @@ package com.android.server.connectivity;
import android.net.LinkProperties;
import android.net.metrics.DefaultNetworkEvent;
-import android.net.metrics.IpConnectivityLog;
+import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.RingBuffer;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.PrintWriter;
@@ -35,19 +37,49 @@ public class DefaultNetworkMetrics {
private static final int ROLLING_LOG_SIZE = 64;
+ public final long creationTimeMs = SystemClock.elapsedRealtime();
+
// Event buffer used for metrics upload. The buffer is cleared when events are collected.
@GuardedBy("this")
private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
+ // Rolling event buffer used for dumpsys and bugreports.
+ @GuardedBy("this")
+ private final RingBuffer<DefaultNetworkEvent> mEventsLog =
+ new RingBuffer(DefaultNetworkEvent.class, ROLLING_LOG_SIZE);
+
+ // Information about the current status of the default network.
+ @GuardedBy("this")
+ private DefaultNetworkEvent mCurrentDefaultNetwork;
+ @GuardedBy("this")
+ private boolean mIsCurrentlyValid;
+ @GuardedBy("this")
+ private long mLastValidationTimeMs;
+ // Transport information about the last default network.
+ @GuardedBy("this")
+ private int mLastTransports;
+
+ public DefaultNetworkMetrics() {
+ newDefaultNetwork(creationTimeMs, null);
+ }
+
public synchronized void listEvents(PrintWriter pw) {
+ pw.println("default network events:");
long localTimeMs = System.currentTimeMillis();
- for (DefaultNetworkEvent ev : mEvents) {
- pw.println(ev);
+ long timeMs = SystemClock.elapsedRealtime();
+ for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
+ printEvent(localTimeMs, pw, ev);
+ }
+ mCurrentDefaultNetwork.updateDuration(timeMs);
+ if (mIsCurrentlyValid) {
+ updateValidationTime(timeMs);
+ mLastValidationTimeMs = timeMs;
}
+ printEvent(localTimeMs, pw, mCurrentDefaultNetwork);
}
public synchronized void listEventsAsProto(PrintWriter pw) {
- for (DefaultNetworkEvent ev : mEvents) {
+ for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
pw.print(IpConnectivityEventBuilder.toProto(ev));
}
}
@@ -59,20 +91,75 @@ public class DefaultNetworkMetrics {
mEvents.clear();
}
- public synchronized void logDefaultNetworkEvent(
- NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
- DefaultNetworkEvent ev = new DefaultNetworkEvent();
- if (newNai != null) {
- ev.netId = newNai.network().netId;
- ev.transportTypes = newNai.networkCapabilities.getTransportTypes();
+ public synchronized void logDefaultNetworkValidity(long timeMs, boolean isValid) {
+ if (!isValid && mIsCurrentlyValid) {
+ mIsCurrentlyValid = false;
+ updateValidationTime(timeMs);
}
- if (prevNai != null) {
- ev.prevNetId = prevNai.network().netId;
- final LinkProperties lp = prevNai.linkProperties;
- ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
- ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+
+ if (isValid && !mIsCurrentlyValid) {
+ mIsCurrentlyValid = true;
+ mLastValidationTimeMs = timeMs;
}
+ }
+
+ private void updateValidationTime(long timeMs) {
+ mCurrentDefaultNetwork.validatedMs += timeMs - mLastValidationTimeMs;
+ }
+
+ public synchronized void logDefaultNetworkEvent(
+ long timeMs, NetworkAgentInfo newNai, NetworkAgentInfo oldNai) {
+ logCurrentDefaultNetwork(timeMs, oldNai);
+ newDefaultNetwork(timeMs, newNai);
+ }
+ private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) {
+ DefaultNetworkEvent ev = mCurrentDefaultNetwork;
+ ev.updateDuration(timeMs);
+ ev.previousTransports = mLastTransports;
+ // oldNai is null if the system had no default network before the transition.
+ if (oldNai != null) {
+ // The system acquired a new default network.
+ fillLinkInfo(ev, oldNai);
+ ev.finalScore = oldNai.getCurrentScore();
+ ev.validatedMs = ev.durationMs;
+ }
+ // Only change transport of the previous default network if the event currently logged
+ // corresponds to an existing default network, and not to the absence of a default network.
+ // This allows to log pairs of transports for successive default networks regardless of
+ // whether or not the system experienced a period without any default network.
+ if (ev.transports != 0) {
+ mLastTransports = ev.transports;
+ }
mEvents.add(ev);
+ mEventsLog.append(ev);
+ }
+
+ private void newDefaultNetwork(long timeMs, NetworkAgentInfo newNai) {
+ DefaultNetworkEvent ev = new DefaultNetworkEvent(timeMs);
+ ev.durationMs = timeMs;
+ // newNai is null if the system has no default network after the transition.
+ if (newNai != null) {
+ fillLinkInfo(ev, newNai);
+ ev.initialScore = newNai.getCurrentScore();
+ if (newNai.lastValidated) {
+ mIsCurrentlyValid = true;
+ mLastValidationTimeMs = timeMs;
+ }
+ }
+ mCurrentDefaultNetwork = ev;
+ }
+
+ private static void fillLinkInfo(DefaultNetworkEvent ev, NetworkAgentInfo nai) {
+ LinkProperties lp = nai.linkProperties;
+ ev.netId = nai.network().netId;
+ ev.transports |= BitUtils.packBits(nai.networkCapabilities.getTransportTypes());
+ ev.ipv4 |= lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
+ ev.ipv6 |= lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+ }
+
+ private static void printEvent(long localTimeMs, PrintWriter pw, DefaultNetworkEvent ev) {
+ long localCreationTimeMs = localTimeMs - ev.durationMs;
+ pw.println(String.format("%tT.%tL: %s", localCreationTimeMs, localCreationTimeMs, ev));
}
}
diff --git a/com/android/server/connectivity/IpConnectivityEventBuilder.java b/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 3d71ecb5..397af7ba 100644
--- a/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -25,6 +25,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import android.net.ConnectivityManager;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
@@ -45,7 +46,6 @@ import android.util.SparseIntArray;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
-import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair;
import java.io.IOException;
import java.util.ArrayList;
@@ -127,6 +127,11 @@ final public class IpConnectivityEventBuilder {
wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups;
wakeupStats.applicationWakeups = in.applicationWakeups;
wakeupStats.noUidWakeups = in.noUidWakeups;
+ wakeupStats.l2UnicastCount = in.l2UnicastCount;
+ wakeupStats.l2MulticastCount = in.l2MulticastCount;
+ wakeupStats.l2BroadcastCount = in.l2BroadcastCount;
+ wakeupStats.ethertypeCounts = toPairArray(in.ethertypes);
+ wakeupStats.ipNextHeaderCounts = toPairArray(in.ipNextHeaders);
final IpConnectivityEvent out = buildEvent(0, 0, in.iface);
out.setWakeupStats(wakeupStats);
return out;
@@ -135,11 +140,17 @@ final public class IpConnectivityEventBuilder {
public static IpConnectivityEvent toProto(DefaultNetworkEvent in) {
IpConnectivityLogClass.DefaultNetworkEvent ev =
new IpConnectivityLogClass.DefaultNetworkEvent();
- ev.networkId = netIdOf(in.netId);
- ev.previousNetworkId = netIdOf(in.prevNetId);
- ev.transportTypes = in.transportTypes;
- ev.previousNetworkIpSupport = ipSupportOf(in);
- final IpConnectivityEvent out = buildEvent(in.netId, 0, null);
+ ev.finalScore = in.finalScore;
+ ev.initialScore = in.initialScore;
+ ev.ipSupport = ipSupportOf(in);
+ ev.defaultNetworkDurationMs = in.durationMs;
+ ev.validationDurationMs = in.validatedMs;
+ ev.previousDefaultNetworkLinkLayer = transportsToLinkLayer(in.previousTransports);
+ final IpConnectivityEvent out = buildEvent(in.netId, in.transports, null);
+ if (in.transports == 0) {
+ // Set link layer to NONE for events representing the absence of a default network.
+ out.linkLayer = IpConnectivityLogClass.NONE;
+ }
out.setDefaultNetworkEvent(ev);
return out;
}
@@ -235,7 +246,6 @@ final public class IpConnectivityEventBuilder {
private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) {
IpConnectivityLogClass.NetworkEvent networkEvent =
new IpConnectivityLogClass.NetworkEvent();
- networkEvent.networkId = netIdOf(in.netId);
networkEvent.eventType = in.eventType;
networkEvent.latencyMs = (int) in.durationMs;
out.setNetworkEvent(networkEvent);
@@ -314,20 +324,14 @@ final public class IpConnectivityEventBuilder {
return pairs;
}
- private static NetworkId netIdOf(int netid) {
- final NetworkId ni = new NetworkId();
- ni.networkId = netid;
- return ni;
- }
-
private static int ipSupportOf(DefaultNetworkEvent in) {
- if (in.prevIPv4 && in.prevIPv6) {
+ if (in.ipv4 && in.ipv6) {
return IpConnectivityLogClass.DefaultNetworkEvent.DUAL;
}
- if (in.prevIPv6) {
+ if (in.ipv6) {
return IpConnectivityLogClass.DefaultNetworkEvent.IPV6;
}
- if (in.prevIPv4) {
+ if (in.ipv4) {
return IpConnectivityLogClass.DefaultNetworkEvent.IPV4;
}
return IpConnectivityLogClass.DefaultNetworkEvent.NONE;
diff --git a/com/android/server/connectivity/IpConnectivityMetrics.java b/com/android/server/connectivity/IpConnectivityMetrics.java
index 24217e6e..f4278196 100644
--- a/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -23,8 +23,6 @@ import android.net.INetdEventCallback;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.IpConnectivityLog;
import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcelable;
import android.os.Process;
import android.provider.Settings;
import android.text.TextUtils;
@@ -45,6 +43,7 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.function.ToIntFunction;
@@ -214,86 +213,66 @@ final public class IpConnectivityMetrics extends SystemService {
}
/**
- * Clears the event buffer and prints its content as a protobuf serialized byte array
+ * Clear the event buffer and prints its content as a protobuf serialized byte array
* inside a base64 encoded string.
*/
- private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
+ private void cmdFlush(PrintWriter pw) {
pw.print(flushEncodedOutput());
}
/**
- * Prints the content of the event buffer, either using the events ASCII representation
- * or using protobuf text format.
+ * Print the content of the rolling event buffer in human readable format.
+ * Also print network dns/connect statistics and recent default network events.
*/
- private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
- final ArrayList<ConnectivityMetricsEvent> events;
- synchronized (mLock) {
- events = new ArrayList(mBuffer);
- }
-
- if (args.length > 1 && args[1].equals("proto")) {
- for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
- pw.print(ev.toString());
- }
- if (mNetdListener != null) {
- mNetdListener.listAsProtos(pw);
- }
- mDefaultNetworkMetrics.listEventsAsProto(pw);
- return;
- }
-
+ private void cmdList(PrintWriter pw) {
+ pw.println("metrics events:");
+ final List<ConnectivityMetricsEvent> events = getEvents();
for (ConnectivityMetricsEvent ev : events) {
pw.println(ev.toString());
}
+ pw.println("");
if (mNetdListener != null) {
mNetdListener.list(pw);
}
+ pw.println("");
mDefaultNetworkMetrics.listEvents(pw);
}
- /**
- * Prints for bug reports the content of the rolling event log and the
- * content of Netd event listener.
+ /*
+ * Print the content of the rolling event buffer in text proto format.
*/
- private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
- final ConnectivityMetricsEvent[] events;
- synchronized (mLock) {
- events = mEventLog.toArray();
- }
- for (ConnectivityMetricsEvent ev : events) {
- pw.println(ev.toString());
+ private void cmdListAsProto(PrintWriter pw) {
+ final List<ConnectivityMetricsEvent> events = getEvents();
+ for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
+ pw.print(ev.toString());
}
if (mNetdListener != null) {
- mNetdListener.list(pw);
+ mNetdListener.listAsProtos(pw);
}
- mDefaultNetworkMetrics.listEvents(pw);
+ mDefaultNetworkMetrics.listEventsAsProto(pw);
}
- private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
+ /*
+ * Return a copy of metrics events stored in buffer for metrics uploading.
+ */
+ private List<ConnectivityMetricsEvent> getEvents() {
synchronized (mLock) {
- pw.println("Buffered events: " + mBuffer.size());
- pw.println("Buffer capacity: " + mCapacity);
- pw.println("Dropped events: " + mDropped);
- }
- if (mNetdListener != null) {
- mNetdListener.dump(pw);
+ return Arrays.asList(mEventLog.toArray());
}
}
- private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (args.length == 0) {
- pw.println("No command");
- return;
- }
- pw.println("Unknown command " + TextUtils.join(" ", args));
- }
-
public final class Impl extends IIpConnectivityMetrics.Stub {
- static final String CMD_FLUSH = "flush";
- static final String CMD_LIST = "list";
- static final String CMD_STATS = "stats";
- static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
- static final String CMD_DEFAULT = CMD_STATS;
+ // Dump and flushes the metrics event buffer in base64 encoded serialized proto output.
+ static final String CMD_FLUSH = "flush";
+ // Dump the rolling buffer of metrics event in human readable proto text format.
+ static final String CMD_PROTO = "proto";
+ // Dump the rolling buffer of metrics event and pretty print events using a human readable
+ // format. Also print network dns/connect statistics and default network event time series.
+ static final String CMD_LIST = "list";
+ // By default any other argument will fall into the default case which is remapped to the
+ // "list" command. This includes most notably bug reports collected by dumpsys.cpp with
+ // the "-a" argument.
+ static final String CMD_DEFAULT = CMD_LIST;
@Override
public int logEvent(ConnectivityMetricsEvent event) {
@@ -308,19 +287,15 @@ final public class IpConnectivityMetrics extends SystemService {
final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
switch (cmd) {
case CMD_FLUSH:
- cmdFlush(fd, pw, args);
+ cmdFlush(pw);
return;
- case CMD_DUMPSYS:
- cmdDumpsys(fd, pw, args);
- return;
- case CMD_LIST:
- cmdList(fd, pw, args);
- return;
- case CMD_STATS:
- cmdStats(fd, pw, args);
+ case CMD_PROTO:
+ cmdListAsProto(pw);
return;
+ case CMD_LIST: // fallthrough
default:
- cmdDefault(fd, pw, args);
+ cmdList(pw);
+ return;
}
}
@@ -345,22 +320,22 @@ final public class IpConnectivityMetrics extends SystemService {
}
@Override
- public boolean registerNetdEventCallback(INetdEventCallback callback) {
+ public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
enforceNetdEventListeningPermission();
if (mNetdListener == null) {
return false;
}
- return mNetdListener.registerNetdEventCallback(callback);
+ return mNetdListener.addNetdEventCallback(callerType, callback);
}
@Override
- public boolean unregisterNetdEventCallback() {
+ public boolean removeNetdEventCallback(int callerType) {
enforceNetdEventListeningPermission();
if (mNetdListener == null) {
// if the service is null, we aren't registered anyway
return true;
}
- return mNetdListener.unregisterNetdEventCallback();
+ return mNetdListener.removeNetdEventCallback(callerType);
}
};
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 05c6e69c..4bdbbe39 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -58,7 +58,6 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
private static final String TAG = NetdEventListenerService.class.getSimpleName();
private static final boolean DBG = false;
- private static final boolean VDBG = false;
// Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
// bursts of 5000 measurements.
@@ -98,21 +97,55 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
@GuardedBy("this")
private final TokenBucket mConnectTb =
new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
- // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
- // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
+
+
+ /**
+ * There are only 2 possible callbacks.
+ *
+ * mNetdEventCallbackList[CALLBACK_CALLER_DEVICE_POLICY].
+ * Callback registered/unregistered when logging is being enabled/disabled in DPM
+ * by the device owner. It's DevicePolicyManager's responsibility to ensure that.
+ *
+ * mNetdEventCallbackList[CALLBACK_CALLER_NETWORK_WATCHLIST]
+ * Callback registered/unregistered by NetworkWatchlistService.
+ */
+ @GuardedBy("this")
+ private static final int[] ALLOWED_CALLBACK_TYPES = {
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY,
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST
+ };
+
@GuardedBy("this")
- private INetdEventCallback mNetdEventCallback;
+ private INetdEventCallback[] mNetdEventCallbackList =
+ new INetdEventCallback[ALLOWED_CALLBACK_TYPES.length];
- public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
- mNetdEventCallback = callback;
+ public synchronized boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+ if (!isValidCallerType(callerType)) {
+ Log.e(TAG, "Invalid caller type: " + callerType);
+ return false;
+ }
+ mNetdEventCallbackList[callerType] = callback;
return true;
}
- public synchronized boolean unregisterNetdEventCallback() {
- mNetdEventCallback = null;
+ public synchronized boolean removeNetdEventCallback(int callerType) {
+ if (!isValidCallerType(callerType)) {
+ Log.e(TAG, "Invalid caller type: " + callerType);
+ return false;
+ }
+ mNetdEventCallbackList[callerType] = null;
return true;
}
+ private static boolean isValidCallerType(int callerType) {
+ for (int i = 0; i < ALLOWED_CALLBACK_TYPES.length; i++) {
+ if (callerType == ALLOWED_CALLBACK_TYPES[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public NetdEventListenerService(Context context) {
this(context.getSystemService(ConnectivityManager.class));
}
@@ -164,13 +197,13 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
throws RemoteException {
- maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
-
long timestamp = System.currentTimeMillis();
getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
- if (mNetdEventCallback != null) {
- mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+ for (INetdEventCallback callback : mNetdEventCallbackList) {
+ if (callback != null) {
+ callback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+ }
}
}
@@ -179,22 +212,23 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
// This method must not block or perform long-running operations.
public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr,
int port, int uid) throws RemoteException {
- maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
-
long timestamp = System.currentTimeMillis();
getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
- if (mNetdEventCallback != null) {
- mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid);
+ for (INetdEventCallback callback : mNetdEventCallbackList) {
+ if (callback != null) {
+ // TODO(rickywai): Remove this checking to collect ip in watchlist.
+ if (callback ==
+ mNetdEventCallbackList[INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY]) {
+ callback.onConnectEvent(ipAddr, port, timestamp, uid);
+ }
+ }
}
}
@Override
- public synchronized void onWakeupEvent(String prefix, int uid, int gid, long timestampNs) {
- maybeVerboseLog("onWakeupEvent(%s, %d, %d, %sns)", prefix, uid, gid, timestampNs);
-
- // TODO: add ip protocol and port
-
+ public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
+ byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, "");
final long timestampMs;
if (timestampNs > 0) {
@@ -203,15 +237,22 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
timestampMs = System.currentTimeMillis();
}
- addWakeupEvent(iface, timestampMs, uid);
- }
-
- @GuardedBy("this")
- private void addWakeupEvent(String iface, long timestampMs, int uid) {
WakeupEvent event = new WakeupEvent();
event.iface = iface;
event.timestampMs = timestampMs;
event.uid = uid;
+ event.ethertype = ethertype;
+ event.dstHwAddr = dstHw;
+ event.srcIp = srcIp;
+ event.dstIp = dstIp;
+ event.ipNextHeader = ipNextHeader;
+ event.srcPort = srcPort;
+ event.dstPort = dstPort;
+ addWakeupEvent(event);
+ }
+
+ private void addWakeupEvent(WakeupEvent event) {
+ String iface = event.iface;
mWakeupEvents.append(event);
WakeupStats stats = mWakeupStats.get(iface);
if (stats == null) {
@@ -243,24 +284,21 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
mWakeupStats.clear();
}
- public synchronized void dump(PrintWriter writer) {
- IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println(TAG + ":");
- pw.increaseIndent();
- list(pw);
- pw.decreaseIndent();
- }
-
public synchronized void list(PrintWriter pw) {
+ pw.println("dns/connect events:");
for (int i = 0; i < mNetworkMetrics.size(); i++) {
pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
}
for (int i = 0; i < mNetworkMetrics.size(); i++) {
pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
}
+ pw.println("");
+ pw.println("network statistics:");
for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
pw.println(s);
}
+ pw.println("");
+ pw.println("packet wakeup events:");
for (int i = 0; i < mWakeupStats.size(); i++) {
pw.println(mWakeupStats.valueAt(i));
}
@@ -294,10 +332,6 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
if (DBG) Log.d(TAG, String.format(s, args));
}
- private static void maybeVerboseLog(String s, Object... args) {
- if (VDBG) Log.d(TAG, String.format(s, args));
- }
-
/** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
static class NetworkMetricsSnapshot {
diff --git a/com/android/server/connectivity/NetworkMonitor.java b/com/android/server/connectivity/NetworkMonitor.java
index 8b886d6b..76840302 100644
--- a/com/android/server/connectivity/NetworkMonitor.java
+++ b/com/android/server/connectivity/NetworkMonitor.java
@@ -1129,7 +1129,8 @@ public class NetworkMonitor extends StateMachine {
}
private void logNetworkEvent(int evtype) {
- mMetricsLog.log(new NetworkEvent(mNetId, evtype));
+ int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+ mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
}
private int networkEventType(ValidationStage s, EvaluationResult r) {
@@ -1150,7 +1151,8 @@ public class NetworkMonitor extends StateMachine {
private void maybeLogEvaluationResult(int evtype) {
if (mEvaluationTimer.isRunning()) {
- mMetricsLog.log(new NetworkEvent(mNetId, evtype, mEvaluationTimer.stop()));
+ int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+ mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
mEvaluationTimer.reset();
}
}
diff --git a/com/android/server/connectivity/Vpn.java b/com/android/server/connectivity/Vpn.java
index a44b18dc..7715727f 100644
--- a/com/android/server/connectivity/Vpn.java
+++ b/com/android/server/connectivity/Vpn.java
@@ -18,6 +18,8 @@ package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -90,6 +92,8 @@ import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.ConnectivityService;
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
import com.android.server.net.BaseNetworkObserver;
@@ -245,10 +249,10 @@ public class Vpn {
}
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, "");
- // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+ updateCapabilities();
loadAlwaysOnPackage();
}
@@ -275,6 +279,62 @@ public class Vpn {
updateAlwaysOnNotification(detailedState);
}
+ public void updateCapabilities() {
+ final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null;
+ updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks,
+ mNetworkCapabilities);
+
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ }
+ }
+
+ @VisibleForTesting
+ public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks,
+ NetworkCapabilities caps) {
+ int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN };
+ int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+ int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+ boolean metered = false;
+ boolean roaming = false;
+
+ if (ArrayUtils.isEmpty(underlyingNetworks)) {
+ // No idea what the underlying networks are; assume sane defaults
+ metered = true;
+ roaming = false;
+ } else {
+ for (Network underlying : underlyingNetworks) {
+ final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying);
+ for (int underlyingType : underlyingCaps.getTransportTypes()) {
+ transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
+ }
+
+ // When we have multiple networks, we have to assume the
+ // worst-case link speed and restrictions.
+ downKbps = NetworkCapabilities.minBandwidth(downKbps,
+ underlyingCaps.getLinkDownstreamBandwidthKbps());
+ upKbps = NetworkCapabilities.minBandwidth(upKbps,
+ underlyingCaps.getLinkUpstreamBandwidthKbps());
+ metered |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_METERED);
+ roaming |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
+ }
+
+ caps.setTransportTypes(transportTypes);
+ caps.setLinkDownstreamBandwidthKbps(downKbps);
+ caps.setLinkUpstreamBandwidthKbps(upKbps);
+ if (metered) {
+ caps.removeCapability(NET_CAPABILITY_NOT_METERED);
+ } else {
+ caps.addCapability(NET_CAPABILITY_NOT_METERED);
+ }
+ if (roaming) {
+ caps.removeCapability(NET_CAPABILITY_NOT_ROAMING);
+ } else {
+ caps.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
+ }
+
/**
* Chooses whether to force all connections to go though VPN.
*
@@ -1344,6 +1404,7 @@ public class Vpn {
}
}
}
+ updateCapabilities();
return true;
}
diff --git a/com/android/server/content/ContentService.java b/com/android/server/content/ContentService.java
index 6e1c21ee..c4e6ff6b 100644
--- a/com/android/server/content/ContentService.java
+++ b/com/android/server/content/ContentService.java
@@ -163,10 +163,6 @@ public final class ContentService extends IContentService.Stub {
};
private SyncManager getSyncManager() {
- if (SystemProperties.getBoolean("config.disable_network", false)) {
- return null;
- }
-
synchronized(mSyncManagerLock) {
try {
// Try to create the SyncManager, return null if it fails (e.g. the disk is full).
diff --git a/com/android/server/coverage/CoverageService.java b/com/android/server/coverage/CoverageService.java
index d600aa8c..ed8d24aa 100644
--- a/com/android/server/coverage/CoverageService.java
+++ b/com/android/server/coverage/CoverageService.java
@@ -112,7 +112,7 @@ public class CoverageService extends Binder {
}
// Try to open the destination file
- ParcelFileDescriptor fd = openOutputFileForSystem(dest);
+ ParcelFileDescriptor fd = openFileForSystem(dest, "w");
if (fd == null) {
return -1;
}
diff --git a/com/android/server/devicepolicy/NetworkLogger.java b/com/android/server/devicepolicy/NetworkLogger.java
index 00859311..0aaf32cb 100644
--- a/com/android/server/devicepolicy/NetworkLogger.java
+++ b/com/android/server/devicepolicy/NetworkLogger.java
@@ -107,7 +107,8 @@ final class NetworkLogger {
return false;
}
try {
- if (mIpConnectivityMetrics.registerNetdEventCallback(mNetdEventCallback)) {
+ if (mIpConnectivityMetrics.addNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
/* allowIo */ false);
mHandlerThread.start();
@@ -138,7 +139,8 @@ final class NetworkLogger {
// logging is forcefully disabled even if unregistering fails
return true;
}
- return mIpConnectivityMetrics.unregisterNetdEventCallback();
+ return mIpConnectivityMetrics.removeNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY);
} catch (RemoteException re) {
Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re);
return true;
diff --git a/com/android/server/display/BrightnessTracker.java b/com/android/server/display/BrightnessTracker.java
new file mode 100644
index 00000000..361d9284
--- /dev/null
+++ b/com/android/server/display/BrightnessTracker.java
@@ -0,0 +1,639 @@
+/*
+ * 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.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.BrightnessChangeEvent;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+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.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.RingBuffer;
+
+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.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+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;
+
+/**
+ * Class that tracks recent brightness settings changes and stores
+ * associated information such as light sensor readings.
+ */
+public class BrightnessTracker {
+
+ private static final String TAG = "BrightnessTracker";
+ private static final boolean DEBUG = false;
+
+ private static final String EVENTS_FILE = "brightness_events.xml";
+ private static final int MAX_EVENTS = 100;
+ // Discard events when reading or writing that are older than this.
+ private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
+ // Time over which we keep lux sensor readings.
+ private static final long LUX_EVENT_HORIZON = TimeUnit.SECONDS.toNanos(10);
+
+ private static final String TAG_EVENTS = "events";
+ private static final String TAG_EVENT = "event";
+ private static final String ATTR_BRIGHTNESS = "brightness";
+ private static final String ATTR_TIMESTAMP = "timestamp";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_USER = "user";
+ private static final String ATTR_LUX = "lux";
+ private static final String ATTR_LUX_TIMESTAMPS = "luxTimestamps";
+ private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
+ private static final String ATTR_NIGHT_MODE = "nightMode";
+ private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature";
+ private static final String ATTR_LAST_BRIGHTNESS = "lastBrightness";
+
+ // Lock held while accessing mEvents, is held while writing events to flash.
+ private final Object mEventsLock = new Object();
+ @GuardedBy("mEventsLock")
+ private RingBuffer<BrightnessChangeEvent> mEvents
+ = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+ private final Runnable mEventsWriter = () -> writeEvents();
+ private volatile boolean mWriteEventsScheduled;
+
+ private UserManager mUserManager;
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private Handler mBgHandler;
+ // mSettingsObserver, mBroadcastReceiver and mSensorListener should only be used on
+ // the mBgHandler thread.
+ private SettingsObserver mSettingsObserver;
+ private BroadcastReceiver mBroadcastReceiver;
+ private SensorListener mSensorListener;
+
+ // Lock held while collecting data related to brightness changes.
+ private final Object mDataCollectionLock = new Object();
+ @GuardedBy("mDataCollectionLock")
+ private Deque<LightData> mLastSensorReadings = new ArrayDeque<>();
+ @GuardedBy("mDataCollectionLock")
+ private float mLastBatteryLevel = Float.NaN;
+ @GuardedBy("mDataCollectionLock")
+ private int mIgnoreBrightness = -1;
+ @GuardedBy("mDataCollectionLock")
+ private int mLastBrightness = -1;
+
+ private final Injector mInjector;
+
+ public BrightnessTracker(Context context, @Nullable Injector injector) {
+ // Note this will be called very early in boot, other system
+ // services may not be present.
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ if (injector != null) {
+ mInjector = injector;
+ } else {
+ mInjector = new Injector();
+ }
+ }
+
+ /** Start listening for brightness slider events */
+ public void start() {
+ if (DEBUG) {
+ Slog.d(TAG, "Start");
+ }
+ mBgHandler = mInjector.getBackgroundHandler();
+ mUserManager = mContext.getSystemService(UserManager.class);
+
+ mBgHandler.post(() -> backgroundStart());
+ }
+
+ private void backgroundStart() {
+ readEvents();
+
+ mLastBrightness = mInjector.getSystemIntForUser(mContentResolver,
+ Settings.System.SCREEN_BRIGHTNESS, -1,
+ UserHandle.USER_CURRENT);
+
+ mSensorListener = new SensorListener();
+ mInjector.registerSensorListener(mContext, mSensorListener);
+
+ mSettingsObserver = new SettingsObserver(mBgHandler);
+ mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
+
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_SHUTDOWN);
+ intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ mBroadcastReceiver = new Receiver();
+ mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+ }
+
+ /** Stop listening for events */
+ @VisibleForTesting
+ void stop() {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop");
+ }
+ mInjector.unregisterSensorListener(mContext, mSensorListener);
+ mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
+ mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+ }
+
+ /**
+ * @param userId userId to fetch data for.
+ * @return List of recent {@link BrightnessChangeEvent}s
+ */
+ public ParceledListSlice<BrightnessChangeEvent> getEvents(int userId) {
+ // TODO include apps from any managed profiles in the brightness information.
+ BrightnessChangeEvent[] events;
+ synchronized (mEventsLock) {
+ events = mEvents.toArray();
+ }
+ ArrayList<BrightnessChangeEvent> out = new ArrayList<>(events.length);
+ for (int i = 0; i < events.length; ++i) {
+ if (events[i].userId == userId) {
+ out.add(events[i]);
+ }
+ }
+ return new ParceledListSlice<>(out);
+ }
+
+ /** Sets brightness without logging the brightness change event */
+ public void setBrightness(int brightness, int userId) {
+ synchronized (mDataCollectionLock) {
+ mIgnoreBrightness = brightness;
+ }
+ mInjector.putSystemIntForUser(mContentResolver, Settings.System.SCREEN_BRIGHTNESS,
+ brightness, userId);
+ }
+
+ private void handleBrightnessChanged() {
+ if (DEBUG) {
+ Slog.d(TAG, "Brightness change");
+ }
+ final BrightnessChangeEvent event = new BrightnessChangeEvent();
+ event.timeStamp = mInjector.currentTimeMillis();
+
+ int brightness = mInjector.getSystemIntForUser(mContentResolver,
+ Settings.System.SCREEN_BRIGHTNESS, -1,
+ UserHandle.USER_CURRENT);
+
+ synchronized (mDataCollectionLock) {
+ int previousBrightness = mLastBrightness;
+ mLastBrightness = brightness;
+
+ if (brightness == -1 || brightness == mIgnoreBrightness) {
+ // Notified of brightness change but no setting or self change so ignore.
+ mIgnoreBrightness = -1;
+ return;
+ }
+
+ final int readingCount = mLastSensorReadings.size();
+ if (readingCount == 0) {
+ // No sensor data so ignore this.
+ return;
+ }
+
+ event.luxValues = new float[readingCount];
+ event.luxTimestamps = new long[readingCount];
+
+ int pos = 0;
+
+ // Convert sensor timestamp in elapsed time nanos to current time millis.
+ long currentTimeMillis = mInjector.currentTimeMillis();
+ long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
+ for (LightData reading : mLastSensorReadings) {
+ event.luxValues[pos] = reading.lux;
+ event.luxTimestamps[pos] = currentTimeMillis -
+ TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
+ ++pos;
+ }
+
+ event.batteryLevel = mLastBatteryLevel;
+ event.lastBrightness = previousBrightness;
+ }
+
+ event.brightness = brightness;
+
+ try {
+ final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
+ event.userId = focusedStack.userId;
+ event.packageName = focusedStack.topActivity.getPackageName();
+ } catch (RemoteException e) {
+ // Really shouldn't be possible.
+ }
+
+ event.nightMode = mInjector.getSecureIntForUser(mContentResolver,
+ Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0, UserHandle.USER_CURRENT)
+ == 1;
+ event.colorTemperature = mInjector.getSecureIntForUser(mContentResolver,
+ Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+ 0, UserHandle.USER_CURRENT);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
+ }
+ synchronized (mEventsLock) {
+ mEvents.append(event);
+ }
+ }
+
+ private void scheduleWriteEvents() {
+ if (!mWriteEventsScheduled) {
+ mBgHandler.post(mEventsWriter);
+ mWriteEventsScheduled = true;
+ }
+ }
+
+ private void writeEvents() {
+ mWriteEventsScheduled = false;
+ // TODO kick off write on handler thread e.g. every 24 hours.
+ synchronized (mEventsLock) {
+ final AtomicFile writeTo = mInjector.getFile();
+ if (writeTo == null) {
+ return;
+ }
+ if (mEvents.isEmpty()) {
+ if (writeTo.exists()) {
+ writeTo.delete();
+ }
+ } else {
+ FileOutputStream output = null;
+ try {
+ output = writeTo.startWrite();
+ writeEventsLocked(output);
+ writeTo.finishWrite(output);
+ } catch (IOException e) {
+ writeTo.failWrite(output);
+ Slog.e(TAG, "Failed to write change mEvents.", e);
+ }
+ }
+ }
+ }
+
+ private void readEvents() {
+ synchronized (mEventsLock) {
+ mEvents.clear();
+ final AtomicFile readFrom = mInjector.getFile();
+ if (readFrom != null && readFrom.exists()) {
+ FileInputStream input = null;
+ try {
+ input = readFrom.openRead();
+ readEventsLocked(input);
+ } catch (IOException e) {
+ readFrom.delete();
+ Slog.e(TAG, "Failed to read change mEvents.", e);
+ } finally {
+ IoUtils.closeQuietly(input);
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mEventsLock")
+ void writeEventsLocked(OutputStream stream) throws IOException {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ out.startTag(null, TAG_EVENTS);
+ BrightnessChangeEvent[] toWrite = mEvents.toArray();
+ if (DEBUG) {
+ Slog.d(TAG, "Writing events " + toWrite.length);
+ }
+ final long timeCutOff = System.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) {
+ 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));
+ out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName);
+ out.attribute(null, ATTR_USER, Integer.toString(userSerialNo));
+ out.attribute(null, ATTR_BATTERY_LEVEL, Float.toString(toWrite[i].batteryLevel));
+ out.attribute(null, ATTR_NIGHT_MODE, Boolean.toString(toWrite[i].nightMode));
+ out.attribute(null, ATTR_COLOR_TEMPERATURE, Integer.toString(
+ toWrite[i].colorTemperature));
+ out.attribute(null, ATTR_LAST_BRIGHTNESS,
+ Integer.toString(toWrite[i].lastBrightness));
+ StringBuilder luxValues = new StringBuilder();
+ StringBuilder luxTimestamps = new StringBuilder();
+ for (int j = 0; j < toWrite[i].luxValues.length; ++j) {
+ if (j > 0) {
+ luxValues.append(',');
+ luxTimestamps.append(',');
+ }
+ luxValues.append(Float.toString(toWrite[i].luxValues[j]));
+ luxTimestamps.append(Long.toString(toWrite[i].luxTimestamps[j]));
+ }
+ out.attribute(null, ATTR_LUX, luxValues.toString());
+ out.attribute(null, ATTR_LUX_TIMESTAMPS, luxTimestamps.toString());
+ out.endTag(null, TAG_EVENT);
+ }
+ }
+ out.endTag(null, TAG_EVENTS);
+ out.endDocument();
+ stream.flush();
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mEventsLock")
+ void readEventsLocked(InputStream stream) throws IOException {
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+ String tag = parser.getName();
+ if (!TAG_EVENTS.equals(tag)) {
+ throw new XmlPullParserException(
+ "Events not found in brightness tracker file " + tag);
+ }
+
+ final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
+
+ parser.next();
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ tag = parser.getName();
+ if (TAG_EVENT.equals(tag)) {
+ BrightnessChangeEvent event = new BrightnessChangeEvent();
+
+ String brightness = parser.getAttributeValue(null, ATTR_BRIGHTNESS);
+ event.brightness = Integer.parseInt(brightness);
+ String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP);
+ event.timeStamp = Long.parseLong(timestamp);
+ event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ String user = parser.getAttributeValue(null, ATTR_USER);
+ event.userId = mInjector.getUserId(mUserManager, Integer.parseInt(user));
+ String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL);
+ event.batteryLevel = Float.parseFloat(batteryLevel);
+ String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE);
+ event.nightMode = Boolean.parseBoolean(nightMode);
+ String colorTemperature =
+ parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE);
+ event.colorTemperature = Integer.parseInt(colorTemperature);
+ String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_BRIGHTNESS);
+ event.lastBrightness = Integer.parseInt(lastBrightness);
+
+ String luxValue = parser.getAttributeValue(null, ATTR_LUX);
+ String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS);
+
+ String[] luxValues = luxValue.split(",");
+ String[] luxTimestamps = luxTimestamp.split(",");
+ if (luxValues.length != luxTimestamps.length) {
+ continue;
+ }
+ event.luxValues = new float[luxValues.length];
+ event.luxTimestamps = new long[luxValues.length];
+ for (int i = 0; i < luxValues.length; ++i) {
+ event.luxValues[i] = Float.parseFloat(luxValues[i]);
+ event.luxTimestamps[i] = Long.parseLong(luxTimestamps[i]);
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG, "Read event " + event.brightness
+ + " " + event.packageName);
+ }
+
+ if (event.userId != -1 && event.timeStamp > timeCutOff
+ && event.luxValues.length > 0) {
+ mEvents.append(event);
+ }
+ }
+ }
+ } catch (NullPointerException | NumberFormatException | XmlPullParserException
+ | IOException e) {
+ // Failed to parse something, just start with an empty event log.
+ mEvents = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+ Slog.e(TAG, "Failed to parse brightness event", e);
+ // Re-throw so we will delete the bad file.
+ throw new IOException("failed to parse file", e);
+ }
+ }
+
+ // Not allowed to keep the SensorEvent so used to copy the data we care about.
+ private static class LightData {
+ public float lux;
+ // Time in elapsedRealtimeNanos
+ public long timestamp;
+ }
+
+ private void recordSensorEvent(SensorEvent event) {
+ long horizon = mInjector.elapsedRealtimeNanos() - LUX_EVENT_HORIZON;
+ synchronized (mDataCollectionLock) {
+ if (DEBUG) {
+ Slog.v(TAG, "Sensor event " + event);
+ }
+ if (!mLastSensorReadings.isEmpty()
+ && event.timestamp < mLastSensorReadings.getLast().timestamp) {
+ // Ignore event that came out of order.
+ return;
+ }
+ LightData data = null;
+ while (!mLastSensorReadings.isEmpty()
+ && mLastSensorReadings.getFirst().timestamp < horizon) {
+ // Remove data that has fallen out of the window.
+ data = mLastSensorReadings.removeFirst();
+ }
+ // We put back the last one we removed so we know how long
+ // the first sensor reading was valid for.
+ if (data != null) {
+ mLastSensorReadings.addFirst(data);
+ }
+
+ data = new LightData();
+ data.timestamp = event.timestamp;
+ data.lux = event.values[0];
+ mLastSensorReadings.addLast(data);
+ }
+ }
+
+ private void batteryLevelChanged(int level, int scale) {
+ synchronized (mDataCollectionLock) {
+ mLastBatteryLevel = (float) level / (float) scale;
+ }
+ }
+
+ private final class SensorListener implements SensorEventListener {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ recordSensorEvent(event);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (DEBUG) {
+ Slog.v(TAG, "settings change " + uri);
+ }
+ // Self change is based on observer passed to notifyObserver, SettingsProvider
+ // passes null so no changes are self changes.
+ handleBrightnessChanged();
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received " + intent.getAction());
+ }
+ String action = intent.getAction();
+ if (Intent.ACTION_SHUTDOWN.equals(action)) {
+ stop();
+ scheduleWriteEvents();
+ } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+ if (level != -1 && scale != 0) {
+ batteryLevelChanged(level, scale);
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ public void registerSensorListener(Context context,
+ SensorEventListener sensorListener) {
+ SensorManager sensorManager = context.getSystemService(SensorManager.class);
+ Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+ sensorManager.registerListener(sensorListener,
+ lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
+ SensorManager sensorManager = context.getSystemService(SensorManager.class);
+ sensorManager.unregisterListener(sensorListener);
+ }
+
+ public void registerBrightnessObserver(ContentResolver resolver,
+ ContentObserver settingsObserver) {
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS),
+ false, settingsObserver, UserHandle.USER_ALL);
+ }
+
+ public void unregisterBrightnessObserver(Context context,
+ ContentObserver settingsObserver) {
+ context.getContentResolver().unregisterContentObserver(settingsObserver);
+ }
+
+ public void registerReceiver(Context context,
+ BroadcastReceiver receiver, IntentFilter filter) {
+ context.registerReceiver(receiver, filter);
+ }
+
+ public void unregisterReceiver(Context context,
+ BroadcastReceiver receiver) {
+ context.unregisterReceiver(receiver);
+ }
+
+ public Handler getBackgroundHandler() {
+ return BackgroundThread.getHandler();
+ }
+
+ public int getSystemIntForUser(ContentResolver resolver, String setting, int defaultValue,
+ int userId) {
+ return Settings.System.getIntForUser(resolver, setting, defaultValue, userId);
+ }
+
+ public void putSystemIntForUser(ContentResolver resolver, String setting, int value,
+ int userId) {
+ Settings.System.putIntForUser(resolver, setting, value, userId);
+ }
+
+ public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
+ int userId) {
+ return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId);
+ }
+
+ public AtomicFile getFile() {
+ return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), EVENTS_FILE));
+ }
+
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ public long elapsedRealtimeNanos() {
+ return SystemClock.elapsedRealtimeNanos();
+ }
+
+ public int getUserSerialNumber(UserManager userManager, int userId) {
+ return userManager.getUserSerialNumber(userId);
+ }
+
+ public int getUserId(UserManager userManager, int userSerialNumber) {
+ return userManager.getUserHandle(userSerialNumber);
+ }
+
+ public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
+ return ActivityManager.getService().getFocusedStackInfo();
+ }
+ }
+}
diff --git a/com/android/server/display/NightDisplayService.java b/com/android/server/display/ColorDisplayService.java
index a7c3ff9e..af8ecadd 100644
--- a/com/android/server/display/NightDisplayService.java
+++ b/com/android/server/display/ColorDisplayService.java
@@ -42,7 +42,7 @@ import android.util.MathUtils;
import android.util.Slog;
import android.view.animation.AnimationUtils;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
@@ -60,10 +60,10 @@ import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MAT
/**
* Tints the display at night.
*/
-public final class NightDisplayService extends SystemService
- implements NightDisplayController.Callback {
+public final class ColorDisplayService extends SystemService
+ implements ColorDisplayController.Callback {
- private static final String TAG = "NightDisplayService";
+ private static final String TAG = "ColorDisplayService";
/**
* The transition time, in milliseconds, for Night Display to turn on/off.
@@ -119,12 +119,12 @@ public final class NightDisplayService extends SystemService
private ContentObserver mUserSetupObserver;
private boolean mBootCompleted;
- private NightDisplayController mController;
+ private ColorDisplayController mController;
private ValueAnimator mColorMatrixAnimator;
private Boolean mIsActivated;
private AutoMode mAutoMode;
- public NightDisplayService(Context context) {
+ public ColorDisplayService(Context context) {
super(context);
mHandler = new Handler(Looper.getMainLooper());
}
@@ -228,7 +228,7 @@ public final class NightDisplayService extends SystemService
Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
// Create a new controller for the current user and start listening for changes.
- mController = new NightDisplayController(getContext(), mCurrentUser);
+ mController = new ColorDisplayController(getContext(), mCurrentUser);
mController.setListener(this);
setCoefficientMatrix(getContext());
@@ -293,9 +293,9 @@ public final class NightDisplayService extends SystemService
mAutoMode = null;
}
- if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
+ if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
mAutoMode = new CustomAutoMode();
- } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+ } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
mAutoMode = new TwilightAutoMode();
}
@@ -463,7 +463,7 @@ public final class NightDisplayService extends SystemService
return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
}
- private abstract class AutoMode implements NightDisplayController.Callback {
+ private abstract class AutoMode implements ColorDisplayController.Callback {
public abstract void onStart();
public abstract void onStop();
diff --git a/com/android/server/display/DisplayManagerService.java b/com/android/server/display/DisplayManagerService.java
index d0a1d9e6..f1e20116 100644
--- a/com/android/server/display/DisplayManagerService.java
+++ b/com/android/server/display/DisplayManagerService.java
@@ -31,9 +31,11 @@ import android.Manifest;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.SensorManager;
+import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayViewport;
@@ -58,6 +60,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.IntArray;
import android.util.Slog;
@@ -139,6 +142,7 @@ public final class DisplayManagerService extends SystemService {
private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
private static final int MSG_REQUEST_TRAVERSAL = 4;
private static final int MSG_UPDATE_VIEWPORT = 5;
+ private static final int MSG_REGISTER_BRIGHTNESS_TRACKER = 6;
private final Context mContext;
private final DisplayManagerHandler mHandler;
@@ -256,6 +260,8 @@ public final class DisplayManagerService extends SystemService {
private final Injector mInjector;
+ private final BrightnessTracker mBrightnessTracker;
+
public DisplayManagerService(Context context) {
this(context, new Injector());
}
@@ -274,6 +280,7 @@ public final class DisplayManagerService extends SystemService {
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
+ mBrightnessTracker = new BrightnessTracker(context, null);
}
public void setupSchedulerPolicies() {
@@ -350,6 +357,7 @@ public final class DisplayManagerService extends SystemService {
}
mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
+ mHandler.sendEmptyMessage(MSG_REGISTER_BRIGHTNESS_TRACKER);
}
@VisibleForTesting
@@ -1352,6 +1360,10 @@ public final class DisplayManagerService extends SystemService {
mTempExternalTouchViewport, mTempVirtualTouchViewports);
break;
}
+
+ case MSG_REGISTER_BRIGHTNESS_TRACKER:
+ mBrightnessTracker.start();
+ break;
}
}
}
@@ -1736,6 +1748,35 @@ public final class DisplayManagerService extends SystemService {
}
}
+ @Override // Binder call
+ public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents() {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.BRIGHTNESS_SLIDER_USAGE,
+ "Permission to read brightness events.");
+ int userId = UserHandle.getUserId(Binder.getCallingUid());
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mBrightnessTracker.getEvents(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public void setBrightness(int brightness) {
+ // STOPSHIP - remove when adaptive brightness controller accepts curves.
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.BRIGHTNESS_SLIDER_USAGE,
+ "Permission to set brightness.");
+ int userId = UserHandle.getUserId(Binder.getCallingUid());
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mBrightnessTracker.setBrightness(brightness, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean validatePackageName(int uid, String packageName) {
if (packageName != null) {
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java
index f930b523..600bc42e 100644
--- a/com/android/server/display/DisplayPowerController.java
+++ b/com/android/server/display/DisplayPowerController.java
@@ -1120,6 +1120,27 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Dismiss the black surface without fanfare.
mPowerState.setColorFadeLevel(1.0f);
mPowerState.dismissColorFade();
+ } else if (target == Display.STATE_ON_SUSPEND) {
+ // Want screen full-power and suspended.
+ // Wait for brightness animation to complete beforehand unless already
+ // suspended because we may not be able to change it after suspension.
+ if (mScreenBrightnessRampAnimator.isAnimating()
+ && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+ return;
+ }
+
+ // If not already suspending, temporarily set the state to on until the
+ // screen on is unblocked, then suspend.
+ if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+ if (!setScreenState(Display.STATE_ON)) {
+ return;
+ }
+ setScreenState(Display.STATE_ON_SUSPEND);
+ }
+
+ // Dismiss the black surface without fanfare.
+ mPowerState.setColorFadeLevel(1.0f);
+ mPowerState.dismissColorFade();
} else {
// Want screen off.
mPendingScreenOff = true;
diff --git a/com/android/server/display/DisplayTransformManager.java b/com/android/server/display/DisplayTransformManager.java
index bef6898a..338e3311 100644
--- a/com/android/server/display/DisplayTransformManager.java
+++ b/com/android/server/display/DisplayTransformManager.java
@@ -29,7 +29,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import java.util.Arrays;
/**
@@ -223,13 +223,13 @@ public class DisplayTransformManager {
}
public boolean setColorMode(int colorMode) {
- if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) {
+ if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
applySaturation(COLOR_SATURATION_NATURAL);
setNativeMode(false);
- } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) {
+ } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
applySaturation(COLOR_SATURATION_BOOSTED);
setNativeMode(false);
- } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) {
+ } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
applySaturation(COLOR_SATURATION_NATURAL);
setNativeMode(true);
}
diff --git a/com/android/server/display/LocalDisplayAdapter.java b/com/android/server/display/LocalDisplayAdapter.java
index d61a418c..eb9ff589 100644
--- a/com/android/server/display/LocalDisplayAdapter.java
+++ b/com/android/server/display/LocalDisplayAdapter.java
@@ -22,6 +22,7 @@ import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import android.content.Context;
+import android.hardware.sidekick.SidekickInternal;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -35,7 +36,6 @@ import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.Surface;
import android.view.SurfaceControl;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -148,6 +148,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
return SurfaceControl.POWER_MODE_DOZE;
case Display.STATE_DOZE_SUSPEND:
return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
+ case Display.STATE_ON_SUSPEND:
+ return SurfaceControl.POWER_MODE_ON_SUSPEND;
default:
return SurfaceControl.POWER_MODE_NORMAL;
}
@@ -170,6 +172,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private int mActiveColorMode;
private boolean mActiveColorModeInvalid;
private Display.HdrCapabilities mHdrCapabilities;
+ private boolean mSidekickActive;
+ private SidekickInternal mSidekickInternal;
private SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
@@ -181,6 +185,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo,
colorModes, activeColorMode);
updateColorModesLocked(colorModes, activeColorMode);
+ mSidekickInternal = LocalServices.getService(SidekickInternal.class);
if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
LightsManager lights = LocalServices.getService(LightsManager.class);
mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
@@ -467,6 +472,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
|| oldState == Display.STATE_DOZE_SUSPEND) {
setDisplayState(Display.STATE_DOZE);
currentState = Display.STATE_DOZE;
+ } else if (state == Display.STATE_ON_SUSPEND
+ || oldState == Display.STATE_ON_SUSPEND) {
+ setDisplayState(Display.STATE_ON);
+ currentState = Display.STATE_ON;
} else {
return; // old state and new state is off
}
@@ -510,16 +519,40 @@ final class LocalDisplayAdapter extends DisplayAdapter {
+ ", state=" + Display.stateToString(state) + ")");
}
+ // We must tell sidekick to stop controlling the display before we
+ // can change its power mode, so do that first.
+ if (mSidekickActive) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER,
+ "SidekickInternal#endDisplayControl");
+ try {
+ mSidekickInternal.endDisplayControl();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ mSidekickActive = false;
+ }
+ final int mode = getPowerModeForState(state);
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
+ "id=" + displayId
+ ", state=" + Display.stateToString(state) + ")");
try {
- final int mode = getPowerModeForState(state);
SurfaceControl.setDisplayPowerMode(token, mode);
Trace.traceCounter(Trace.TRACE_TAG_POWER, "DisplayPowerMode", mode);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
+ // If we're entering a suspended (but not OFF) power state and we
+ // have a sidekick available, tell it now that it can take control.
+ if (Display.isSuspendedState(state) && state != Display.STATE_OFF
+ && mSidekickInternal != null && !mSidekickActive) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER,
+ "SidekickInternal#startDisplayControl");
+ try {
+ mSidekickActive = mSidekickInternal.startDisplayControl(state);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ }
}
private void setDisplayBrightness(int brightness) {
diff --git a/com/android/server/ethernet/EthernetNetworkFactory.java b/com/android/server/ethernet/EthernetNetworkFactory.java
index 2d54fd20..0bbe3b5e 100644
--- a/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -427,6 +427,8 @@ class EthernetNetworkFactory {
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
// We have no useful data on bandwidth. Say 100M up and 100M down. :-(
mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000);
diff --git a/com/android/server/job/JobPackageTracker.java b/com/android/server/job/JobPackageTracker.java
index 025ff0b0..296743b5 100644
--- a/com/android/server/job/JobPackageTracker.java
+++ b/com/android/server/job/JobPackageTracker.java
@@ -16,15 +16,19 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+
import android.app.job.JobInfo;
import android.app.job.JobParameters;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+
import com.android.internal.util.RingBufferIndices;
import com.android.server.job.controllers.JobStatus;
@@ -57,7 +61,7 @@ public final class JobPackageTracker {
public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason) {
int index = mEventIndices.add();
mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
- mEventTimes[index] = SystemClock.elapsedRealtime();
+ mEventTimes[index] = sElapsedRealtimeClock.millis();
mEventUids[index] = uid;
mEventTags[index] = tag;
mEventJobIds[index] = jobId;
@@ -125,9 +129,9 @@ public final class JobPackageTracker {
}
public DataSet() {
- mStartUptimeTime = SystemClock.uptimeMillis();
- mStartElapsedTime = SystemClock.elapsedRealtime();
- mStartClockTime = System.currentTimeMillis();
+ mStartUptimeTime = sUptimeMillisClock.millis();
+ mStartElapsedTime = sElapsedRealtimeClock.millis();
+ mStartClockTime = sSystemClock.millis();
}
private PackageEntry getOrCreateEntry(int uid, String pkg) {
@@ -376,20 +380,20 @@ public final class JobPackageTracker {
}
public void notePending(JobStatus job) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
job.madePending = now;
rebatchIfNeeded(now);
mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
}
public void noteNonpending(JobStatus job) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
rebatchIfNeeded(now);
}
public void noteActive(JobStatus job) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
job.madeActive = now;
rebatchIfNeeded(now);
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
@@ -402,7 +406,7 @@ public final class JobPackageTracker {
}
public void noteInactive(JobStatus job, int stopReason) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
stopReason);
@@ -431,7 +435,7 @@ public final class JobPackageTracker {
if (cur == null && last == null) {
return 0;
}
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
long time = 0;
if (cur != null) {
time += cur.getActiveTime(now) + cur.getPendingTime(now);
@@ -445,8 +449,8 @@ public final class JobPackageTracker {
}
public void dump(PrintWriter pw, String prefix, int filterUid) {
- final long now = SystemClock.uptimeMillis();
- final long nowEllapsed = SystemClock.elapsedRealtime();
+ final long now = sUptimeMillisClock.millis();
+ final long nowEllapsed = sElapsedRealtimeClock.millis();
final DataSet total;
if (mLastDataSets[0] != null) {
total = new DataSet(mLastDataSets[0]);
@@ -470,7 +474,7 @@ public final class JobPackageTracker {
return false;
}
pw.println(" Job history:");
- final long now = SystemClock.elapsedRealtime();
+ final long now = sElapsedRealtimeClock.millis();
for (int i=0; i<size; i++) {
final int index = mEventIndices.indexOf(i);
final int uid = mEventUids[index];
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index 78aa2f94..b5497681 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -19,24 +19,15 @@ package com.android.server.job;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.IUidObserver;
+import android.app.job.IJobScheduler;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
-import android.app.job.IJobScheduler;
import android.app.job.JobWorkItem;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -46,8 +37,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryStats;
@@ -55,8 +46,8 @@ import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Process;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -71,6 +62,7 @@ import android.util.SparseArray;
import android.util.SparseIntArray;
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.util.ArrayUtils;
@@ -93,6 +85,16 @@ import com.android.server.job.controllers.TimeController;
import libcore.util.EmptyArray;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
/**
* Responsible for taking jobs representing work to be performed by a client app, and determining
* based on the criteria specified when that job should be run against the client application's
@@ -117,6 +119,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
+ @VisibleForTesting
+ public static Clock sSystemClock = Clock.systemUTC();
+ @VisibleForTesting
+ public static Clock sUptimeMillisClock = SystemClock.uptimeMillisClock();
+ @VisibleForTesting
+ public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
/** Global local for all job scheduler state. */
final Object mLock = new Object();
@@ -960,7 +968,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
// When we reach clock sanity, recalculate the temporal windows
// of all affected jobs.
- if (mJobs.clockNowValidToInflate(System.currentTimeMillis())) {
+ if (mJobs.clockNowValidToInflate(sSystemClock.millis())) {
Slog.i(TAG, "RTC now valid; recalculating persisted job windows");
// We've done our job now, so stop watching the time.
@@ -1068,7 +1076,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (!jobStatus.isPreparedLocked()) {
Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
}
- jobStatus.enqueueTime = SystemClock.elapsedRealtime();
+ jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
final boolean update = mJobs.add(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
@@ -1156,7 +1164,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
* @see #maybeQueueReadyJobsForExecutionLocked
*/
private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
- final long elapsedNowMillis = SystemClock.elapsedRealtime();
+ final long elapsedNowMillis = sElapsedRealtimeClock.millis();
final JobInfo job = failureToReschedule.getJob();
final long initialBackoffMillis = job.getInitialBackoffMillis();
@@ -1200,7 +1208,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
- failureToReschedule.getLastSuccessfulRunTime(), System.currentTimeMillis());
+ failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
for (int ic=0; ic<mControllers.size(); ic++) {
StateController controller = mControllers.get(ic);
controller.rescheduleForFailureLocked(newJob, failureToReschedule);
@@ -1220,7 +1228,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
* recurring job.
*/
private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
// Compute how much of the period is remaining.
long runEarly = 0L;
@@ -1239,7 +1247,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
newLatestRuntimeElapsed, 0 /* backoffAttempt */,
- System.currentTimeMillis() /* lastSuccessfulRunTime */,
+ sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime());
}
@@ -2367,8 +2375,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
final int filterUidFinal = UserHandle.getAppId(filterUid);
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long nowUptime = SystemClock.uptimeMillis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long nowUptime = sUptimeMillisClock.millis();
synchronized (mLock) {
mConstants.dump(pw);
pw.println();
diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java
index a7e674b5..ac7297e6 100644
--- a/com/android/server/job/JobServiceContext.java
+++ b/com/android/server/job/JobServiceContext.java
@@ -16,11 +16,13 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.app.ActivityManager;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.app.job.JobWorkItem;
import android.content.ComponentName;
import android.content.Context;
@@ -34,7 +36,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Slog;
@@ -202,7 +203,7 @@ public final class JobServiceContext implements ServiceConnection {
mRunningCallback = new JobCallback();
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&
- (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
+ (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
Uri[] triggeredUris = null;
if (job.changedUris != null) {
triggeredUris = new Uri[job.changedUris.size()];
@@ -217,7 +218,7 @@ public final class JobServiceContext implements ServiceConnection {
mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
- mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
+ mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
// 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.
@@ -428,7 +429,7 @@ public final class JobServiceContext implements ServiceConnection {
sb.append("Caller no longer running");
if (cb.mStoppedReason != null) {
sb.append(", last stopped ");
- TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
+ TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
sb.append(" because: ");
sb.append(cb.mStoppedReason);
}
@@ -457,7 +458,7 @@ public final class JobServiceContext implements ServiceConnection {
sb.append("Ignoring timeout of no longer active job");
if (jc.mStoppedReason != null) {
sb.append(", stopped ");
- TimeUtils.formatDuration(SystemClock.elapsedRealtime()
+ TimeUtils.formatDuration(sElapsedRealtimeClock.millis()
- jc.mStoppedTime, sb);
sb.append(" because: ");
sb.append(jc.mStoppedReason);
@@ -740,7 +741,7 @@ public final class JobServiceContext implements ServiceConnection {
private void applyStoppedReasonLocked(String reason) {
if (reason != null && mStoppedReason == null) {
mStoppedReason = reason;
- mStoppedTime = SystemClock.elapsedRealtime();
+ mStoppedTime = sElapsedRealtimeClock.millis();
if (mRunningCallback != null) {
mRunningCallback.mStoppedReason = mStoppedReason;
mRunningCallback.mStoppedTime = mStoppedTime;
@@ -777,7 +778,7 @@ public final class JobServiceContext implements ServiceConnection {
}
Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
- mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
+ mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
}
diff --git a/com/android/server/job/JobStore.java b/com/android/server/job/JobStore.java
index aa9f77c8..1af3b39e 100644
--- a/com/android/server/job/JobStore.java
+++ b/com/android/server/job/JobStore.java
@@ -16,20 +16,23 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+
import android.app.ActivityManager;
import android.app.IActivityManager;
-import android.content.ComponentName;
import android.app.job.JobInfo;
+import android.content.ComponentName;
import android.content.Context;
+import android.net.NetworkRequest;
import android.os.Environment;
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateUtils;
-import android.util.AtomicFile;
import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -37,11 +40,16 @@ import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
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.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -53,10 +61,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
/**
* Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
* reference, so none of the functions in this class should make a copy.
@@ -141,7 +145,7 @@ public final class JobStore {
// a *correct* timestamp, see a bunch of overdue jobs, and run them; then
// settle into normal operation.
mXmlTimestamp = mJobsFile.getLastModifiedTime();
- mRtcGood = (System.currentTimeMillis() > mXmlTimestamp);
+ mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
readJobMapFromDisk(mJobSet, mRtcGood);
}
@@ -161,7 +165,7 @@ public final class JobStore {
*/
public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
final ArrayList<JobStatus> toRemove) {
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
// Find the jobs that need to be fixed up, collecting them for post-iteration
// replacement with their new versions
@@ -323,7 +327,7 @@ public final class JobStore {
private final Runnable mWriteRunnable = new Runnable() {
@Override
public void run() {
- final long startElapsed = SystemClock.elapsedRealtime();
+ final long startElapsed = sElapsedRealtimeClock.millis();
final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
synchronized (mLock) {
// Clone the jobs so we can release the lock before writing.
@@ -338,7 +342,7 @@ public final class JobStore {
}
writeJobsMapImpl(storeCopy);
if (DEBUG) {
- Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
+ Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
- startElapsed) + "ms");
}
}
@@ -454,17 +458,12 @@ public final class JobStore {
*/
private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
- if (jobStatus.needsAnyConnectivity()) {
- out.attribute(null, "connectivity", Boolean.toString(true));
- }
- if (jobStatus.needsMeteredConnectivity()) {
- out.attribute(null, "metered", Boolean.toString(true));
- }
- if (jobStatus.needsUnmeteredConnectivity()) {
- out.attribute(null, "unmetered", Boolean.toString(true));
- }
- if (jobStatus.needsNonRoamingConnectivity()) {
- out.attribute(null, "not-roaming", Boolean.toString(true));
+ if (jobStatus.hasConnectivityConstraint()) {
+ final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+ out.attribute(null, "net-capabilities", Long.toString(
+ BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+ out.attribute(null, "net-transport-types", Long.toString(
+ BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
}
if (jobStatus.hasIdleConstraint()) {
out.attribute(null, "idle", Boolean.toString(true));
@@ -497,8 +496,8 @@ public final class JobStore {
Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
}
- final long nowRTC = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowRTC = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
if (jobStatus.hasDeadlineConstraint()) {
// Wall clock deadline.
final long deadlineWallclock = (utcJobTimes == null)
@@ -538,7 +537,7 @@ public final class JobStore {
*/
private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
long nowElapsed) {
- final long nowWallclock = System.currentTimeMillis();
+ final long nowWallclock = sSystemClock.millis();
final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
: JobStatus.NO_EARLIEST_RUNTIME;
@@ -581,7 +580,7 @@ public final class JobStore {
synchronized (mLock) {
jobs = readJobMapImpl(fis, rtcGood);
if (jobs != null) {
- long now = SystemClock.elapsedRealtime();
+ long now = sElapsedRealtimeClock.millis();
IActivityManager am = ActivityManager.getService();
for (int i=0; i<jobs.size(); i++) {
JobStatus js = jobs.get(i);
@@ -754,7 +753,7 @@ public final class JobStore {
return null;
}
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
if (XML_TAG_PERIODIC.equals(parser.getName())) {
@@ -862,22 +861,38 @@ public final class JobStore {
}
private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
- String val = parser.getAttributeValue(null, "connectivity");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
- }
- val = parser.getAttributeValue(null, "metered");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
- }
- val = parser.getAttributeValue(null, "unmetered");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
- }
- val = parser.getAttributeValue(null, "not-roaming");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
+ String val;
+
+ final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
+ final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
+ if (netCapabilities != null && netTransportTypes != null) {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ // We're okay throwing NFE here; caught by caller
+ request.networkCapabilities.setCapabilities(
+ BitUtils.unpackBits(Long.parseLong(netCapabilities)));
+ request.networkCapabilities.setTransportTypes(
+ BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
+ jobBuilder.setRequiredNetwork(request);
+ } else {
+ // Read legacy values
+ val = parser.getAttributeValue(null, "connectivity");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ }
+ val = parser.getAttributeValue(null, "metered");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
+ }
+ val = parser.getAttributeValue(null, "unmetered");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ }
+ val = parser.getAttributeValue(null, "not-roaming");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
+ }
}
+
val = parser.getAttributeValue(null, "idle");
if (val != null) {
jobBuilder.setRequiresDeviceIdle(true);
@@ -937,8 +952,8 @@ public final class JobStore {
private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
throws NumberFormatException {
// Pull out execution time data.
- final long nowWallclock = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowWallclock = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
diff --git a/com/android/server/job/controllers/AppIdleController.java b/com/android/server/job/controllers/AppIdleController.java
index 39f2a96b..caa85220 100644
--- a/com/android/server/job/controllers/AppIdleController.java
+++ b/com/android/server/job/controllers/AppIdleController.java
@@ -174,7 +174,7 @@ public final class AppIdleController extends StateController {
private final class AppIdleStateChangeListener
extends UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
- public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+ public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
boolean changed = false;
synchronized (mLock) {
if (mAppIdleParoleOn) {
diff --git a/com/android/server/job/controllers/BatteryController.java b/com/android/server/job/controllers/BatteryController.java
index 91119690..76ff8348 100644
--- a/com/android/server/job/controllers/BatteryController.java
+++ b/com/android/server/job/controllers/BatteryController.java
@@ -16,13 +16,14 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -203,7 +204,7 @@ public final class BatteryController extends StateController {
if (Intent.ACTION_BATTERY_LOW.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery life too low to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
// If we get this action, the battery is discharging => it isn't plugged in so
// there's no work to cancel. We track this variable for the case where it is
@@ -213,14 +214,14 @@ public final class BatteryController extends StateController {
} else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery life healthy enough to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mBatteryHealthy = true;
maybeReportNewChargingStateLocked();
} else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Received charging intent, fired @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mCharging = true;
maybeReportNewChargingStateLocked();
diff --git a/com/android/server/job/controllers/ConnectivityController.java b/com/android/server/job/controllers/ConnectivityController.java
index ddee345a..da287691 100644
--- a/com/android/server/job/controllers/ConnectivityController.java
+++ b/com/android/server/job/controllers/ConnectivityController.java
@@ -54,7 +54,6 @@ public final class ConnectivityController extends StateController implements
private final ConnectivityManager mConnManager;
private final NetworkPolicyManager mNetPolicyManager;
private boolean mConnected;
- private boolean mValidated;
@GuardedBy("mLock")
private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
@@ -79,7 +78,7 @@ public final class ConnectivityController extends StateController implements
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
- mConnected = mValidated = false;
+ mConnected = false;
mConnManager.registerDefaultNetworkCallback(mNetworkCallback);
mNetPolicyManager.registerListener(mNetPolicyListener);
@@ -149,32 +148,21 @@ public final class ConnectivityController extends StateController implements
}
private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
+ // TODO: consider matching against non-active networks
+
final int jobUid = jobStatus.getSourceUid();
final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
-
- final NetworkCapabilities capabilities = (network != null)
- ? mConnManager.getNetworkCapabilities(network) : null;
+ final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
final boolean connected = (info != null) && info.isConnected();
- final boolean validated = (capabilities != null)
- && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+ final boolean satisfied = jobStatus.getJob().getRequiredNetwork().networkCapabilities
+ .satisfiedByNetworkCapabilities(capabilities);
final boolean sane = isSane(jobStatus, capabilities);
- final boolean connectionUsable = connected && validated && sane;
-
- final boolean metered = connected && (capabilities != null)
- && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- final boolean unmetered = connected && (capabilities != null)
- && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- final boolean notRoaming = connected && (info != null)
- && !info.isRoaming();
- boolean changed = false;
- changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable);
- changed |= jobStatus.setMeteredConstraintSatisfied(metered);
- changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
- changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
+ final boolean changed = jobStatus
+ .setConnectivityConstraintSatisfied(connected && satisfied && sane);
// Pass along the evaluated network for job to use; prevents race
// conditions as default routes change over time, and opens the door to
@@ -185,17 +173,13 @@ public final class ConnectivityController extends StateController implements
// overall state of connectivity constraint satisfiability.
if (jobUid == Process.SYSTEM_UID) {
mConnected = connected;
- mValidated = validated;
}
if (DEBUG) {
Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
- + " for " + jobStatus + ": usable=" + connectionUsable
- + " connected=" + connected
- + " validated=" + validated
- + " metered=" + metered
- + " unmetered=" + unmetered
- + " notRoaming=" + notRoaming);
+ + " for " + jobStatus + ": connected=" + connected
+ + " satisfied=" + satisfied
+ + " sane=" + sane);
}
return changed;
}
@@ -292,8 +276,6 @@ public final class ConnectivityController extends StateController implements
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.print("Connectivity: connected=");
pw.print(mConnected);
- pw.print(" validated=");
- pw.println(mValidated);
pw.print("Tracking ");
pw.print(mTrackedJobs.size());
pw.println(":");
@@ -304,10 +286,7 @@ public final class ConnectivityController extends StateController implements
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
- pw.print(": C="); pw.print(js.needsAnyConnectivity());
- pw.print(": M="); pw.print(js.needsMeteredConnectivity());
- pw.print(": UM="); pw.print(js.needsUnmeteredConnectivity());
- pw.print(": NR="); pw.println(js.needsNonRoamingConnectivity());
+ pw.print(": "); pw.print(js.getJob().getRequiredNetwork());
}
}
}
diff --git a/com/android/server/job/controllers/IdleController.java b/com/android/server/job/controllers/IdleController.java
index 9eda046f..7bde1744 100644
--- a/com/android/server/job/controllers/IdleController.java
+++ b/com/android/server/job/controllers/IdleController.java
@@ -16,7 +16,7 @@
package com.android.server.job.controllers;
-import java.io.PrintWriter;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -24,7 +24,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -33,6 +32,8 @@ import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
+import java.io.PrintWriter;
+
public final class IdleController extends StateController {
private static final String TAG = "IdleController";
@@ -169,7 +170,7 @@ public final class IdleController extends StateController {
// when the screen goes off or dreaming starts, we schedule the
// alarm that will tell us when we have decided the device is
// truly idle.
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
final long when = nowElapsed + mInactivityIdleThreshold;
if (DEBUG) {
Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
@@ -182,7 +183,7 @@ public final class IdleController extends StateController {
// idle time starts now. Do not set mIdle if screen is on.
if (!mIdle && !mScreenOn) {
if (DEBUG) {
- Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
+ Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
}
mIdle = true;
reportNewIdleState(mIdle);
diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java
index 46ed84e5..a5906cb1 100644
--- a/com/android/server/job/controllers/JobStatus.java
+++ b/com/android/server/job/controllers/JobStatus.java
@@ -16,6 +16,8 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.job.JobInfo;
@@ -25,7 +27,6 @@ import android.content.ComponentName;
import android.net.Network;
import android.net.Uri;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.Time;
import android.util.ArraySet;
@@ -64,19 +65,12 @@ public final class JobStatus {
static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW;
static final int CONSTRAINT_TIMING_DELAY = 1<<31;
static final int CONSTRAINT_DEADLINE = 1<<30;
- static final int CONSTRAINT_UNMETERED = 1<<29;
static final int CONSTRAINT_CONNECTIVITY = 1<<28;
static final int CONSTRAINT_APP_NOT_IDLE = 1<<27;
static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25;
- static final int CONSTRAINT_NOT_ROAMING = 1<<24;
- static final int CONSTRAINT_METERED = 1<<23;
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1<<22;
- static final int CONNECTIVITY_MASK =
- CONSTRAINT_UNMETERED | CONSTRAINT_CONNECTIVITY |
- CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED;
-
// Soft override: ignore constraints like time that don't affect API availability
public static final int OVERRIDE_SOFT = 1;
// Full override: ignore all constraints including API-affecting like connectivity
@@ -264,27 +258,9 @@ public final class JobStatus {
int requiredConstraints = job.getConstraintFlags();
- switch (job.getNetworkType()) {
- case JobInfo.NETWORK_TYPE_NONE:
- // No constraint.
- break;
- case JobInfo.NETWORK_TYPE_ANY:
- requiredConstraints |= CONSTRAINT_CONNECTIVITY;
- break;
- case JobInfo.NETWORK_TYPE_UNMETERED:
- requiredConstraints |= CONSTRAINT_UNMETERED;
- break;
- case JobInfo.NETWORK_TYPE_NOT_ROAMING:
- requiredConstraints |= CONSTRAINT_NOT_ROAMING;
- break;
- case JobInfo.NETWORK_TYPE_METERED:
- requiredConstraints |= CONSTRAINT_METERED;
- break;
- default:
- Slog.w(TAG, "Unrecognized networking constraint " + job.getNetworkType());
- break;
+ if (job.getRequiredNetwork() != null) {
+ requiredConstraints |= CONSTRAINT_CONNECTIVITY;
}
-
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_TIMING_DELAY;
}
@@ -365,7 +341,7 @@ public final class JobStatus {
*/
public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, String tag) {
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
if (job.isPeriodic()) {
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
@@ -610,25 +586,9 @@ public final class JobStatus {
/** Does this job have any sort of networking constraint? */
public boolean hasConnectivityConstraint() {
- return (requiredConstraints&CONNECTIVITY_MASK) != 0;
- }
-
- public boolean needsAnyConnectivity() {
return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
}
- public boolean needsUnmeteredConnectivity() {
- return (requiredConstraints&CONSTRAINT_UNMETERED) != 0;
- }
-
- public boolean needsMeteredConnectivity() {
- return (requiredConstraints&CONSTRAINT_METERED) != 0;
- }
-
- public boolean needsNonRoamingConnectivity() {
- return (requiredConstraints&CONSTRAINT_NOT_ROAMING) != 0;
- }
-
public boolean hasChargingConstraint() {
return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
}
@@ -725,18 +685,6 @@ public final class JobStatus {
return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
}
- boolean setUnmeteredConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_UNMETERED, state);
- }
-
- boolean setMeteredConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_METERED, state);
- }
-
- boolean setNotRoamingConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_NOT_ROAMING, state);
- }
-
boolean setAppNotIdleConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state);
}
@@ -817,12 +765,9 @@ public final class JobStatus {
&& notRestrictedInBg;
}
- static final int CONSTRAINTS_OF_INTEREST =
- CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
- CONSTRAINT_TIMING_DELAY |
- CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
- CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
- CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
+ static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
+ | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
+ | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
// Soft override covers all non-"functional" constraints
static final int SOFT_OVERRIDE_CONSTRAINTS =
@@ -870,15 +815,14 @@ public final class JobStatus {
sb.append(getSourceUid());
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
|| latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
- long now = SystemClock.elapsedRealtime();
+ long now = sElapsedRealtimeClock.millis();
sb.append(" TIME=");
formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
sb.append(":");
formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
}
- if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
- sb.append(" NET=");
- sb.append(job.getNetworkType());
+ if (job.getRequiredNetwork() != null) {
+ sb.append(" NET");
}
if (job.isRequireCharging()) {
sb.append(" CHARGING");
@@ -985,15 +929,6 @@ public final class JobStatus {
if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
pw.print(" CONNECTIVITY");
}
- if ((constraints&CONSTRAINT_UNMETERED) != 0) {
- pw.print(" UNMETERED");
- }
- if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
- pw.print(" NOT_ROAMING");
- }
- if ((constraints&CONSTRAINT_METERED) != 0) {
- pw.print(" METERED");
- }
if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
pw.print(" APP_NOT_IDLE");
}
@@ -1084,11 +1019,13 @@ public final class JobStatus {
pw.print(prefix); pw.println(" Granted URI permissions:");
uriPerms.dump(pw, prefix + " ");
}
- if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
- pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType());
+ if (job.getRequiredNetwork() != null) {
+ pw.print(prefix); pw.print(" Network type: ");
+ pw.println(job.getRequiredNetwork());
}
if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- pw.print(prefix); pw.print(" Network bytes: "); pw.println(totalNetworkBytes);
+ pw.print(prefix); pw.print(" Network bytes: ");
+ pw.println(totalNetworkBytes);
}
if (job.getMinLatencyMillis() != 0) {
pw.print(prefix); pw.print(" Minimum latency: ");
diff --git a/com/android/server/job/controllers/StorageController.java b/com/android/server/job/controllers/StorageController.java
index c24e5639..84782f59 100644
--- a/com/android/server/job/controllers/StorageController.java
+++ b/com/android/server/job/controllers/StorageController.java
@@ -16,11 +16,12 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -154,13 +155,13 @@ public final class StorageController extends StateController {
if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Available storage too low to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mStorageLow = true;
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Available stoage high enough to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mStorageLow = false;
maybeReportNewStorageState();
diff --git a/com/android/server/job/controllers/TimeController.java b/com/android/server/job/controllers/TimeController.java
index ee4c606f..cb9e43a1 100644
--- a/com/android/server/job/controllers/TimeController.java
+++ b/com/android/server/job/controllers/TimeController.java
@@ -16,10 +16,11 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Slog;
@@ -84,7 +85,7 @@ public final class TimeController extends StateController {
// pattern of having a job with a 0 deadline constraint ("run immediately").
// Unlike most controllers, once one of our constraints has been satisfied, it
// will never be unsatisfied (our time base can not go backwards).
- final long nowElapsedMillis = SystemClock.elapsedRealtime();
+ final long nowElapsedMillis = sElapsedRealtimeClock.millis();
if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
return;
} else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
@@ -157,7 +158,7 @@ public final class TimeController extends StateController {
long nextExpiryTime = Long.MAX_VALUE;
int nextExpiryUid = 0;
String nextExpiryPackageName = null;
- final long nowElapsedMillis = SystemClock.elapsedRealtime();
+ final long nowElapsedMillis = sElapsedRealtimeClock.millis();
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
@@ -201,7 +202,7 @@ public final class TimeController extends StateController {
*/
private void checkExpiredDelaysAndResetAlarm() {
synchronized (mLock) {
- final long nowElapsedMillis = SystemClock.elapsedRealtime();
+ final long nowElapsedMillis = sElapsedRealtimeClock.millis();
long nextDelayTime = Long.MAX_VALUE;
int nextDelayUid = 0;
String nextDelayPackageName = null;
@@ -283,7 +284,7 @@ public final class TimeController extends StateController {
}
private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
- final long earliestWakeupTimeElapsed = SystemClock.elapsedRealtime();
+ final long earliestWakeupTimeElapsed = sElapsedRealtimeClock.millis();
if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
return earliestWakeupTimeElapsed;
}
@@ -328,9 +329,9 @@ public final class TimeController extends StateController {
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
pw.print("Alarms: now=");
- pw.print(SystemClock.elapsedRealtime());
+ pw.print(sElapsedRealtimeClock.millis());
pw.println();
pw.print("Next delay alarm in ");
TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
diff --git a/com/android/server/location/ActivityRecognitionProxy.java b/com/android/server/location/ActivityRecognitionProxy.java
index 55222dc3..f82b9761 100644
--- a/com/android/server/location/ActivityRecognitionProxy.java
+++ b/com/android/server/location/ActivityRecognitionProxy.java
@@ -103,51 +103,55 @@ public class ActivityRecognitionProxy {
* Helper function to bind the FusedLocationHardware to the appropriate FusedProvider instance.
*/
private void bindProvider() {
- IBinder binder = mServiceWatcher.getBinder();
- if (binder == null) {
- Log.e(TAG, "Null binder found on connection.");
- return;
- }
- String descriptor;
- try {
- descriptor = binder.getInterfaceDescriptor();
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to get interface descriptor.", e);
- return;
- }
+ if (!mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ String descriptor;
+ try {
+ descriptor = binder.getInterfaceDescriptor();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to get interface descriptor.", e);
+ return;
+ }
- if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
- IActivityRecognitionHardwareWatcher watcher =
- IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
- if (watcher == null) {
- Log.e(TAG, "No watcher found on connection.");
- return;
- }
- if (mInstance == null) {
- // to keep backwards compatibility do not update the watcher when there is no
- // instance available, or it will cause an NPE
- Log.d(TAG, "AR HW instance not available, binding will be a no-op.");
- return;
+ if (IActivityRecognitionHardwareWatcher.class.getCanonicalName()
+ .equals(descriptor)) {
+ IActivityRecognitionHardwareWatcher watcher =
+ IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
+ if (watcher == null) {
+ Log.e(TAG, "No watcher found on connection.");
+ return;
+ }
+ if (mInstance == null) {
+ // to keep backwards compatibility do not update the watcher when there is
+ // no instance available, or it will cause an NPE
+ Log.d(TAG, "AR HW instance not available, binding will be a no-op.");
+ return;
+ }
+ try {
+ watcher.onInstanceChanged(mInstance);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering hardware interface to watcher.", e);
+ }
+ } else if (IActivityRecognitionHardwareClient.class.getCanonicalName()
+ .equals(descriptor)) {
+ IActivityRecognitionHardwareClient client =
+ IActivityRecognitionHardwareClient.Stub.asInterface(binder);
+ if (client == null) {
+ Log.e(TAG, "No client found on connection.");
+ return;
+ }
+ try {
+ client.onAvailabilityChanged(mIsSupported, mInstance);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering hardware interface to client.", e);
+ }
+ } else {
+ Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+ }
}
- try {
- watcher.onInstanceChanged(mInstance);
- } catch (RemoteException e) {
- Log.e(TAG, "Error delivering hardware interface to watcher.", e);
- }
- } else if (IActivityRecognitionHardwareClient.class.getCanonicalName().equals(descriptor)) {
- IActivityRecognitionHardwareClient client =
- IActivityRecognitionHardwareClient.Stub.asInterface(binder);
- if (client == null) {
- Log.e(TAG, "No client found on connection.");
- return;
- }
- try {
- client.onAvailabilityChanged(mIsSupported, mInstance);
- } catch (RemoteException e) {
- Log.e(TAG, "Error delivering hardware interface to client.", e);
- }
- } else {
- Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+ })) {
+ Log.e(TAG, "Null binder found on connection.");
}
}
}
diff --git a/com/android/server/location/FusedProxy.java b/com/android/server/location/FusedProxy.java
index f7fac774..b90f037a 100644
--- a/com/android/server/location/FusedProxy.java
+++ b/com/android/server/location/FusedProxy.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.hardware.location.IFusedLocationHardware;
import android.location.IFusedProvider;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -112,17 +113,18 @@ public final class FusedProxy {
* @param locationHardware The FusedLocationHardware instance to use for the binding operation.
*/
private void bindProvider(IFusedLocationHardware locationHardware) {
- IFusedProvider provider = IFusedProvider.Stub.asInterface(mServiceWatcher.getBinder());
-
- if (provider == null) {
+ if (!mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ IFusedProvider provider = IFusedProvider.Stub.asInterface(binder);
+ try {
+ provider.onFusedLocationHardwareChange(locationHardware);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ })) {
Log.e(TAG, "No instance of FusedProvider found on FusedLocationHardware connected.");
- return;
- }
-
- try {
- provider.onFusedLocationHardwareChange(locationHardware);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
}
}
}
diff --git a/com/android/server/location/GeocoderProxy.java b/com/android/server/location/GeocoderProxy.java
index 422b94b3..9ad4aa19 100644
--- a/com/android/server/location/GeocoderProxy.java
+++ b/com/android/server/location/GeocoderProxy.java
@@ -21,6 +21,7 @@ import android.location.Address;
import android.location.GeocoderParams;
import android.location.IGeocodeProvider;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -63,42 +64,47 @@ public class GeocoderProxy {
return mServiceWatcher.start();
}
- private IGeocodeProvider getService() {
- return IGeocodeProvider.Stub.asInterface(mServiceWatcher.getBinder());
- }
-
public String getConnectedPackageName() {
return mServiceWatcher.getBestPackageName();
}
public String getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
- IGeocodeProvider provider = getService();
- if (provider != null) {
- try {
- return provider.getFromLocation(latitude, longitude, maxResults, params, addrs);
- } catch (RemoteException e) {
- Log.w(TAG, e);
+ final String[] result = new String[] {"Service not Available"};
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
+ try {
+ result[0] = provider.getFromLocation(
+ latitude, longitude, maxResults, params, addrs);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ }
}
- }
- return "Service not Available";
+ });
+ return result[0];
}
public String getFromLocationName(String locationName,
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
- IGeocodeProvider provider = getService();
- if (provider != null) {
- try {
- return provider.getFromLocationName(locationName, lowerLeftLatitude,
- lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
- maxResults, params, addrs);
- } catch (RemoteException e) {
- Log.w(TAG, e);
+ final String[] result = new String[] {"Service not Available"};
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
+ try {
+ result[0] = provider.getFromLocationName(locationName, lowerLeftLatitude,
+ lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+ maxResults, params, addrs);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ }
}
- }
- return "Service not Available";
+ });
+ return result[0];
}
}
diff --git a/com/android/server/location/GeofenceManager.java b/com/android/server/location/GeofenceManager.java
index 2493dfb4..d50ffe96 100644
--- a/com/android/server/location/GeofenceManager.java
+++ b/com/android/server/location/GeofenceManager.java
@@ -145,7 +145,8 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish
*/
private void updateMinInterval() {
mEffectiveMinIntervalMs = Settings.Global.getLong(mResolver,
- Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, DEFAULT_MIN_INTERVAL_MS);
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+ DEFAULT_MIN_INTERVAL_MS);
}
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
diff --git a/com/android/server/location/GeofenceProxy.java b/com/android/server/location/GeofenceProxy.java
index b3a00102..eb47b2f8 100644
--- a/com/android/server/location/GeofenceProxy.java
+++ b/com/android/server/location/GeofenceProxy.java
@@ -116,15 +116,17 @@ public final class GeofenceProxy {
};
private void setGeofenceHardwareInProviderLocked() {
- try {
- IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface(
- mServiceWatcher.getBinder());
- if (provider != null) {
- provider.setGeofenceHardware(mGeofenceHardware);
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ final IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface(binder);
+ try {
+ provider.setGeofenceHardware(mGeofenceHardware);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote Exception: setGeofenceHardwareInProviderLocked: " + e);
+ }
}
- } catch (RemoteException e) {
- Log.e(TAG, "Remote Exception: setGeofenceHardwareInProviderLocked: " + e);
- }
+ });
}
private void setGpsGeofenceLocked() {
diff --git a/com/android/server/location/GnssLocationProvider.java b/com/android/server/location/GnssLocationProvider.java
index 4cf35bc4..0b511f23 100644
--- a/com/android/server/location/GnssLocationProvider.java
+++ b/com/android/server/location/GnssLocationProvider.java
@@ -48,6 +48,7 @@ import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.os.BatteryStats;
import android.os.Binder;
@@ -84,7 +85,6 @@ import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.server.power.BatterySaverPolicy;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
import libcore.io.IoUtils;
@@ -803,18 +803,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
}
};
mGnssMetrics = new GnssMetrics();
-
- /*
- * A cycle of native_init() and native_cleanup() is needed so that callbacks are registered
- * after bootup even when location is disabled. This will allow Emergency SUPL to work even
- * when location is disabled before device restart.
- * */
- boolean isInitialized = native_init();
- if(!isInitialized) {
- Log.d(TAG, "Failed to initialize at bootup");
- } else {
- native_cleanup();
- }
}
/**
@@ -1373,24 +1361,26 @@ public class GnssLocationProvider implements LocationProviderInterface {
public boolean sendExtraCommand(String command, Bundle extras) {
long identity = Binder.clearCallingIdentity();
- boolean result = false;
+ try {
+ boolean result = false;
- if ("delete_aiding_data".equals(command)) {
- result = deleteAidingData(extras);
- } else if ("force_time_injection".equals(command)) {
- requestUtcTime();
- result = true;
- } else if ("force_xtra_injection".equals(command)) {
- if (mSupportsXtra) {
- xtraDownloadRequest();
+ if ("delete_aiding_data".equals(command)) {
+ result = deleteAidingData(extras);
+ } else if ("force_time_injection".equals(command)) {
+ requestUtcTime();
result = true;
+ } else if ("force_xtra_injection".equals(command)) {
+ if (mSupportsXtra) {
+ xtraDownloadRequest();
+ result = true;
+ }
+ } else {
+ Log.w(TAG, "sendExtraCommand: unknown command " + command);
}
- } else {
- Log.w(TAG, "sendExtraCommand: unknown command " + command);
+ return result;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
-
- Binder.restoreCallingIdentity(identity);
- return result;
}
private IGpsGeofenceHardware mGpsGeofenceBinder = new IGpsGeofenceHardware.Stub() {
@@ -2272,6 +2262,19 @@ public class GnssLocationProvider implements LocationProviderInterface {
* this handler.
*/
private void handleInitialize() {
+ /*
+ * A cycle of native_init() and native_cleanup() is needed so that callbacks are
+ * registered after bootup even when location is disabled.
+ * This will allow Emergency SUPL to work even when location is disabled before device
+ * restart.
+ */
+ boolean isInitialized = native_init();
+ if(!isInitialized) {
+ Log.w(TAG, "Native initialization failed at bootup");
+ } else {
+ native_cleanup();
+ }
+
// load default GPS configuration
// (this configuration might change in the future based on SIM changes)
reloadGpsProperties(mContext, mProperties);
diff --git a/com/android/server/location/LocationProviderProxy.java b/com/android/server/location/LocationProviderProxy.java
index b44087c0..16eae620 100644
--- a/com/android/server/location/LocationProviderProxy.java
+++ b/com/android/server/location/LocationProviderProxy.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.WorkSource;
import android.util.Log;
@@ -82,10 +83,6 @@ public class LocationProviderProxy implements LocationProviderInterface {
return mServiceWatcher.start();
}
- private ILocationProvider getService() {
- return ILocationProvider.Stub.asInterface(mServiceWatcher.getBinder());
- }
-
public String getConnectedPackageName() {
return mServiceWatcher.getBestPackageName();
}
@@ -101,43 +98,46 @@ public class LocationProviderProxy implements LocationProviderInterface {
if (D) Log.d(TAG, "applying state to connected service");
boolean enabled;
- ProviderProperties properties = null;
+ final ProviderProperties[] properties = new ProviderProperties[1];
ProviderRequest request;
WorkSource source;
- ILocationProvider service;
synchronized (mLock) {
enabled = mEnabled;
request = mRequest;
source = mWorksource;
- service = getService();
}
- if (service == null) return;
- try {
- // load properties from provider
- properties = service.getProperties();
- if (properties == null) {
- Log.e(TAG, mServiceWatcher.getBestPackageName() +
- " has invalid locatino provider properties");
- }
-
- // apply current state to new service
- if (enabled) {
- service.enable();
- if (request != null) {
- service.setRequest(request, source);
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ // load properties from provider
+ properties[0] = service.getProperties();
+ if (properties[0] == null) {
+ Log.e(TAG, mServiceWatcher.getBestPackageName() +
+ " has invalid location provider properties");
+ }
+
+ // apply current state to new service
+ if (enabled) {
+ service.enable();
+ if (request != null) {
+ service.setRequest(request, source);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ } catch (Exception e) {
+ // never let remote service crash system server
+ Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
}
}
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } catch (Exception e) {
- // never let remote service crash system server
- Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
- }
+ });
synchronized (mLock) {
- mProperties = properties;
+ mProperties = properties[0];
}
}
};
@@ -159,17 +159,20 @@ public class LocationProviderProxy implements LocationProviderInterface {
synchronized (mLock) {
mEnabled = true;
}
- ILocationProvider service = getService();
- if (service == null) return;
-
- try {
- service.enable();
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } catch (Exception e) {
- // never let remote service crash system server
- Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
- }
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ service.enable();
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ } catch (Exception e) {
+ // never let remote service crash system server
+ Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+ }
+ }
+ });
}
@Override
@@ -177,17 +180,20 @@ public class LocationProviderProxy implements LocationProviderInterface {
synchronized (mLock) {
mEnabled = false;
}
- ILocationProvider service = getService();
- if (service == null) return;
-
- try {
- service.disable();
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } catch (Exception e) {
- // never let remote service crash system server
- Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
- }
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ service.disable();
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ } catch (Exception e) {
+ // never let remote service crash system server
+ Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+ }
+ }
+ });
}
@Override
@@ -203,17 +209,20 @@ public class LocationProviderProxy implements LocationProviderInterface {
mRequest = request;
mWorksource = source;
}
- ILocationProvider service = getService();
- if (service == null) return;
-
- try {
- service.setRequest(request, source);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } catch (Exception e) {
- // never let remote service crash system server
- Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
- }
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ service.setRequest(request, source);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ } catch (Exception e) {
+ // never let remote service crash system server
+ Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+ }
+ }
+ });
}
@Override
@@ -223,66 +232,78 @@ public class LocationProviderProxy implements LocationProviderInterface {
pw.append(" pkg=").append(mServiceWatcher.getBestPackageName());
pw.append(" version=").append("" + mServiceWatcher.getBestVersion());
pw.append('\n');
-
- ILocationProvider service = getService();
- if (service == null) {
+ if (!mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ TransferPipe.dumpAsync(service.asBinder(), fd, args);
+ } catch (IOException | RemoteException e) {
+ pw.println("Failed to dump location provider: " + e);
+ }
+ }
+ })) {
pw.println("service down (null)");
- return;
- }
- pw.flush();
-
- try {
- TransferPipe.dumpAsync(service.asBinder(), fd, args);
- } catch (IOException | RemoteException e) {
- pw.println("Failed to dump location provider: " + e);
}
}
@Override
public int getStatus(Bundle extras) {
- ILocationProvider service = getService();
- if (service == null) return LocationProvider.TEMPORARILY_UNAVAILABLE;
-
- try {
- return service.getStatus(extras);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } catch (Exception e) {
- // never let remote service crash system server
- Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
- }
- return LocationProvider.TEMPORARILY_UNAVAILABLE;
+ final int[] result = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE};
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ result[0] = service.getStatus(extras);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ } catch (Exception e) {
+ // never let remote service crash system server
+ Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+ }
+ }
+ });
+ return result[0];
}
@Override
public long getStatusUpdateTime() {
- ILocationProvider service = getService();
- if (service == null) return 0;
-
- try {
- return service.getStatusUpdateTime();
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } catch (Exception e) {
- // never let remote service crash system server
- Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
- }
- return 0;
+ final long[] result = new long[] {0L};
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ result[0] = service.getStatusUpdateTime();
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ } catch (Exception e) {
+ // never let remote service crash system server
+ Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+ }
+ }
+ });
+ return result[0];
}
@Override
public boolean sendExtraCommand(String command, Bundle extras) {
- ILocationProvider service = getService();
- if (service == null) return false;
-
- try {
- return service.sendExtraCommand(command, extras);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } catch (Exception e) {
- // never let remote service crash system server
- Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
- }
- return false;
+ final boolean[] result = new boolean[] {false};
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ @Override
+ public void run(IBinder binder) {
+ ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+ try {
+ result[0] = service.sendExtraCommand(command, extras);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ } catch (Exception e) {
+ // never let remote service crash system server
+ Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+ }
+ }
+ });
+ return result[0];
}
}
diff --git a/com/android/server/locksettings/SyntheticPasswordCrypto.java b/com/android/server/locksettings/SyntheticPasswordCrypto.java
index b7bca1fb..ef94000d 100644
--- a/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -112,7 +112,7 @@ public class SyntheticPasswordCrypto {
}
}
- public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
+ public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
@@ -120,6 +120,20 @@ public class SyntheticPasswordCrypto {
SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
return decrypt(decryptionKey, intermediate);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException("Failed to decrypt blob", e);
+ }
+ }
+
+ public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+
+ SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
+ byte[] intermediate = decrypt(decryptionKey, blob);
+ return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
| KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -150,9 +164,8 @@ public class SyntheticPasswordCrypto {
keyStore.setEntry(keyAlias,
new KeyStore.SecretKeyEntry(secretKey),
builder.build());
- byte[] intermediate = encrypt(secretKey, data);
- return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
-
+ byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
+ return encrypt(secretKey, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
| KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 9440f171..1a1aa569 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -101,7 +101,8 @@ public class SyntheticPasswordManager {
private static final byte WEAVER_VERSION = 1;
private static final int INVALID_WEAVER_SLOT = -1;
- private static final byte SYNTHETIC_PASSWORD_VERSION = 1;
+ private static final byte SYNTHETIC_PASSWORD_VERSION_V1 = 1;
+ private static final byte SYNTHETIC_PASSWORD_VERSION = 2;
private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
@@ -389,11 +390,9 @@ public class SyntheticPasswordManager {
}
public void removeUser(int userId) {
- if (isWeaverAvailable()) {
- for (long handle : mStorage.listSyntheticPasswordHandlesForUser(WEAVER_SLOT_NAME,
- userId)) {
- destroyWeaverSlot(handle, userId);
- }
+ for (long handle : mStorage.listSyntheticPasswordHandlesForUser(SP_BLOB_NAME, userId)) {
+ destroyWeaverSlot(handle, userId);
+ destroySPBlobKey(getHandleName(handle));
}
}
@@ -792,6 +791,7 @@ public class SyntheticPasswordManager {
byte[] pwdToken = computePasswordToken(credential, pwd);
final byte[] applicationId;
+ final long sid;
int weaverSlot = loadWeaverSlot(handle, userId);
if (weaverSlot != INVALID_WEAVER_SLOT) {
// Weaver based user password
@@ -804,6 +804,7 @@ public class SyntheticPasswordManager {
if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
return result;
}
+ sid = GateKeeper.INVALID_SECURE_USER_ID;
applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
} else {
byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
@@ -836,12 +837,13 @@ public class SyntheticPasswordManager {
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
+ sid = sidFromPasswordHandle(pwd.passwordHandle);
applicationId = transformUnderSecdiscardable(pwdToken,
loadSecdiscardable(handle, userId));
}
result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
- applicationId, userId);
+ applicationId, sid, userId);
// Perform verifyChallenge to refresh auth tokens for GK if user password exists.
result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
@@ -877,7 +879,7 @@ public class SyntheticPasswordManager {
}
byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
- applicationId, userId);
+ applicationId, 0L, userId);
if (result.authToken != null) {
result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
if (result.gkResponse == null) {
@@ -892,19 +894,26 @@ public class SyntheticPasswordManager {
}
private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
- byte[] applicationId, int userId) {
+ byte[] applicationId, long sid, int userId) {
byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
if (blob == null) {
return null;
}
- if (blob[0] != SYNTHETIC_PASSWORD_VERSION) {
+ final byte version = blob[0];
+ if (version != SYNTHETIC_PASSWORD_VERSION && version != SYNTHETIC_PASSWORD_VERSION_V1) {
throw new RuntimeException("Unknown blob version");
}
if (blob[1] != type) {
throw new RuntimeException("Invalid blob type");
}
- byte[] secret = decryptSPBlob(getHandleName(handle),
+ final byte[] secret;
+ if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
+ secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
+ Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+ } else {
+ secret = decryptSPBlob(getHandleName(handle),
Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+ }
if (secret == null) {
Log.e(TAG, "Fail to decrypt SP for user " + userId);
return null;
@@ -919,6 +928,10 @@ public class SyntheticPasswordManager {
} else {
result.syntheticPassword = new String(secret);
}
+ if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
+ Log.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
+ createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
+ }
return result;
}
diff --git a/com/android/server/media/AudioPlaybackMonitor.java b/com/android/server/media/AudioPlaybackMonitor.java
deleted file mode 100644
index 791ee821..00000000
--- a/com/android/server/media/AudioPlaybackMonitor.java
+++ /dev/null
@@ -1,289 +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.server.media;
-
-import android.content.Context;
-import android.media.AudioManager.AudioPlaybackCallback;
-import android.media.AudioPlaybackConfiguration;
-import android.media.IAudioService;
-import android.media.IPlaybackConfigDispatcher;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.IntArray;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Monitors changes in audio playback, and notify the newly started audio playback through the
- * {@link OnAudioPlaybackStartedListener} and the activeness change through the
- * {@link OnAudioPlaybackActiveStateListener}.
- */
-class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
- private static boolean DEBUG = MediaSessionService.DEBUG;
- private static String TAG = "AudioPlaybackMonitor";
-
- private static AudioPlaybackMonitor sInstance;
-
- /**
- * Called when audio playback is started for a given UID.
- */
- interface OnAudioPlaybackStartedListener {
- void onAudioPlaybackStarted(int uid);
- }
-
- /**
- * Called when audio player state is changed.
- */
- interface OnAudioPlayerActiveStateChangedListener {
- void onAudioPlayerActiveStateChanged(int uid, boolean active);
- }
-
- private final Object mLock = new Object();
- private final Context mContext;
- private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
- = new ArrayList<>();
- private final List<OnAudioPlayerActiveStateChangedListener>
- mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
- private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
- private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
-
- // 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.
- private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
-
- static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
- if (sInstance == null) {
- sInstance = new AudioPlaybackMonitor(context, audioService);
- }
- return sInstance;
- }
-
- private AudioPlaybackMonitor(Context context, IAudioService audioService) {
- mContext = context;
- try {
- audioService.registerPlaybackCallback(this);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failed to register playback callback", e);
- }
- }
-
- /**
- * Called when the {@link AudioPlaybackConfiguration} is updated.
- * <p>If an app starts audio playback, the app's local media session will be the media button
- * session. If the app has multiple media sessions, the playback active local session will be
- * picked.
- *
- * @param configs List of the current audio playback configuration
- */
- @Override
- public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
- boolean flush) {
- if (flush) {
- Binder.flushPendingCommands();
- }
- final long token = Binder.clearCallingIdentity();
- try {
- List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
- List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
- List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
- synchronized (mLock) {
- // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
- // and find newly activated audio playbacks.
- mActiveAudioPlaybackClientUids.clear();
- for (AudioPlaybackConfiguration config : configs) {
- // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
- // (i.e. playback from the SoundPool class which is only for sound effects)
- // playback.
- // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
- // specific audio/video players.
- if (!config.isActive() || config.getPlayerType()
- == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
- continue;
- }
-
- mActiveAudioPlaybackClientUids.add(config.getClientUid());
- Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
- if (!isActiveState(oldState)) {
- if (DEBUG) {
- Log.d(TAG, "Found a new active media playback. " +
- AudioPlaybackConfiguration.toLogFriendlyString(config));
- }
- // New active audio playback.
- newActiveAudioPlaybackClientUids.add(config.getClientUid());
- int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
- if (index == 0) {
- // It's the lastly played music app already. Skip updating.
- continue;
- } else if (index > 0) {
- mSortedAudioPlaybackClientUids.remove(index);
- }
- mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
- }
- }
- audioPlayerActiveStateChangedListeners = new ArrayList<>(
- mAudioPlayerActiveStateChangedListeners);
- audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
- }
- // Notify the change of audio playback states.
- for (AudioPlaybackConfiguration config : configs) {
- boolean wasActive = isActiveState(
- mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
- boolean isActive = config.isActive();
- if (wasActive != isActive) {
- for (OnAudioPlayerActiveStateChangedListener listener
- : audioPlayerActiveStateChangedListeners) {
- listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
- isActive);
- }
- }
- }
- // Notify the start of audio playback
- for (int uid : newActiveAudioPlaybackClientUids) {
- for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
- listener.onAudioPlaybackStarted(uid);
- }
- }
- mAudioPlaybackStates.clear();
- for (AudioPlaybackConfiguration config : configs) {
- mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Registers OnAudioPlaybackStartedListener.
- */
- public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
- synchronized (mLock) {
- mAudioPlaybackStartedListeners.add(listener);
- }
- }
-
- /**
- * Unregisters OnAudioPlaybackStartedListener.
- */
- public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
- synchronized (mLock) {
- mAudioPlaybackStartedListeners.remove(listener);
- }
- }
-
- /**
- * Registers OnAudioPlayerActiveStateChangedListener.
- */
- public void registerOnAudioPlayerActiveStateChangedListener(
- OnAudioPlayerActiveStateChangedListener listener) {
- synchronized (mLock) {
- mAudioPlayerActiveStateChangedListeners.add(listener);
- }
- }
-
- /**
- * Unregisters OnAudioPlayerActiveStateChangedListener.
- */
- public void unregisterOnAudioPlayerActiveStateChangedListener(
- OnAudioPlayerActiveStateChangedListener listener) {
- synchronized (mLock) {
- mAudioPlayerActiveStateChangedListeners.remove(listener);
- }
- }
-
- /**
- * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
- * audio/video) The UID whose audio playback becomes active at the last comes first.
- */
- public IntArray getSortedAudioPlaybackClientUids() {
- IntArray sortedAudioPlaybackClientUids = new IntArray();
- synchronized (mLock) {
- sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
- }
- return sortedAudioPlaybackClientUids;
- }
-
- /**
- * Returns if the audio playback is active for the uid.
- */
- public boolean isPlaybackActive(int uid) {
- synchronized (mLock) {
- return mActiveAudioPlaybackClientUids.contains(uid);
- }
- }
-
- /**
- * Cleans up the sorted list of audio playback client UIDs with given {@param
- * mediaButtonSessionUid}.
- * <p>UIDs whose audio playback started after the media button session's audio playback
- * cannot be the lastly played media app. So they won't needed anymore.
- *
- * @param mediaButtonSessionUid UID of the media button session.
- */
- public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mediaButtonSessionUid);
- for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
- if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
- break;
- }
- int uid = mSortedAudioPlaybackClientUids.get(i);
- if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
- // Clean up unnecessary UIDs.
- // It doesn't need to be managed profile aware because it's just to prevent
- // the list from increasing indefinitely. The media button session updating
- // shouldn't be affected by cleaning up.
- mSortedAudioPlaybackClientUids.remove(i);
- }
- }
- }
- }
-
- /**
- * Dumps {@link AudioPlaybackMonitor}.
- */
- public void dump(PrintWriter pw, String prefix) {
- synchronized (mLock) {
- pw.println(prefix + "Audio playback (lastly played comes first)");
- String indent = prefix + " ";
- for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
- int uid = mSortedAudioPlaybackClientUids.get(i);
- pw.print(indent + "uid=" + uid + " packages=");
- String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages != null && packages.length > 0) {
- for (int j = 0; j < packages.length; j++) {
- pw.print(packages[j] + " ");
- }
- }
- pw.println();
- }
- }
- }
-
- private boolean isActiveState(Integer state) {
- return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
- }
-}
diff --git a/com/android/server/media/AudioPlayerStateMonitor.java b/com/android/server/media/AudioPlayerStateMonitor.java
new file mode 100644
index 00000000..be223f1e
--- /dev/null
+++ b/com/android/server/media/AudioPlayerStateMonitor.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+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;
+
+/**
+ * Monitors the state changes of audio players.
+ */
+class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
+ private static boolean DEBUG = MediaSessionService.DEBUG;
+ private static String TAG = "AudioPlayerStateMonitor";
+
+ private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
+
+ /**
+ * Called when the state of audio player is changed.
+ */
+ interface OnAudioPlayerStateChangedListener {
+ void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+ }
+
+ private final static class MessageHandler extends Handler {
+ private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+
+ private final OnAudioPlayerStateChangedListener mListsner;
+
+ public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+ super(looper);
+ mListsner = 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);
+ break;
+ }
+ }
+
+ public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
+ AudioPlaybackConfiguration config) {
+ obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+ }
+ }
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
+ new HashMap<>();
+ @GuardedBy("mLock")
+ private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+ @GuardedBy("mLock")
+ private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+ // 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.
+ @GuardedBy("mLock")
+ private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+ @GuardedBy("mLock")
+ private boolean mRegisteredToAudioService;
+
+ static AudioPlayerStateMonitor getInstance() {
+ return sInstance;
+ }
+
+ private AudioPlayerStateMonitor() {
+ }
+
+ /**
+ * Called when the {@link AudioPlaybackConfiguration} is updated.
+ * <p>If an app starts audio playback, the app's local media session will be the media button
+ * session. If the app has multiple media sessions, the playback active local session will be
+ * picked.
+ *
+ * @param configs List of the current audio playback configuration
+ */
+ @Override
+ public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+ boolean flush) {
+ if (flush) {
+ Binder.flushPendingCommands();
+ }
+ 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();
+ 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);
+ }
+ }
+ for (AudioPlaybackConfiguration config : configs) {
+ if (!config.isActive()) {
+ continue;
+ }
+
+ int uid = config.getClientUid();
+ if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+ if (DEBUG) {
+ Log.d(TAG, "Found a new active media playback. " +
+ AudioPlaybackConfiguration.toLogFriendlyString(config));
+ }
+ // New active audio playback.
+ int index = mSortedAudioPlaybackClientUids.indexOf(uid);
+ if (index == 0) {
+ // It's the lastly played music app already. Skip updating.
+ continue;
+ } else if (index > 0) {
+ mSortedAudioPlaybackClientUids.remove(index);
+ }
+ mSortedAudioPlaybackClientUids.add(0, uid);
+ }
+ }
+ // Notify the change of audio player states.
+ 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);
+ }
+ }
+ 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);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Registers OnAudioPlayerStateChangedListener.
+ */
+ public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+ synchronized (mLock) {
+ mListenerMap.put(listener, new MessageHandler((handler == null) ?
+ Looper.myLooper() : handler.getLooper(), listener));
+ }
+ }
+
+ /**
+ * Unregisters OnAudioPlayerStateChangedListener.
+ */
+ public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+ synchronized (mLock) {
+ mListenerMap.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
+ * audio/video) The UID whose audio playback becomes active at the last comes first.
+ */
+ public IntArray getSortedAudioPlaybackClientUids() {
+ IntArray sortedAudioPlaybackClientUids = new IntArray();
+ synchronized (mLock) {
+ sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+ }
+ return sortedAudioPlaybackClientUids;
+ }
+
+ /**
+ * Returns if the audio playback is active for the uid.
+ */
+ 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;
+ }
+ }
+
+ /**
+ * Cleans up the sorted list of audio playback client UIDs with given {@param
+ * mediaButtonSessionUid}.
+ * <p>UIDs whose audio playback are inactive and have started before the media button session's
+ * audio playback cannot be the lastly played media app. So they won't needed anymore.
+ *
+ * @param mediaButtonSessionUid UID of the media button session.
+ */
+ public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mediaButtonSessionUid);
+ for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+ if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+ break;
+ }
+ int uid = mSortedAudioPlaybackClientUids.get(i);
+ if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+ // Clean up unnecessary UIDs.
+ // It doesn't need to be managed profile aware because it's just to prevent
+ // the list from increasing indefinitely. The media button session updating
+ // shouldn't be affected by cleaning up.
+ mSortedAudioPlaybackClientUids.remove(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Dumps {@link AudioPlayerStateMonitor}.
+ */
+ public void dump(Context context, PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + "Audio playback (lastly played comes first)");
+ String indent = prefix + " ";
+ for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+ int uid = mSortedAudioPlaybackClientUids.get(i);
+ pw.print(indent + "uid=" + uid + " packages=");
+ String[] packages = context.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ for (int j = 0; j < packages.length; j++) {
+ pw.print(packages[j] + " ");
+ }
+ }
+ pw.println();
+ }
+ }
+ }
+
+ public void registerSelfIntoAudioServiceIfNeeded(IAudioService audioService) {
+ synchronized (mLock) {
+ try {
+ if (!mRegisteredToAudioService) {
+ audioService.registerPlaybackCallback(this);
+ mRegisteredToAudioService = true;
+ }
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failed to register playback callback", e);
+ mRegisteredToAudioService = false;
+ }
+ }
+ }
+
+ private void sendAudioPlayerStateChangedMessageLocked(
+ final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+ for (MessageHandler messageHandler : mListenerMap.values()) {
+ messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+ }
+ }
+
+ 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 1cfd5f02..3c9e1d45 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -19,12 +19,14 @@ package com.android.server.media;
import com.android.internal.util.DumpUtils;
import com.android.server.Watchdog;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
@@ -96,7 +98,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private int mCurrentUserId = -1;
private boolean mGlobalBluetoothA2dpOn = false;
private final IAudioService mAudioService;
- private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+ private final Handler mHandler = new Handler();
private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
public MediaRouterService(Context context) {
@@ -106,31 +109,57 @@ public final class MediaRouterService extends IMediaRouterService.Stub
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService);
- mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener(
- new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() {
+ mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+ mAudioPlayerStateMonitor.registerListener(
+ new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+ static final long WAIT_MS = 500;
+ final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
+ @Override
+ public void run() {
+ restoreBluetoothA2dp();
+ }
+ };
+
@Override
- public void onAudioPlayerActiveStateChanged(int uid, boolean active) {
+ public void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+ int restoreUid = -1;
+ boolean active = config == null ? false : config.isActive();
if (active) {
- restoreRoute(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 =
- mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
- boolean restored = false;
- for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) {
- if (mAudioPlaybackMonitor.isPlaybackActive(
+ mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
+ for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
+ if (mAudioPlayerStateMonitor.isPlaybackActive(
sortedAudioPlaybackClientUids.get(i))) {
- restoreRoute(sortedAudioPlaybackClientUids.get(i));
- restored = true;
+ restoreUid = sortedAudioPlaybackClientUids.get(i);
break;
}
}
- if (!restored) {
- restoreBluetoothA2dp();
+ }
+
+ mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
+ if (restoreUid >= 0) {
+ restoreRoute(restoreUid);
+ if (DEBUG) {
+ Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+ + " active " + active + " restoring " + restoreUid);
+ }
+ } else {
+ mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
+ if (DEBUG) {
+ Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+ + " active " + active + " delaying");
}
}
}
- });
+ }, mHandler);
+ mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+
AudioRoutesInfo audioRoutes = null;
try {
audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
@@ -261,9 +290,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub
final long token = Binder.clearCallingIdentity();
try {
+ ClientRecord clientRecord;
synchronized (mLock) {
- return isPlaybackActiveLocked(client);
+ clientRecord = mAllClientRecords.get(client.asBinder());
+ }
+ if (clientRecord != null) {
+ return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
}
+ return false;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -480,14 +514,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub
return null;
}
- private boolean isPlaybackActiveLocked(IMediaRouterClient client) {
- ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
- if (clientRecord != null) {
- return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid);
- }
- return false;
- }
-
private void setDiscoveryRequestLocked(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
final IBinder binder = client.asBinder();
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index aa652445..f6a81d07 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -31,7 +32,7 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioManager;
-import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
@@ -68,7 +69,6 @@ import android.view.KeyEvent;
import android.view.ViewConfiguration;
import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.Watchdog.Monitor;
@@ -104,7 +104,6 @@ public class MediaSessionService extends SystemService implements Monitor {
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
- private AudioManagerInternal mAudioManagerInternal;
private ContentResolver mContentResolver;
private SettingsObserver mSettingsObserver;
private INotificationManager mNotificationManager;
@@ -114,7 +113,7 @@ public class MediaSessionService extends SystemService implements Monitor {
// It's always not null after the MediaSessionService is started.
private FullUserRecord mCurrentFullUserRecord;
private MediaSessionRecord mGlobalPrioritySession;
- private AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
// Used to notify system UI when remote volume was changed. TODO find a
// better way to handle this.
@@ -137,11 +136,16 @@ public class MediaSessionService extends SystemService implements Monitor {
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
- mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);
- mAudioPlaybackMonitor.registerOnAudioPlaybackStartedListener(
- new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+ mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+ mAudioPlayerStateMonitor.registerListener(
+ new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
@Override
- public void onAudioPlaybackStarted(int uid) {
+ 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));
@@ -150,8 +154,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
}
- });
- mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+ }, null /* handler */);
+ mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
mSettingsObserver.observe();
@@ -650,7 +654,7 @@ public class MediaSessionService extends SystemService implements Monitor {
public FullUserRecord(int fullUserId) {
mFullUserId = fullUserId;
- mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
+ mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
// Restore the remembered media button receiver before the boot.
String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -1309,7 +1313,7 @@ public class MediaSessionService extends SystemService implements Monitor {
for (int i = 0; i < count; i++) {
mUserRecords.valueAt(i).dumpLocked(pw, "");
}
- mAudioPlaybackMonitor.dump(pw, "");
+ mAudioPlayerStateMonitor.dump(getContext(), pw, "");
}
}
diff --git a/com/android/server/media/MediaSessionStack.java b/com/android/server/media/MediaSessionStack.java
index d9fe72e0..719ec362 100644
--- a/com/android/server/media/MediaSessionStack.java
+++ b/com/android/server/media/MediaSessionStack.java
@@ -75,7 +75,7 @@ class MediaSessionStack {
*/
private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
- private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
/**
@@ -84,7 +84,6 @@ class MediaSessionStack {
*/
private MediaSessionRecord mMediaButtonSession;
- private MediaSessionRecord mCachedDefault;
private MediaSessionRecord mCachedVolumeDefault;
/**
@@ -93,8 +92,8 @@ class MediaSessionStack {
private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
new SparseArray<>();
- MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
- mAudioPlaybackMonitor = monitor;
+ MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+ mAudioPlayerStateMonitor = monitor;
mOnMediaButtonSessionChangedListener = listener;
}
@@ -187,13 +186,13 @@ class MediaSessionStack {
if (DEBUG) {
Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
}
- IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+ IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
for (int i = 0; i < audioPlaybackUids.size(); i++) {
MediaSessionRecord mediaButtonSession =
findMediaButtonSession(audioPlaybackUids.get(i));
if (mediaButtonSession != null) {
// Found the media button session.
- mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+ mAudioPlayerStateMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
if (mMediaButtonSession != mediaButtonSession) {
updateMediaButtonSession(mediaButtonSession);
}
@@ -216,7 +215,7 @@ class MediaSessionStack {
for (MediaSessionRecord session : mSessions) {
if (uid == session.getUid()) {
if (session.getPlaybackState() != null && session.isPlaybackActive() ==
- mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+ mAudioPlayerStateMonitor.isPlaybackActive(session.getUid())) {
// If there's a media session whose PlaybackState matches
// the audio playback state, return it immediately.
return session;
@@ -376,7 +375,6 @@ class MediaSessionStack {
}
private void clearCache(int userId) {
- mCachedDefault = null;
mCachedVolumeDefault = null;
mCachedActiveLists.remove(userId);
// mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL,
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index b4056b33..3fa3cd49 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -142,6 +142,7 @@ import android.os.Message;
import android.os.MessageQueue.IdleHandler;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
@@ -192,7 +193,6 @@ import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
import libcore.io.IoUtils;
@@ -3746,7 +3746,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
extends UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
- public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+ public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
try {
final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
diff --git a/com/android/server/net/watchlist/DigestUtils.java b/com/android/server/net/watchlist/DigestUtils.java
new file mode 100644
index 00000000..57becb00
--- /dev/null
+++ b/com/android/server/net/watchlist/DigestUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Utils for calculating digests.
+ */
+public class DigestUtils {
+
+ private static final int FILE_READ_BUFFER_SIZE = 16 * 1024;
+
+ private DigestUtils() {}
+
+ /** @return SHA256 hash of the provided file */
+ public static byte[] getSha256Hash(File apkFile) throws IOException, NoSuchAlgorithmException {
+ try (InputStream stream = new FileInputStream(apkFile)) {
+ return getSha256Hash(stream);
+ }
+ }
+
+ /** @return SHA256 hash of data read from the provided input stream */
+ public static byte[] getSha256Hash(InputStream stream)
+ throws IOException, NoSuchAlgorithmException {
+ MessageDigest digester = MessageDigest.getInstance("SHA256");
+
+ int bytesRead;
+ byte[] buf = new byte[FILE_READ_BUFFER_SIZE];
+ while ((bytesRead = stream.read(buf)) >= 0) {
+ digester.update(buf, 0, bytesRead);
+ }
+ return digester.digest();
+ }
+} \ No newline at end of file
diff --git a/com/android/server/net/watchlist/HarmfulDigests.java b/com/android/server/net/watchlist/HarmfulDigests.java
new file mode 100644
index 00000000..27c22cea
--- /dev/null
+++ b/com/android/server/net/watchlist/HarmfulDigests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.watchlist;
+
+import com.android.internal.util.HexDump;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to store all harmful digests in memory.
+ * TODO: Optimize memory usage using byte array with binary search.
+ */
+class HarmfulDigests {
+
+ private final Set<String> mDigestSet;
+
+ HarmfulDigests(List<byte[]> digests) {
+ final HashSet<String> tmpDigestSet = new HashSet<>();
+ final int size = digests.size();
+ for (int i = 0; i < size; i++) {
+ tmpDigestSet.add(HexDump.toHexString(digests.get(i)));
+ }
+ mDigestSet = Collections.unmodifiableSet(tmpDigestSet);
+ }
+
+ public boolean contains(byte[] digest) {
+ return mDigestSet.contains(HexDump.toHexString(digest));
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ for (String digest : mDigestSet) {
+ pw.println(digest);
+ }
+ pw.println("");
+ }
+}
diff --git a/com/android/server/net/watchlist/NetworkWatchlistService.java b/com/android/server/net/watchlist/NetworkWatchlistService.java
new file mode 100644
index 00000000..171703ac
--- /dev/null
+++ b/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.watchlist;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.net.NetworkWatchlistManager;
+import android.net.metrics.IpConnectivityLog;
+import android.os.Binder;
+import android.os.Process;
+import android.os.SharedMemory;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Implementation of network watchlist service.
+ */
+public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
+
+ private static final String TAG = NetworkWatchlistService.class.getSimpleName();
+ static final boolean DEBUG = false;
+
+ private static final String PROPERTY_NETWORK_WATCHLIST_ENABLED =
+ "ro.network_watchlist_enabled";
+
+ private static final int MAX_NUM_OF_WATCHLIST_DIGESTS = 10000;
+
+ public static class Lifecycle extends SystemService {
+ private NetworkWatchlistService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+ // Watchlist service is disabled
+ return;
+ }
+ mService = new NetworkWatchlistService(getContext());
+ publishBinderService(Context.NETWORK_WATCHLIST_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+ // Watchlist service is disabled
+ return;
+ }
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ try {
+ mService.initIpConnectivityMetrics();
+ mService.startWatchlistLogging();
+ } catch (RemoteException e) {
+ // Should not happen
+ }
+ ReportWatchlistJobService.schedule(getContext());
+ }
+ }
+ }
+
+ private volatile boolean mIsLoggingEnabled = false;
+ private final Object mLoggingSwitchLock = new Object();
+
+ private final WatchlistSettings mSettings;
+ private final Context mContext;
+
+ // Separate thread to handle expensive watchlist logging work.
+ private final ServiceThread mHandlerThread;
+
+ @VisibleForTesting
+ IIpConnectivityMetrics mIpConnectivityMetrics;
+ @VisibleForTesting
+ WatchlistLoggingHandler mNetworkWatchlistHandler;
+
+ public NetworkWatchlistService(Context context) {
+ mContext = context;
+ mSettings = WatchlistSettings.getInstance();
+ mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ /* allowIo */ false);
+ mHandlerThread.start();
+ mNetworkWatchlistHandler = new WatchlistLoggingHandler(mContext,
+ mHandlerThread.getLooper());
+ mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+ }
+
+ // For testing only
+ @VisibleForTesting
+ NetworkWatchlistService(Context context, ServiceThread handlerThread,
+ WatchlistLoggingHandler handler, IIpConnectivityMetrics ipConnectivityMetrics) {
+ mContext = context;
+ mSettings = WatchlistSettings.getInstance();
+ mHandlerThread = handlerThread;
+ mNetworkWatchlistHandler = handler;
+ mIpConnectivityMetrics = ipConnectivityMetrics;
+ }
+
+ private void initIpConnectivityMetrics() {
+ mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
+ ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+ }
+
+ private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() {
+ @Override
+ public void onDnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
+ long timestamp, int uid) {
+ if (!mIsLoggingEnabled) {
+ return;
+ }
+ mNetworkWatchlistHandler.asyncNetworkEvent(hostname, ipAddresses, uid);
+ }
+
+ @Override
+ public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+ if (!mIsLoggingEnabled) {
+ return;
+ }
+ mNetworkWatchlistHandler.asyncNetworkEvent(null, new String[]{ipAddr}, uid);
+ }
+ };
+
+ @VisibleForTesting
+ protected boolean startWatchlistLoggingImpl() throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, "Starting watchlist logging.");
+ }
+ synchronized (mLoggingSwitchLock) {
+ if (mIsLoggingEnabled) {
+ Slog.w(TAG, "Watchlist logging is already running");
+ return true;
+ }
+ try {
+ if (mIpConnectivityMetrics.addNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST, mNetdEventCallback)) {
+ mIsLoggingEnabled = true;
+ return true;
+ } else {
+ return false;
+ }
+ } catch (RemoteException re) {
+ // Should not happen
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean startWatchlistLogging() throws RemoteException {
+ enforceWatchlistLoggingPermission();
+ return startWatchlistLoggingImpl();
+ }
+
+ @VisibleForTesting
+ protected boolean stopWatchlistLoggingImpl() {
+ if (DEBUG) {
+ Slog.i(TAG, "Stopping watchlist logging");
+ }
+ synchronized (mLoggingSwitchLock) {
+ if (!mIsLoggingEnabled) {
+ Slog.w(TAG, "Watchlist logging is not running");
+ return true;
+ }
+ // stop the logging regardless of whether we fail to unregister listener
+ mIsLoggingEnabled = false;
+
+ try {
+ return mIpConnectivityMetrics.removeNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+ } catch (RemoteException re) {
+ // Should not happen
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean stopWatchlistLogging() throws RemoteException {
+ enforceWatchlistLoggingPermission();
+ return stopWatchlistLoggingImpl();
+ }
+
+ private void enforceWatchlistLoggingPermission() {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new SecurityException(String.format("Uid %d has no permission to change watchlist"
+ + " setting.", uid));
+ }
+ }
+
+ /**
+ * Set a new network watchlist.
+ * This method should be called by ConfigUpdater only.
+ *
+ * @return True if network watchlist is updated.
+ */
+ public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests,
+ List<byte[]> domainsSha256Digests,
+ List<byte[]> ipAddressesCrc32Digests,
+ List<byte[]> ipAddressesSha256Digests) {
+ Slog.i(TAG, "Setting network watchlist");
+ if (domainsCrc32Digests == null || domainsSha256Digests == null
+ || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) {
+ Slog.e(TAG, "Parameters cannot be null");
+ return false;
+ }
+ if (domainsCrc32Digests.size() != domainsSha256Digests.size()
+ || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) {
+ Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests");
+ return false;
+ }
+ if (domainsSha256Digests.size() + ipAddressesSha256Digests.size()
+ > MAX_NUM_OF_WATCHLIST_DIGESTS) {
+ Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS);
+ return false;
+ }
+ mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests,
+ ipAddressesCrc32Digests, ipAddressesSha256Digests);
+ Slog.i(TAG, "Set network watchlist: Success");
+ return true;
+ }
+
+ @Override
+ public void reportWatchlistIfNecessary() {
+ // Allow any apps to trigger report event, as we won't run it if it's too early.
+ mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ mSettings.dump(fd, pw, args);
+ }
+
+}
diff --git a/com/android/server/net/watchlist/ReportWatchlistJobService.java b/com/android/server/net/watchlist/ReportWatchlistJobService.java
new file mode 100644
index 00000000..dfeb1b2d
--- /dev/null
+++ b/com/android/server/net/watchlist/ReportWatchlistJobService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+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.net.NetworkWatchlistManager;
+import android.util.Slog;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A job that periodically report watchlist records.
+ */
+public class ReportWatchlistJobService extends JobService {
+
+ private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+ private static final String TAG = "WatchlistJobService";
+
+ // Unique job id used in system service, other jobs should not use the same value.
+ public static final int REPORT_WATCHLIST_RECORDS_JOB_ID = 0xd7689;
+ public static final long REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS =
+ TimeUnit.HOURS.toMillis(12);
+
+ @Override
+ public boolean onStartJob(final JobParameters jobParameters) {
+ if (jobParameters.getJobId() != REPORT_WATCHLIST_RECORDS_JOB_ID) {
+ return false;
+ }
+ if (DEBUG) Slog.d(TAG, "Start scheduled job.");
+ new NetworkWatchlistManager(this).reportWatchlistIfNecessary();
+ jobFinished(jobParameters, false);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return true; // Reschedule when possible.
+ }
+
+ /**
+ * Schedule the {@link ReportWatchlistJobService} to run periodically.
+ */
+ public static void schedule(Context context) {
+ if (DEBUG) Slog.d(TAG, "Scheduling records aggregator task");
+ final JobScheduler scheduler =
+ (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ scheduler.schedule(new JobInfo.Builder(REPORT_WATCHLIST_RECORDS_JOB_ID,
+ new ComponentName(context, ReportWatchlistJobService.class))
+ //.setOverrideDeadline(45 * 1000) // Schedule job soon, for testing.
+ .setPeriodic(REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setPersisted(false)
+ .build());
+ }
+
+}
diff --git a/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/com/android/server/net/watchlist/WatchlistLoggingHandler.java
new file mode 100644
index 00000000..22475584
--- /dev/null
+++ b/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.watchlist;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.DropBoxManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Handler class for network watchlist logging on a background thread.
+ */
+class WatchlistLoggingHandler extends Handler {
+
+ private static final String TAG = WatchlistLoggingHandler.class.getSimpleName();
+ private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+
+ @VisibleForTesting
+ static final int LOG_WATCHLIST_EVENT_MSG = 1;
+ @VisibleForTesting
+ static final int REPORT_RECORDS_IF_NECESSARY_MSG = 2;
+
+ private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
+ private static final String DROPBOX_TAG = "network_watchlist_report";
+
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final PackageManager mPm;
+ private final WatchlistReportDbHelper mDbHelper;
+ private final WatchlistSettings mSettings;
+ // A cache for uid and apk digest mapping.
+ // As uid won't be reused until reboot, it's safe to assume uid is unique per signature and app.
+ // TODO: Use more efficient data structure.
+ private final HashMap<Integer, byte[]> mCachedUidDigestMap = new HashMap<>();
+
+ private interface WatchlistEventKeys {
+ String HOST = "host";
+ String IP_ADDRESSES = "ipAddresses";
+ String UID = "uid";
+ String TIMESTAMP = "timestamp";
+ }
+
+ WatchlistLoggingHandler(Context context, Looper looper) {
+ super(looper);
+ mContext = context;
+ mPm = mContext.getPackageManager();
+ mResolver = mContext.getContentResolver();
+ mDbHelper = WatchlistReportDbHelper.getInstance(context);
+ mSettings = WatchlistSettings.getInstance();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case LOG_WATCHLIST_EVENT_MSG: {
+ final Bundle data = msg.getData();
+ handleNetworkEvent(
+ data.getString(WatchlistEventKeys.HOST),
+ data.getStringArray(WatchlistEventKeys.IP_ADDRESSES),
+ data.getInt(WatchlistEventKeys.UID),
+ data.getLong(WatchlistEventKeys.TIMESTAMP)
+ );
+ break;
+ }
+ case REPORT_RECORDS_IF_NECESSARY_MSG:
+ tryAggregateRecords();
+ break;
+ default: {
+ Slog.d(TAG, "WatchlistLoggingHandler received an unknown of message.");
+ break;
+ }
+ }
+ }
+
+ /**
+ * Report network watchlist records if we collected enough data.
+ */
+ public void reportWatchlistIfNecessary() {
+ final Message msg = obtainMessage(REPORT_RECORDS_IF_NECESSARY_MSG);
+ sendMessage(msg);
+ }
+
+ /**
+ * Insert network traffic event to watchlist async queue processor.
+ */
+ public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+ final Message msg = obtainMessage(LOG_WATCHLIST_EVENT_MSG);
+ final Bundle bundle = new Bundle();
+ bundle.putString(WatchlistEventKeys.HOST, host);
+ bundle.putStringArray(WatchlistEventKeys.IP_ADDRESSES, ipAddresses);
+ bundle.putInt(WatchlistEventKeys.UID, uid);
+ bundle.putLong(WatchlistEventKeys.TIMESTAMP, System.currentTimeMillis());
+ msg.setData(bundle);
+ sendMessage(msg);
+ }
+
+ private void handleNetworkEvent(String hostname, String[] ipAddresses,
+ int uid, long timestamp) {
+ if (DEBUG) {
+ Slog.i(TAG, "handleNetworkEvent with host: " + hostname + ", uid: " + uid);
+ }
+ final String cncDomain = searchAllSubDomainsInWatchlist(hostname);
+ if (cncDomain != null) {
+ insertRecord(getDigestFromUid(uid), cncDomain, timestamp);
+ } else {
+ final String cncIp = searchIpInWatchlist(ipAddresses);
+ if (cncIp != null) {
+ insertRecord(getDigestFromUid(uid), cncIp, timestamp);
+ }
+ }
+ }
+
+ private boolean insertRecord(byte[] digest, String cncHost, long timestamp) {
+ final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp);
+ tryAggregateRecords();
+ return result;
+ }
+
+ private boolean shouldReportNetworkWatchlist() {
+ final long lastReportTime = Settings.Global.getLong(mResolver,
+ Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L);
+ final long currentTimestamp = System.currentTimeMillis();
+ if (currentTimestamp < lastReportTime) {
+ Slog.i(TAG, "Last report time is larger than current time, reset report");
+ mDbHelper.cleanup();
+ return false;
+ }
+ return currentTimestamp >= lastReportTime + ONE_DAY_MS;
+ }
+
+ private void tryAggregateRecords() {
+ if (shouldReportNetworkWatchlist()) {
+ Slog.i(TAG, "Start aggregating watchlist records.");
+ final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+ if (dbox != null && !dbox.isTagEnabled(DROPBOX_TAG)) {
+ final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
+ mDbHelper.getAggregatedRecords();
+ final byte[] encodedResult = encodeAggregatedResult(aggregatedResult);
+ if (encodedResult != null) {
+ addEncodedReportToDropBox(encodedResult);
+ }
+ }
+ mDbHelper.cleanup();
+ Settings.Global.putLong(mResolver, Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+ System.currentTimeMillis());
+ } else {
+ Slog.i(TAG, "No need to aggregate record yet.");
+ }
+ }
+
+ private byte[] encodeAggregatedResult(
+ WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
+ // TODO: Encode results using differential privacy.
+ return null;
+ }
+
+ private void addEncodedReportToDropBox(byte[] encodedReport) {
+ final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+ dbox.addData(DROPBOX_TAG, encodedReport, 0);
+ }
+
+ /**
+ * Get app digest from app uid.
+ */
+ private byte[] getDigestFromUid(int uid) {
+ final byte[] cachedDigest = mCachedUidDigestMap.get(uid);
+ if (cachedDigest != null) {
+ return cachedDigest;
+ }
+ final String[] packageNames = mPm.getPackagesForUid(uid);
+ final int userId = UserHandle.getUserId(uid);
+ if (!ArrayUtils.isEmpty(packageNames)) {
+ for (String packageName : packageNames) {
+ try {
+ final String apkPath = mPm.getPackageInfoAsUser(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId)
+ .applicationInfo.publicSourceDir;
+ if (TextUtils.isEmpty(apkPath)) {
+ Slog.w(TAG, "Cannot find apkPath for " + packageName);
+ continue;
+ }
+ final byte[] digest = DigestUtils.getSha256Hash(new File(apkPath));
+ mCachedUidDigestMap.put(uid, digest);
+ return digest;
+ } catch (NameNotFoundException | NoSuchAlgorithmException | IOException e) {
+ Slog.e(TAG, "Should not happen", e);
+ return null;
+ }
+ }
+ } else {
+ Slog.e(TAG, "Should not happen");
+ }
+ return null;
+ }
+
+ /**
+ * Search if any ip addresses are in watchlist.
+ *
+ * @param ipAddresses Ip address that you want to search in watchlist.
+ * @return Ip address that exists in watchlist, null if it does not match anything.
+ */
+ private String searchIpInWatchlist(String[] ipAddresses) {
+ for (String ipAddress : ipAddresses) {
+ if (isIpInWatchlist(ipAddress)) {
+ return ipAddress;
+ }
+ }
+ return null;
+ }
+
+ /** Search if the ip is in watchlist */
+ private boolean isIpInWatchlist(String ipAddr) {
+ if (ipAddr == null) {
+ return false;
+ }
+ return mSettings.containsIp(ipAddr);
+ }
+
+ /** Search if the host is in watchlist */
+ private boolean isHostInWatchlist(String host) {
+ if (host == null) {
+ return false;
+ }
+ return mSettings.containsDomain(host);
+ }
+
+ /**
+ * Search if any sub-domain in host is in watchlist.
+ *
+ * @param host Host that we want to search.
+ * @return Domain that exists in watchlist, null if it does not match anything.
+ */
+ private String searchAllSubDomainsInWatchlist(String host) {
+ if (host == null) {
+ return null;
+ }
+ final String[] subDomains = getAllSubDomains(host);
+ for (String subDomain : subDomains) {
+ if (isHostInWatchlist(subDomain)) {
+ return subDomain;
+ }
+ }
+ return null;
+ }
+
+ /** Get all sub-domains in a host */
+ @VisibleForTesting
+ static String[] getAllSubDomains(String host) {
+ if (host == null) {
+ return null;
+ }
+ final ArrayList<String> subDomainList = new ArrayList<>();
+ subDomainList.add(host);
+ int index = host.indexOf(".");
+ while (index != -1) {
+ host = host.substring(index + 1);
+ if (!TextUtils.isEmpty(host)) {
+ subDomainList.add(host);
+ }
+ index = host.indexOf(".");
+ }
+ return subDomainList.toArray(new String[0]);
+ }
+}
diff --git a/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/com/android/server/net/watchlist/WatchlistReportDbHelper.java
new file mode 100644
index 00000000..f48463f5
--- /dev/null
+++ b/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.watchlist;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Pair;
+
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to process watchlist read / save watchlist reports.
+ */
+class WatchlistReportDbHelper extends SQLiteOpenHelper {
+
+ private static final String TAG = "WatchlistReportDbHelper";
+
+ private static final String NAME = "watchlist_report.db";
+ private static final int VERSION = 2;
+
+ private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
+
+ private static class WhiteListReportContract {
+ private static final String TABLE = "records";
+ private static final String APP_DIGEST = "app_digest";
+ private static final String CNC_DOMAIN = "cnc_domain";
+ private static final String TIMESTAMP = "timestamp";
+ }
+
+ private static final String CREATE_TABLE_MODEL = "CREATE TABLE "
+ + WhiteListReportContract.TABLE + "("
+ + WhiteListReportContract.APP_DIGEST + " BLOB,"
+ + WhiteListReportContract.CNC_DOMAIN + " TEXT,"
+ + WhiteListReportContract.TIMESTAMP + " INTEGER DEFAULT 0" + " )";
+
+ private static final int INDEX_DIGEST = 0;
+ private static final int INDEX_CNC_DOMAIN = 1;
+ private static final int INDEX_TIMESTAMP = 2;
+
+ private static final String[] DIGEST_DOMAIN_PROJECTION =
+ new String[] {
+ WhiteListReportContract.APP_DIGEST,
+ WhiteListReportContract.CNC_DOMAIN
+ };
+
+ private static WatchlistReportDbHelper sInstance;
+
+ /**
+ * Aggregated watchlist records.
+ */
+ public static class AggregatedResult {
+ // A list of digests that visited c&c domain or ip before.
+ Set<String> appDigestList;
+
+ // The c&c domain or ip visited before.
+ String cncDomainVisited;
+
+ // A list of app digests and c&c domain visited.
+ HashMap<String, String> appDigestCNCList;
+ }
+
+ private WatchlistReportDbHelper(Context context) {
+ super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(),
+ null, VERSION);
+ // Memory optimization - close idle connections after 30s of inactivity
+ setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+ }
+
+ public static synchronized WatchlistReportDbHelper getInstance(Context context) {
+ if (sInstance != null) {
+ return sInstance;
+ }
+ sInstance = new WatchlistReportDbHelper(context);
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_MODEL);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO: For now, drop older tables and recreate new ones.
+ db.execSQL("DROP TABLE IF EXISTS " + WhiteListReportContract.TABLE);
+ onCreate(db);
+ }
+
+ /**
+ * Insert new watchlist record.
+ *
+ * @param appDigest The digest of an app.
+ * @param cncDomain C&C domain that app visited.
+ * @return True if success.
+ */
+ public boolean insertNewRecord(byte[] appDigest, String cncDomain,
+ long timestamp) {
+ final SQLiteDatabase db = getWritableDatabase();
+ final ContentValues values = new ContentValues();
+ values.put(WhiteListReportContract.APP_DIGEST, appDigest);
+ values.put(WhiteListReportContract.CNC_DOMAIN, cncDomain);
+ values.put(WhiteListReportContract.TIMESTAMP, timestamp);
+ return db.insert(WhiteListReportContract.TABLE, null, values) != -1;
+ }
+
+ /**
+ * Aggregate the records in database, and return a rappor encoded result.
+ */
+ public AggregatedResult getAggregatedRecords() {
+ final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+ final long yesterday = getYesterdayTimestamp();
+ final String selectStatement = WhiteListReportContract.TIMESTAMP + " >= ? AND " +
+ WhiteListReportContract.TIMESTAMP + " <= ?";
+
+ final SQLiteDatabase db = getReadableDatabase();
+ Cursor c = null;
+ try {
+ c = db.query(true /* distinct */,
+ WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
+ new String[]{"" + twoDaysBefore, "" + yesterday}, null, null,
+ null, null);
+ if (c == null || c.getCount() == 0) {
+ return null;
+ }
+ final AggregatedResult result = new AggregatedResult();
+ result.cncDomainVisited = null;
+ // After aggregation, each digest maximum will have only 1 record.
+ result.appDigestList = new HashSet<>();
+ result.appDigestCNCList = new HashMap<>();
+ while (c.moveToNext()) {
+ // We use hex string here as byte[] cannot be a key in HashMap.
+ String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
+ String cncDomain = c.getString(INDEX_CNC_DOMAIN);
+
+ result.appDigestList.add(digestHexStr);
+ if (result.cncDomainVisited != null) {
+ result.cncDomainVisited = cncDomain;
+ }
+ result.appDigestCNCList.put(digestHexStr, cncDomain);
+ }
+ return result;
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ /**
+ * Remove all the records before yesterday.
+ *
+ * @return True if success.
+ */
+ public boolean cleanup() {
+ final SQLiteDatabase db = getWritableDatabase();
+ final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+ final String clause = WhiteListReportContract.TIMESTAMP + "< " + twoDaysBefore;
+ return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
+ }
+
+ static long getTwoDaysBeforeTimestamp() {
+ return getMidnightTimestamp(2);
+ }
+
+ static long getYesterdayTimestamp() {
+ return getMidnightTimestamp(1);
+ }
+
+ static long getMidnightTimestamp(int daysBefore) {
+ java.util.Calendar date = new GregorianCalendar();
+ // reset hour, minutes, seconds and millis
+ date.set(java.util.Calendar.HOUR_OF_DAY, 0);
+ date.set(java.util.Calendar.MINUTE, 0);
+ date.set(java.util.Calendar.SECOND, 0);
+ date.set(java.util.Calendar.MILLISECOND, 0);
+ date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore);
+ return date.getTimeInMillis();
+ }
+} \ No newline at end of file
diff --git a/com/android/server/net/watchlist/WatchlistSettings.java b/com/android/server/net/watchlist/WatchlistSettings.java
new file mode 100644
index 00000000..c50f0d56
--- /dev/null
+++ b/com/android/server/net/watchlist/WatchlistSettings.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.watchlist;
+
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.CRC32;
+
+/**
+ * A util class to do watchlist settings operations, like setting watchlist, query if a domain
+ * exists in watchlist.
+ */
+class WatchlistSettings {
+ private static final String TAG = "WatchlistSettings";
+
+ // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
+ static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
+
+ private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
+
+ private static class XmlTags {
+ private static final String WATCHLIST_SETTINGS = "watchlist-settings";
+ private static final String SHA256_DOMAIN = "sha256-domain";
+ private static final String CRC32_DOMAIN = "crc32-domain";
+ private static final String SHA256_IP = "sha256-ip";
+ private static final String CRC32_IP = "crc32-ip";
+ private static final String HASH = "hash";
+ }
+
+ private static WatchlistSettings sInstance = new WatchlistSettings();
+ private final AtomicFile mXmlFile;
+ private final Object mLock = new Object();
+ private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
+ private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
+ private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
+ private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
+
+ public static synchronized WatchlistSettings getInstance() {
+ return sInstance;
+ }
+
+ private WatchlistSettings() {
+ this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
+ }
+
+ @VisibleForTesting
+ protected WatchlistSettings(File xmlFile) {
+ mXmlFile = new AtomicFile(xmlFile);
+ readSettingsLocked();
+ }
+
+ static File getSystemWatchlistFile(String filename) {
+ final File dataSystemDir = Environment.getDataSystemDirectory();
+ final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
+ systemWatchlistDir.mkdirs();
+ return new File(systemWatchlistDir, filename);
+ }
+
+ private void readSettingsLocked() {
+ synchronized (mLock) {
+ FileInputStream stream;
+ try {
+ stream = mXmlFile.openRead();
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
+ return;
+ }
+
+ final List<byte[]> crc32DomainList = new ArrayList<>();
+ final List<byte[]> sha256DomainList = new ArrayList<>();
+ final List<byte[]> crc32IpList = new ArrayList<>();
+ final List<byte[]> sha256IpList = new ArrayList<>();
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ switch (tagName) {
+ case XmlTags.CRC32_DOMAIN:
+ parseHash(parser, tagName, crc32DomainList);
+ break;
+ case XmlTags.CRC32_IP:
+ parseHash(parser, tagName, crc32IpList);
+ break;
+ case XmlTags.SHA256_DOMAIN:
+ parseHash(parser, tagName, sha256DomainList);
+ break;
+ case XmlTags.SHA256_IP:
+ parseHash(parser, tagName, sha256IpList);
+ break;
+ default:
+ Log.w(TAG, "Unknown element: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
+ } catch (IllegalStateException | NullPointerException | NumberFormatException |
+ XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+ Log.w(TAG, "Failed parsing " + e);
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ private void parseHash(XmlPullParser parser, String tagName, List<byte[]> hashSet)
+ throws IOException, XmlPullParserException {
+ parser.require(XmlPullParser.START_TAG, null, tagName);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.HASH);
+ byte[] hash = HexDump.hexStringToByteArray(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.HASH);
+ hashSet.add(hash);
+ }
+ parser.require(XmlPullParser.END_TAG, null, tagName);
+ }
+
+ /**
+ * Write network watchlist settings to disk.
+ * Adb should not use it, should use writeSettingsToMemory directly instead.
+ */
+ public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
+ List<byte[]> newSha256DomainList,
+ List<byte[]> newCrc32IpList,
+ List<byte[]> newSha256IpList) {
+ synchronized (mLock) {
+ FileOutputStream stream;
+ try {
+ stream = mXmlFile.startWrite();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write display settings: " + e);
+ return;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
+
+ writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
+ writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
+ writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
+ writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
+
+ out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
+ out.endDocument();
+ mXmlFile.finishWrite(stream);
+ writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
+ newSha256IpList);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write display settings, restoring backup.", e);
+ mXmlFile.failWrite(stream);
+ }
+ }
+ }
+
+ /**
+ * Write network watchlist settings to memory.
+ */
+ public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
+ List<byte[]> newSha256DomainList,
+ List<byte[]> newCrc32IpList,
+ List<byte[]> newSha256IpList) {
+ synchronized (mLock) {
+ mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
+ mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
+ mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
+ mSha256IpDigests = new HarmfulDigests(newSha256IpList);
+ }
+ }
+
+ private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
+ throws IOException {
+ out.startTag(null, tagName);
+ for (byte[] hash : hashSet) {
+ out.startTag(null, XmlTags.HASH);
+ out.text(HexDump.toHexString(hash));
+ out.endTag(null, XmlTags.HASH);
+ }
+ out.endTag(null, tagName);
+ }
+
+ public boolean containsDomain(String domain) {
+ // First it does a quick CRC32 check.
+ final byte[] crc32 = getCrc32(domain);
+ if (!mCrc32DomainDigests.contains(crc32)) {
+ return false;
+ }
+ // Now we do a slow SHA256 check.
+ final byte[] sha256 = getSha256(domain);
+ return mSha256DomainDigests.contains(sha256);
+ }
+
+ public boolean containsIp(String ip) {
+ // First it does a quick CRC32 check.
+ final byte[] crc32 = getCrc32(ip);
+ if (!mCrc32IpDigests.contains(crc32)) {
+ return false;
+ }
+ // Now we do a slow SHA256 check.
+ final byte[] sha256 = getSha256(ip);
+ return mSha256IpDigests.contains(sha256);
+ }
+
+
+ /** Get CRC32 of a string */
+ private byte[] getCrc32(String str) {
+ final CRC32 crc = new CRC32();
+ crc.update(str.getBytes());
+ final long tmp = crc.getValue();
+ return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
+ (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
+ }
+
+ /** Get SHA256 of a string */
+ private byte[] getSha256(String str) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA256");
+ } catch (NoSuchAlgorithmException e) {
+ /* can't happen */
+ return null;
+ }
+ messageDigest.update(str.getBytes());
+ return messageDigest.digest();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Domain CRC32 digest list:");
+ mCrc32DomainDigests.dump(fd, pw, args);
+ pw.println("Domain SHA256 digest list:");
+ mSha256DomainDigests.dump(fd, pw, args);
+ pw.println("Ip CRC32 digest list:");
+ mCrc32IpDigests.dump(fd, pw, args);
+ pw.println("Ip SHA256 digest list:");
+ mSha256IpDigests.dump(fd, pw, args);
+ }
+}
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index 238d87b7..557ba427 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -460,7 +460,7 @@ public class NotificationManagerService extends SystemService {
mRankingHelper.readXml(parser, forRestore);
}
// No non-system managed services are allowed on low ram devices
- if (!ActivityManager.isLowRamDeviceStatic()) {
+ if (canUseManagedServices()) {
if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
mListeners.readXml(parser);
migratedManagedServices = true;
@@ -548,12 +548,6 @@ public class NotificationManagerService extends SystemService {
out.endDocument();
}
- /** Use this to check if a package can post a notification or toast. */
- private boolean checkNotificationOp(String pkg, int uid) {
- return mAppOps.checkOp(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
- == AppOpsManager.MODE_ALLOWED && !isPackageSuspendedForUser(pkg, uid);
- }
-
private static final class ToastRecord
{
final int pid;
@@ -1226,7 +1220,6 @@ public class NotificationManagerService extends SystemService {
mAccessibilityManager = am;
}
-
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
void init(Looper looper, IPackageManager packageManager,
@@ -2727,9 +2720,9 @@ public class NotificationManagerService extends SystemService {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
final DumpFilter filter = DumpFilter.parseFromArguments(args);
- if (filter != null && filter.stats) {
+ if (filter.stats) {
dumpJson(pw, filter);
- } else if (filter != null && filter.proto) {
+ } else if (filter.proto) {
dumpProto(fd, filter);
} else {
dumpImpl(pw, filter);
@@ -2818,19 +2811,25 @@ public class NotificationManagerService extends SystemService {
@Override
public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
throws RemoteException {
+ setNotificationPolicyAccessGrantedForUser(
+ pkg, getCallingUserHandle().getIdentifier(), granted);
+ }
+
+ @Override
+ public void setNotificationPolicyAccessGrantedForUser(
+ String pkg, int userId, boolean granted) {
checkCallerIsSystemOrShell();
final long identity = Binder.clearCallingIdentity();
try {
- if (!mActivityManager.isLowRamDevice()) {
+ if (canUseManagedServices()) {
mConditionProviders.setPackageOrComponentEnabled(
- pkg, getCallingUserHandle().getIdentifier(), true, granted);
+ pkg, userId, true, granted);
getContext().sendBroadcastAsUser(new Intent(
NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
.setPackage(pkg)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- getCallingUserHandle(), null);
-
+ UserHandle.of(userId), null);
savePolicyFile();
}
} finally {
@@ -2840,7 +2839,6 @@ public class NotificationManagerService extends SystemService {
@Override
public Policy getNotificationPolicy(String pkg) {
- enforcePolicyAccess(pkg, "getNotificationPolicy");
final long identity = Binder.clearCallingIdentity();
try {
return mZenModeHelper.getNotificationPolicy();
@@ -2918,18 +2916,17 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSystemOrShell();
final long identity = Binder.clearCallingIdentity();
try {
- if (!mActivityManager.isLowRamDevice()) {
+ if (canUseManagedServices()) {
mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(),
userId, false, granted);
mListeners.setPackageOrComponentEnabled(listener.flattenToString(),
userId, true, granted);
getContext().sendBroadcastAsUser(new Intent(
- NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
-
+ NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
.setPackage(listener.getPackageName())
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- getCallingUserHandle(), null);
+ UserHandle.of(userId), null);
savePolicyFile();
}
@@ -2945,7 +2942,7 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSystemOrShell();
final long identity = Binder.clearCallingIdentity();
try {
- if (!mActivityManager.isLowRamDevice()) {
+ if (canUseManagedServices()) {
mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
userId, false, granted);
mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
@@ -2955,7 +2952,7 @@ public class NotificationManagerService extends SystemService {
NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
.setPackage(assistant.getPackageName())
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- getCallingUserHandle(), null);
+ UserHandle.of(userId), null);
savePolicyFile();
}
@@ -3238,7 +3235,7 @@ public class NotificationManagerService extends SystemService {
return null;
};
- private void dumpJson(PrintWriter pw, DumpFilter filter) {
+ private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter) {
JSONObject dump = new JSONObject();
try {
dump.put("service", "Notification Manager");
@@ -3252,7 +3249,7 @@ public class NotificationManagerService extends SystemService {
pw.println(dump);
}
- private void dumpProto(FileDescriptor fd, DumpFilter filter) {
+ private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mNotificationLock) {
long records = proto.start(NotificationServiceDumpProto.RECORDS);
@@ -3334,7 +3331,7 @@ public class NotificationManagerService extends SystemService {
proto.flush();
}
- void dumpImpl(PrintWriter pw, DumpFilter filter) {
+ void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) {
pw.print("Current Notification Manager state");
if (filter.filtered) {
pw.print(" (filtered to "); pw.print(filter); pw.print(")");
@@ -5434,6 +5431,11 @@ public class NotificationManagerService extends SystemService {
}
}
+ private boolean canUseManagedServices() {
+ return !mActivityManager.isLowRamDevice()
+ || mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
private class TrimCache {
StatusBarNotification heavy;
StatusBarNotification sbnClone;
@@ -5898,6 +5900,7 @@ public class NotificationManagerService extends SystemService {
public boolean redact = true;
public boolean proto = false;
+ @NonNull
public static DumpFilter parseFromArguments(String[] args) {
final DumpFilter filter = new DumpFilter();
for (int ai = 0; ai < args.length; ai++) {
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index d7e9cf37..d566a450 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -22,6 +22,7 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@@ -915,21 +916,21 @@ public class RankingHelper implements RankingConfig {
}
}
- public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
- if (filter == null) {
- final int N = mSignalExtractors.length;
- pw.print(prefix);
- pw.print("mSignalExtractors.length = ");
- pw.println(N);
- for (int i = 0; i < N; i++) {
- pw.print(prefix);
- pw.print(" ");
- pw.println(mSignalExtractors[i]);
- }
-
+ public void dump(PrintWriter pw, String prefix,
+ @NonNull NotificationManagerService.DumpFilter filter) {
+ final int N = mSignalExtractors.length;
+ pw.print(prefix);
+ pw.print("mSignalExtractors.length = ");
+ pw.println(N);
+ for (int i = 0; i < N; i++) {
pw.print(prefix);
- pw.println("per-package config:");
+ pw.print(" ");
+ pw.println(mSignalExtractors[i].getClass().getSimpleName());
}
+
+ pw.print(prefix);
+ pw.println("per-package config:");
+
pw.println("Records:");
synchronized (mRecords) {
dumpRecords(pw, prefix, filter, mRecords);
@@ -938,7 +939,8 @@ public class RankingHelper implements RankingConfig {
dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
}
- public void dump(ProtoOutputStream proto, NotificationManagerService.DumpFilter filter) {
+ public void dump(ProtoOutputStream proto,
+ @NonNull NotificationManagerService.DumpFilter filter) {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
@@ -952,12 +954,13 @@ public class RankingHelper implements RankingConfig {
}
private static void dumpRecords(ProtoOutputStream proto, long fieldId,
- NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+ @NonNull NotificationManagerService.DumpFilter filter,
+ ArrayMap<String, Record> records) {
final int N = records.size();
long fToken;
for (int i = 0; i < N; i++) {
final Record r = records.valueAt(i);
- if (filter == null || filter.matches(r.pkg)) {
+ if (filter.matches(r.pkg)) {
fToken = proto.start(fieldId);
proto.write(RecordProto.PACKAGE, r.pkg);
@@ -985,11 +988,12 @@ public class RankingHelper implements RankingConfig {
}
private static void dumpRecords(PrintWriter pw, String prefix,
- NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+ @NonNull NotificationManagerService.DumpFilter filter,
+ ArrayMap<String, Record> records) {
final int N = records.size();
for (int i = 0; i < N; i++) {
final Record r = records.valueAt(i);
- if (filter == null || filter.matches(r.pkg)) {
+ if (filter.matches(r.pkg)) {
pw.print(prefix);
pw.print(" AppSettings: ");
pw.print(r.pkg);
diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java
index f61cec97..1e9fab5c 100644
--- a/com/android/server/notification/ZenModeHelper.java
+++ b/com/android/server/notification/ZenModeHelper.java
@@ -835,7 +835,8 @@ public class ZenModeHelper {
// alarm restrictions
final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
// total silence restrictions
- final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+ || areAllBehaviorSoundsMuted();
for (int usage : AudioAttributes.SDK_USAGES) {
final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
@@ -855,9 +856,11 @@ public class ZenModeHelper {
}
}
+
@VisibleForTesting
protected void applyRestrictions(boolean mute, int usage) {
final String[] exceptionPackages = null; // none (for now)
+
mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
exceptionPackages);
@@ -866,6 +869,12 @@ public class ZenModeHelper {
exceptionPackages);
}
+ private boolean areAllBehaviorSoundsMuted() {
+ return !mConfig.allowAlarms && !mConfig.allowMediaSystemOther && !mConfig.allowReminders
+ && !mConfig.allowCalls && !mConfig.allowMessages && !mConfig.allowEvents
+ && !mConfig.allowRepeatCallers;
+ }
+
private void applyZenToRingerMode() {
if (mAudioManager == null) return;
// force the ringer mode into compliance
diff --git a/com/android/server/pm/KeySetManagerService.java b/com/android/server/pm/KeySetManagerService.java
index 35744662..fca95857 100644
--- a/com/android/server/pm/KeySetManagerService.java
+++ b/com/android/server/pm/KeySetManagerService.java
@@ -18,6 +18,8 @@ package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
+
import com.android.internal.util.Preconditions;
import android.content.pm.PackageParser;
import android.util.ArrayMap;
@@ -341,6 +343,41 @@ public class KeySetManagerService {
return mKeySets.get(id) != null;
}
+ public boolean shouldCheckUpgradeKeySetLocked(PackageSettingBase oldPs, int scanFlags) {
+ // Can't rotate keys during boot or if sharedUser.
+ if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
+ || !oldPs.keySetData.isUsingUpgradeKeySets()) {
+ return false;
+ }
+ // app is using upgradeKeySets; make sure all are valid
+ long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
+ for (int i = 0; i < upgradeKeySets.length; i++) {
+ if (!isIdValidKeySetId(upgradeKeySets[i])) {
+ Slog.wtf(TAG, "Package "
+ + (oldPs.name != null ? oldPs.name : "<null>")
+ + " contains upgrade-key-set reference to unknown key-set: "
+ + upgradeKeySets[i]
+ + " reverting to signatures check.");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean checkUpgradeKeySetLocked(PackageSettingBase oldPS,
+ PackageParser.Package newPkg) {
+ // Upgrade keysets are being used. Determine if new package has a superset of the
+ // required keys.
+ long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
+ for (int i = 0; i < upgradeKeySets.length; i++) {
+ Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
+ if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Fetches the {@link PublicKey public keys} which belong to the specified
* KeySet id.
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 86a1c03d..29f48ee3 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -110,9 +110,9 @@ public class PackageDexOptimizer {
return false;
}
- // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+ // We do not dexopt a priv-app package when pm.dexopt.priv-apps-oob is true.
if (pkg.isPrivileged()) {
- return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+ return !SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false);
}
return true;
@@ -209,7 +209,7 @@ public class PackageDexOptimizer {
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
// flags.
- final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
+ final int dexoptFlags = getDexFlags(pkg, compilerFilter, options);
for (String dexCodeIsa : dexCodeInstructionSets) {
int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
@@ -349,8 +349,7 @@ public class PackageDexOptimizer {
dexUseInfo.isUsedByOtherApps());
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
// Secondary dex files are currently not compiled at boot.
- int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
- | DEXOPT_SECONDARY_DEX;
+ int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX;
// Check the app storage and add the appropriate flags.
if (info.deviceProtectedDataDir != null &&
FileUtils.contains(info.deviceProtectedDataDir, path)) {
@@ -486,11 +485,11 @@ public class PackageDexOptimizer {
* filter.
*/
private int getDexFlags(PackageParser.Package pkg, String compilerFilter,
- boolean bootComplete) {
- return getDexFlags(pkg.applicationInfo, compilerFilter, bootComplete);
+ DexoptOptions options) {
+ return getDexFlags(pkg.applicationInfo, compilerFilter, options);
}
- private int getDexFlags(ApplicationInfo info, String compilerFilter, boolean bootComplete) {
+ private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) {
int flags = info.flags;
boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
// Profile guide compiled oat files should not be public.
@@ -501,7 +500,8 @@ public class PackageDexOptimizer {
(isPublic ? DEXOPT_PUBLIC : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
| profileFlag
- | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0);
+ | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
+ | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
return adjustDexoptFlags(dexFlags);
}
diff --git a/com/android/server/pm/PackageInstallerService.java b/com/android/server/pm/PackageInstallerService.java
index 09f9cb8c..be9b2f36 100644
--- a/com/android/server/pm/PackageInstallerService.java
+++ b/com/android/server/pm/PackageInstallerService.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
@@ -45,6 +46,7 @@ import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -724,6 +726,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
Binder.restoreCallingIdentity(ident);
}
} else {
+ ApplicationInfo appInfo = mPm.getApplicationInfo(callerPackageName, 0, userId);
+ if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
+ null);
+ }
+
// Take a short detour to confirm with user
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 7be0cde4..83cffe57 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -102,6 +102,15 @@ import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
+import static com.android.server.pm.PackageManagerServiceUtils.decompressFile;
+import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
+import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
+import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
@@ -408,7 +417,7 @@ public class PackageManagerService extends IPackageManager.Stub
private static final boolean DEBUG_FILTERS = false;
public static final boolean DEBUG_PERMISSIONS = false;
private static final boolean DEBUG_SHARED_LIBRARIES = false;
- private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
+ public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
// Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
// and PackageDexOptimizer. All these classes have their own flag to allow switching a single
@@ -462,9 +471,9 @@ public class PackageManagerService extends IPackageManager.Stub
private static final String STATIC_SHARED_LIB_DELIMITER = "_";
/** Extension of the compressed packages */
- private final static String COMPRESSED_EXTENSION = ".gz";
+ public final static String COMPRESSED_EXTENSION = ".gz";
/** Suffix of stub packages on the system partition */
- private final static String STUB_SUFFIX = "-Stub";
+ public final static String STUB_SUFFIX = "-Stub";
private static final int[] EMPTY_INT_ARRAY = new int[0];
@@ -542,6 +551,8 @@ public class PackageManagerService extends IPackageManager.Stub
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
+ private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
+
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
static {
@@ -574,37 +585,6 @@ public class PackageManagerService extends IPackageManager.Stub
public static final int REASON_LAST = REASON_SHARED;
- /** All dangerous permission names in the same order as the events in MetricsEvent */
- private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
- Manifest.permission.READ_CALENDAR,
- Manifest.permission.WRITE_CALENDAR,
- Manifest.permission.CAMERA,
- Manifest.permission.READ_CONTACTS,
- Manifest.permission.WRITE_CONTACTS,
- Manifest.permission.GET_ACCOUNTS,
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION,
- Manifest.permission.RECORD_AUDIO,
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.CALL_PHONE,
- Manifest.permission.READ_CALL_LOG,
- Manifest.permission.WRITE_CALL_LOG,
- Manifest.permission.ADD_VOICEMAIL,
- Manifest.permission.USE_SIP,
- Manifest.permission.PROCESS_OUTGOING_CALLS,
- Manifest.permission.READ_CELL_BROADCASTS,
- Manifest.permission.BODY_SENSORS,
- Manifest.permission.SEND_SMS,
- Manifest.permission.RECEIVE_SMS,
- Manifest.permission.READ_SMS,
- Manifest.permission.RECEIVE_WAP_PUSH,
- Manifest.permission.RECEIVE_MMS,
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_PHONE_NUMBERS,
- Manifest.permission.ANSWER_PHONE_CALLS);
-
-
/**
* Version number for the package parser cache. Increment this whenever the format or
* extent of cached data changes. See {@code PackageParser#setCacheDir}.
@@ -3107,75 +3087,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private int decompressFile(File srcFile, File dstFile) throws ErrnoException {
- if (DEBUG_COMPRESSION) {
- Slog.i(TAG, "Decompress file"
- + "; src: " + srcFile.getAbsolutePath()
- + ", dst: " + dstFile.getAbsolutePath());
- }
- try (
- InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
- OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
- ) {
- Streams.copy(fileIn, fileOut);
- Os.chmod(dstFile.getAbsolutePath(), 0644);
- return PackageManager.INSTALL_SUCCEEDED;
- } catch (IOException e) {
- logCriticalInfo(Log.ERROR, "Failed to decompress file"
- + "; src: " + srcFile.getAbsolutePath()
- + ", dst: " + dstFile.getAbsolutePath());
- }
- return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- }
-
- private File[] getCompressedFiles(String codePath) {
- final File stubCodePath = new File(codePath);
- final String stubName = stubCodePath.getName();
-
- // The layout of a compressed package on a given partition is as follows :
- //
- // Compressed artifacts:
- //
- // /partition/ModuleName/foo.gz
- // /partation/ModuleName/bar.gz
- //
- // Stub artifact:
- //
- // /partition/ModuleName-Stub/ModuleName-Stub.apk
- //
- // In other words, stub is on the same partition as the compressed artifacts
- // and in a directory that's suffixed with "-Stub".
- int idx = stubName.lastIndexOf(STUB_SUFFIX);
- if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
- return null;
- }
-
- final File stubParentDir = stubCodePath.getParentFile();
- if (stubParentDir == null) {
- Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
- return null;
- }
-
- final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
- final File[] files = compressedPath.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
- }
- });
-
- if (DEBUG_COMPRESSION && files != null && files.length > 0) {
- Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
- }
-
- return files;
- }
-
- private boolean compressedFileExists(String codePath) {
- final File[] compressedFiles = getCompressedFiles(codePath);
- return compressedFiles != null && compressedFiles.length > 0;
- }
-
/**
* Decompresses the given package on the system image onto
* the /data partition.
@@ -5176,66 +5087,6 @@ public class PackageManagerService extends IPackageManager.Stub
getCallingUid(), userId, mPermissionCallback);
}
- /**
- * Get the first event id for the permission.
- *
- * <p>There are four events for each permission: <ul>
- * <li>Request permission: first id + 0</li>
- * <li>Grant permission: first id + 1</li>
- * <li>Request for permission denied: first id + 2</li>
- * <li>Revoke permission: first id + 3</li>
- * </ul></p>
- *
- * @param name name of the permission
- *
- * @return The first event id for the permission
- */
- private static int getBaseEventId(@NonNull String name) {
- int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
-
- if (eventIdIndex == -1) {
- if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
- || Build.IS_USER) {
- Log.i(TAG, "Unknown permission " + name);
-
- return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
- } else {
- // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
- //
- // Also update
- // - EventLogger#ALL_DANGEROUS_PERMISSIONS
- // - metrics_constants.proto
- throw new IllegalStateException("Unknown permission " + name);
- }
- }
-
- return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
- }
-
- /**
- * Log that a permission was revoked.
- *
- * @param context Context of the caller
- * @param name name of the permission
- * @param packageName package permission if for
- */
- private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
- @NonNull String packageName) {
- MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
- }
-
- /**
- * Log that a permission request was granted.
- *
- * @param context Context of the caller
- * @param name name of the permission
- * @param packageName package permission if for
- */
- private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
- @NonNull String packageName) {
- MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
- }
-
@Override
public void resetRuntimePermissions() {
mContext.enforceCallingOrSelfPermission(
@@ -5476,56 +5327,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
/**
- * Compares two sets of signatures. Returns:
- * <br />
- * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
- * <br />
- * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
- * <br />
- * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
- * <br />
- * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
- * <br />
- * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
- */
- public static int compareSignatures(Signature[] s1, Signature[] s2) {
- if (s1 == null) {
- return s2 == null
- ? PackageManager.SIGNATURE_NEITHER_SIGNED
- : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
- }
-
- if (s2 == null) {
- return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
- }
-
- if (s1.length != s2.length) {
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- // Since both signature sets are of size 1, we can compare without HashSets.
- if (s1.length == 1) {
- return s1[0].equals(s2[0]) ?
- PackageManager.SIGNATURE_MATCH :
- PackageManager.SIGNATURE_NO_MATCH;
- }
-
- ArraySet<Signature> set1 = new ArraySet<Signature>();
- for (Signature sig : s1) {
- set1.add(sig);
- }
- ArraySet<Signature> set2 = new ArraySet<Signature>();
- for (Signature sig : s2) {
- set2.add(sig);
- }
- // Make sure s2 contains all signatures in s1.
- if (set1.equals(set2)) {
- return PackageManager.SIGNATURE_MATCH;
- }
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- /**
* If the database version for this type of package (internal storage or
* external storage) is less than the version where package signatures
* were updated, return true.
@@ -5535,76 +5336,11 @@ public class PackageManagerService extends IPackageManager.Stub
return ver.databaseVersion < DatabaseVersion.SIGNATURE_END_ENTITY;
}
- /**
- * Used for backward compatibility to make sure any packages with
- * certificate chains get upgraded to the new style. {@code existingSigs}
- * will be in the old format (since they were stored on disk from before the
- * system upgrade) and {@code scannedSigs} will be in the newer format.
- */
- private int compareSignaturesCompat(PackageSignatures existingSigs,
- PackageParser.Package scannedPkg) {
- if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- ArraySet<Signature> existingSet = new ArraySet<Signature>();
- for (Signature sig : existingSigs.mSignatures) {
- existingSet.add(sig);
- }
- ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
- for (Signature sig : scannedPkg.mSignatures) {
- try {
- Signature[] chainSignatures = sig.getChainSignatures();
- for (Signature chainSig : chainSignatures) {
- scannedCompatSet.add(chainSig);
- }
- } catch (CertificateEncodingException e) {
- scannedCompatSet.add(sig);
- }
- }
- /*
- * Make sure the expanded scanned set contains all signatures in the
- * existing one.
- */
- if (scannedCompatSet.equals(existingSet)) {
- // Migrate the old signatures to the new scheme.
- existingSigs.assignSignatures(scannedPkg.mSignatures);
- // The new KeySets will be re-added later in the scanning process.
- synchronized (mPackages) {
- mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
- }
- return PackageManager.SIGNATURE_MATCH;
- }
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
private boolean isRecoverSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
final VersionInfo ver = getSettingsVersionForPackage(scannedPkg);
return ver.databaseVersion < DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
}
- private int compareSignaturesRecover(PackageSignatures existingSigs,
- PackageParser.Package scannedPkg) {
- if (!isRecoverSignatureUpdateNeeded(scannedPkg)) {
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- String msg = null;
- try {
- if (Signature.areEffectiveMatch(existingSigs.mSignatures, scannedPkg.mSignatures)) {
- logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for "
- + scannedPkg.packageName);
- return PackageManager.SIGNATURE_MATCH;
- }
- } catch (CertificateException e) {
- msg = e.getMessage();
- }
-
- logCriticalInfo(Log.INFO,
- "Failed to recover certificates for " + scannedPkg.packageName + ": " + msg);
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
@Override
public List<String> getAllPackages() {
final int callingUid = Binder.getCallingUid();
@@ -8328,51 +8064,10 @@ public class PackageManagerService extends IPackageManager.Stub
parallelPackageParser.close();
}
- private static File getSettingsProblemFile() {
- File dataDir = Environment.getDataDirectory();
- File systemDir = new File(dataDir, "system");
- File fname = new File(systemDir, "uiderrors.txt");
- return fname;
- }
-
public static void reportSettingsProblem(int priority, String msg) {
logCriticalInfo(priority, msg);
}
- public static void logCriticalInfo(int priority, String msg) {
- Slog.println(priority, TAG, msg);
- EventLogTags.writePmCriticalInfo(msg);
- try {
- File fname = getSettingsProblemFile();
- FileOutputStream out = new FileOutputStream(fname, true);
- PrintWriter pw = new FastPrintWriter(out);
- SimpleDateFormat formatter = new SimpleDateFormat();
- String dateString = formatter.format(new Date(System.currentTimeMillis()));
- pw.println(dateString + ": " + msg);
- pw.close();
- FileUtils.setPermissions(
- fname.toString(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
- -1, -1);
- } catch (java.io.IOException e) {
- }
- }
-
- private 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());
- }
- }
- return maxModifiedTime;
- }
- return srcFile.lastModified();
- }
-
private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
final int policyFlags) throws PackageManagerException {
// When upgrading from pre-N MR1, verify the package time stamp using the package
@@ -8385,7 +8080,7 @@ public class PackageManagerService extends IPackageManager.Stub
&& !isCompatSignatureUpdateNeeded(pkg)
&& !isRecoverSignatureUpdateNeeded(pkg)) {
long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
ArraySet<PublicKey> signingKs;
synchronized (mPackages) {
signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
@@ -8687,6 +8382,7 @@ public class PackageManagerService extends IPackageManager.Stub
pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
}
+ pkg.mExtras = updatedPkg;
throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
+ scanFile + " ignored: updated version " + ps.versionCode
@@ -8800,7 +8496,7 @@ public class PackageManagerService extends IPackageManager.Stub
return scannedPkg;
}
- private void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
+ private static void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
// Derive the new package synthetic package name
pkg.setPackageName(pkg.packageName + STATIC_SHARED_LIB_DELIMITER
+ pkg.staticSharedLibVersion);
@@ -8814,49 +8510,6 @@ public class PackageManagerService extends IPackageManager.Stub
return processName;
}
- private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
- throws PackageManagerException {
- if (pkgSetting.signatures.mSignatures != null) {
- // Already existing package. Make sure signatures match
- boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
- == PackageManager.SIGNATURE_MATCH;
- if (!match) {
- match = compareSignaturesCompat(pkgSetting.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- match = compareSignaturesRecover(pkgSetting.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
- + pkg.packageName + " signatures do not match the "
- + "previously installed version; ignoring!");
- }
- }
-
- // Check for shared user signatures
- if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
- // Already existing package. Make sure signatures match
- boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
- pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
- if (!match) {
- match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
- "Package " + pkg.packageName
- + " has no signatures that match those in shared user "
- + pkgSetting.sharedUser.name + "; ignoring!");
- }
- }
- }
-
/**
* Enforces that only the system UID or root's UID can call a method exposed
* via Binder.
@@ -9827,24 +9480,6 @@ public class PackageManagerService extends IPackageManager.Stub
return res;
}
- /**
- * Derive the value of the {@code cpuAbiOverride} based on the provided
- * value and an optional stored value from the package settings.
- */
- private static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
- String cpuAbiOverride = null;
-
- if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
- cpuAbiOverride = null;
- } else if (abiOverride != null) {
- cpuAbiOverride = abiOverride;
- } else if (settings != null) {
- cpuAbiOverride = settings.cpuAbiOverrideString;
- }
-
- return cpuAbiOverride;
- }
-
private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
throws PackageManagerException {
@@ -9965,7 +9600,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (Build.IS_DEBUGGABLE &&
pkg.isPrivileged() &&
- !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
}
@@ -10192,8 +9827,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
- if (checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+ if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10211,8 +9847,16 @@ public class PackageManagerService extends IPackageManager.Stub
}
} else {
try {
- // SIDE EFFECTS; compareSignaturesCompat() changes KeysetManagerService
- verifySignaturesLP(signatureCheckPs, pkg);
+ final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+ final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+ final boolean compatMatch = verifySignatures(signatureCheckPs, pkg.mSignatures,
+ compareCompat, compareRecover);
+ // The new KeySets will be re-added later in the scanning process.
+ if (compatMatch) {
+ synchronized (mPackages) {
+ ksms.removeAppKeySetDataLPw(pkg.packageName);
+ }
+ }
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10500,7 +10144,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
// Make sure we're not adding any bogus keyset info
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
ksms.assertScannedPackageValid(pkg);
synchronized (mPackages) {
@@ -15656,42 +15300,6 @@ public class PackageManagerService extends IPackageManager.Stub
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
- // Can't rotate keys during boot or if sharedUser.
- if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
- || !oldPs.keySetData.isUsingUpgradeKeySets()) {
- return false;
- }
- // app is using upgradeKeySets; make sure all are valid
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
- long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
- for (int i = 0; i < upgradeKeySets.length; i++) {
- if (!ksms.isIdValidKeySetId(upgradeKeySets[i])) {
- Slog.wtf(TAG, "Package "
- + (oldPs.name != null ? oldPs.name : "<null>")
- + " contains upgrade-key-set reference to unknown key-set: "
- + upgradeKeySets[i]
- + " reverting to signatures check.");
- return false;
- }
- }
- return true;
- }
-
- private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
- // Upgrade keysets are being used. Determine if new package has a superset of the
- // required keys.
- long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
- for (int i = 0; i < upgradeKeySets.length; i++) {
- Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
- if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
- return true;
- }
- }
- return false;
- }
-
private static void updateDigest(MessageDigest digest, File file) throws IOException {
try (DigestInputStream digestStream =
new DigestInputStream(new FileInputStream(file), digest)) {
@@ -15730,8 +15338,9 @@ public class PackageManagerService extends IPackageManager.Stub
ps = mSettings.mPackages.get(pkgName);
// verify signatures are valid
- if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
- if (!checkUpgradeKeySetLP(ps, pkg)) {
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) {
+ if (!ksms.checkUpgradeKeySetLocked(ps, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package not signed by keys specified by upgrade-keysets: "
+ pkgName);
@@ -16632,8 +16241,9 @@ public class PackageManagerService extends IPackageManager.Stub
// Quick sanity check that we're signed correctly if updating;
// we'll check this again later when scanning, but we want to
// bail early here before tripping over redefined permissions.
- if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
- if (!checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+ if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+ pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
@@ -16641,7 +16251,16 @@ public class PackageManagerService extends IPackageManager.Stub
}
} else {
try {
- verifySignaturesLP(signatureCheckPs, pkg);
+ final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+ final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+ final boolean compatMatch = verifySignatures(
+ signatureCheckPs, pkg.mSignatures, compareCompat, compareRecover);
+ // The new KeySets will be re-added later in the scanning process.
+ if (compatMatch) {
+ synchronized (mPackages) {
+ ksms.removeAppKeySetDataLPw(pkg.packageName);
+ }
+ }
} catch (PackageManagerException e) {
res.setError(e.error, e.getMessage());
return;
@@ -16679,10 +16298,11 @@ public class PackageManagerService extends IPackageManager.Stub
final boolean sigsOk;
final String sourcePackageName = bp.getSourcePackageName();
final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
if (sourcePackageName.equals(pkg.packageName)
- && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
- scanFlags))) {
- sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
+ && (ksms.shouldCheckUpgradeKeySetLocked(
+ sourcePackageSetting, scanFlags))) {
+ sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
} else {
sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
@@ -20282,6 +19902,23 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
.getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_SYSTEM);
co.onChange(true);
+ // This observer provides an one directional mapping from Global.PRIV_APP_OOB_ENABLED to
+ // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
+ // it is done.
+ ContentObserver privAppOobObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
+ SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
+ oobEnabled == 1 ? "true" : "false");
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
+ UserHandle.USER_SYSTEM);
+ // At boot, restore the value from the setting, which persists across reboot.
+ privAppOobObserver.onChange(true);
+
// Disable any carrier apps. We do this very early in boot to prevent the apps from being
// disabled after already being started.
CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
@@ -21028,34 +20665,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
pw.println();
pw.println("Package warning messages:");
- BufferedReader in = null;
- String line = null;
- try {
- in = new BufferedReader(new FileReader(getSettingsProblemFile()));
- while ((line = in.readLine()) != null) {
- if (line.contains("ignored: updated version")) continue;
- pw.println(line);
- }
- } catch (IOException ignored) {
- } finally {
- IoUtils.closeQuietly(in);
- }
+ dumpCriticalInfo(pw, null);
}
if (checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES)) {
- BufferedReader in = null;
- String line = null;
- try {
- in = new BufferedReader(new FileReader(getSettingsProblemFile()));
- while ((line = in.readLine()) != null) {
- if (line.contains("ignored: updated version")) continue;
- pw.print("msg,");
- pw.println(line);
- }
- } catch (IOException ignored) {
- } finally {
- IoUtils.closeQuietly(in);
- }
+ dumpCriticalInfo(pw, "msg,");
}
}
@@ -21101,26 +20715,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
dumpFeaturesProto(proto);
mSettings.dumpPackagesProto(proto);
mSettings.dumpSharedUsersProto(proto);
- dumpMessagesProto(proto);
+ dumpCriticalInfo(proto);
}
proto.flush();
}
- private void dumpMessagesProto(ProtoOutputStream proto) {
- BufferedReader in = null;
- String line = null;
- try {
- in = new BufferedReader(new FileReader(getSettingsProblemFile()));
- while ((line = in.readLine()) != null) {
- if (line.contains("ignored: updated version")) continue;
- proto.write(PackageServiceDumpProto.MESSAGES, line);
- }
- } catch (IOException ignored) {
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
-
private void dumpFeaturesProto(ProtoOutputStream proto) {
synchronized (mAvailableFeatures) {
final int count = mAvailableFeatures.size();
@@ -22529,7 +22128,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
Slog.w(TAG, "KeySet requested for filtered package: " + packageName);
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return new KeySet(ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias));
}
}
@@ -22558,7 +22157,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
&& Process.SYSTEM_UID != callingUid) {
throw new SecurityException("May not access signing KeySet of other apps.");
}
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return new KeySet(ksms.getSigningKeySetByPackageNameLPr(packageName));
}
}
@@ -22582,7 +22181,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
IBinder ksh = ks.getToken();
if (ksh instanceof KeySetHandle) {
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ksh);
}
return false;
@@ -22608,7 +22207,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
IBinder ksh = ks.getToken();
if (ksh instanceof KeySetHandle) {
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ksh);
}
return false;
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 67e06dda..758abd76 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,42 +16,74 @@
package com.android.server.pm;
-import com.android.server.pm.dex.DexManager;
-import com.android.server.pm.dex.PackageDexUsage;
-
+import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
+import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.server.EventLogTags;
+import com.android.server.pm.dex.DexManager;
+import com.android.server.pm.dex.PackageDexUsage;
import android.annotation.NonNull;
import android.app.AppGlobals;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
import android.os.Build;
import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.service.pm.PackageServiceDumpProto;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.jar.StrictJarFile;
+import android.util.proto.ProtoOutputStream;
+
import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
import libcore.io.Libcore;
+import libcore.io.Streams;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
+import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
/**
@@ -200,7 +232,7 @@ public class PackageManagerServiceUtils {
*
* If it doesn't have sufficient information about the package, it return <code>false</code>.
*/
- static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
+ public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
@@ -263,6 +295,21 @@ 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());
+ }
+ }
+ return maxModifiedTime;
+ }
+ return srcFile.lastModified();
+ }
+
/**
* Checks that the archive located at {@code fileName} has uncompressed dex file and so
* files that can be direclty mapped.
@@ -318,6 +365,57 @@ public class PackageManagerServiceUtils {
}
}
+ private static File getSettingsProblemFile() {
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File fname = new File(systemDir, "uiderrors.txt");
+ return fname;
+ }
+
+ public static void dumpCriticalInfo(ProtoOutputStream proto) {
+ try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ if (line.contains("ignored: updated version")) continue;
+ proto.write(PackageServiceDumpProto.MESSAGES, line);
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ public static void dumpCriticalInfo(PrintWriter pw, String msg) {
+ try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ if (line.contains("ignored: updated version")) continue;
+ if (msg != null) {
+ pw.print(msg);
+ }
+ pw.println(line);
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ public static void logCriticalInfo(int priority, String msg) {
+ Slog.println(priority, TAG, msg);
+ EventLogTags.writePmCriticalInfo(msg);
+ try {
+ File fname = getSettingsProblemFile();
+ FileOutputStream out = new FileOutputStream(fname, true);
+ PrintWriter pw = new FastPrintWriter(out);
+ SimpleDateFormat formatter = new SimpleDateFormat();
+ String dateString = formatter.format(new Date(System.currentTimeMillis()));
+ pw.println(dateString + ": " + msg);
+ pw.close();
+ FileUtils.setPermissions(
+ fname.toString(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
+ -1, -1);
+ } catch (java.io.IOException e) {
+ }
+ }
+
public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
if (callingUid == Process.SHELL_UID) {
if (userHandle >= 0
@@ -331,4 +429,240 @@ public class PackageManagerServiceUtils {
}
}
}
+
+ /**
+ * Derive the value of the {@code cpuAbiOverride} based on the provided
+ * value and an optional stored value from the package settings.
+ */
+ public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
+ String cpuAbiOverride = null;
+ if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
+ cpuAbiOverride = null;
+ } else if (abiOverride != null) {
+ cpuAbiOverride = abiOverride;
+ } else if (settings != null) {
+ cpuAbiOverride = settings.cpuAbiOverrideString;
+ }
+ return cpuAbiOverride;
+ }
+
+ /**
+ * Compares two sets of signatures. Returns:
+ * <br />
+ * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
+ * <br />
+ * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
+ * <br />
+ * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
+ * <br />
+ * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
+ * <br />
+ * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
+ */
+ public static int compareSignatures(Signature[] s1, Signature[] s2) {
+ if (s1 == null) {
+ return s2 == null
+ ? PackageManager.SIGNATURE_NEITHER_SIGNED
+ : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
+ }
+
+ if (s2 == null) {
+ return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
+ }
+
+ if (s1.length != s2.length) {
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ // Since both signature sets are of size 1, we can compare without HashSets.
+ if (s1.length == 1) {
+ return s1[0].equals(s2[0]) ?
+ PackageManager.SIGNATURE_MATCH :
+ PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ ArraySet<Signature> set1 = new ArraySet<Signature>();
+ for (Signature sig : s1) {
+ set1.add(sig);
+ }
+ ArraySet<Signature> set2 = new ArraySet<Signature>();
+ for (Signature sig : s2) {
+ set2.add(sig);
+ }
+ // Make sure s2 contains all signatures in s1.
+ if (set1.equals(set2)) {
+ return PackageManager.SIGNATURE_MATCH;
+ }
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ /**
+ * Used for backward compatibility to make sure any packages with
+ * certificate chains get upgraded to the new style. {@code existingSigs}
+ * will be in the old format (since they were stored on disk from before the
+ * system upgrade) and {@code scannedSigs} will be in the newer format.
+ */
+ private static boolean matchSignaturesCompat(String packageName,
+ PackageSignatures packageSignatures, Signature[] parsedSignatures) {
+ ArraySet<Signature> existingSet = new ArraySet<Signature>();
+ for (Signature sig : packageSignatures.mSignatures) {
+ existingSet.add(sig);
+ }
+ ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
+ for (Signature sig : parsedSignatures) {
+ try {
+ Signature[] chainSignatures = sig.getChainSignatures();
+ for (Signature chainSig : chainSignatures) {
+ scannedCompatSet.add(chainSig);
+ }
+ } catch (CertificateEncodingException e) {
+ scannedCompatSet.add(sig);
+ }
+ }
+ // make sure the expanded scanned set contains all signatures in the existing one
+ if (scannedCompatSet.equals(existingSet)) {
+ // migrate the old signatures to the new scheme
+ packageSignatures.assignSignatures(parsedSignatures);
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean matchSignaturesRecover(String packageName,
+ Signature[] existingSignatures, Signature[] parsedSignatures) {
+ String msg = null;
+ try {
+ if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
+ logCriticalInfo(Log.INFO,
+ "Recovered effectively matching certificates for " + packageName);
+ return true;
+ }
+ } catch (CertificateException e) {
+ msg = e.getMessage();
+ }
+ logCriticalInfo(Log.INFO,
+ "Failed to recover certificates for " + packageName + ": " + msg);
+ return false;
+ }
+
+ /**
+ * Verifies that signatures match.
+ * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
+ * @throws PackageManagerException if the signatures did not match.
+ */
+ public static boolean verifySignatures(PackageSetting pkgSetting,
+ Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover)
+ throws PackageManagerException {
+ final String packageName = pkgSetting.name;
+ boolean compatMatch = false;
+ if (pkgSetting.signatures.mSignatures != null) {
+ // Already existing package. Make sure signatures match
+ boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures)
+ == PackageManager.SIGNATURE_MATCH;
+ if (!match && compareCompat) {
+ match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures);
+ compatMatch = match;
+ }
+ if (!match && compareRecover) {
+ match = matchSignaturesRecover(
+ packageName, pkgSetting.signatures.mSignatures, parsedSignatures);
+ }
+ if (!match) {
+ throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "Package " + packageName +
+ " signatures don't match previously installed version; ignoring!");
+ }
+ }
+ // Check for shared user signatures
+ if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
+ // Already existing package. Make sure signatures match
+ boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+ parsedSignatures) == PackageManager.SIGNATURE_MATCH;
+ if (!match) {
+ match = matchSignaturesCompat(
+ packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
+ }
+ if (!match && compareCompat) {
+ match = matchSignaturesRecover(
+ packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
+ compatMatch |= match;
+ }
+ if (!match && compareRecover) {
+ throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+ "Package " + packageName
+ + " has no signatures that match those in shared user "
+ + pkgSetting.sharedUser.name + "; ignoring!");
+ }
+ }
+ return compatMatch;
+ }
+
+ public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
+ if (DEBUG_COMPRESSION) {
+ Slog.i(TAG, "Decompress file"
+ + "; src: " + srcFile.getAbsolutePath()
+ + ", dst: " + dstFile.getAbsolutePath());
+ }
+ try (
+ InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
+ OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
+ ) {
+ Streams.copy(fileIn, fileOut);
+ Os.chmod(dstFile.getAbsolutePath(), 0644);
+ return PackageManager.INSTALL_SUCCEEDED;
+ } catch (IOException e) {
+ logCriticalInfo(Log.ERROR, "Failed to decompress file"
+ + "; src: " + srcFile.getAbsolutePath()
+ + ", dst: " + dstFile.getAbsolutePath());
+ }
+ return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+ }
+
+ public static File[] getCompressedFiles(String codePath) {
+ final File stubCodePath = new File(codePath);
+ final String stubName = stubCodePath.getName();
+
+ // The layout of a compressed package on a given partition is as follows :
+ //
+ // Compressed artifacts:
+ //
+ // /partition/ModuleName/foo.gz
+ // /partation/ModuleName/bar.gz
+ //
+ // Stub artifact:
+ //
+ // /partition/ModuleName-Stub/ModuleName-Stub.apk
+ //
+ // In other words, stub is on the same partition as the compressed artifacts
+ // and in a directory that's suffixed with "-Stub".
+ int idx = stubName.lastIndexOf(STUB_SUFFIX);
+ if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
+ return null;
+ }
+
+ final File stubParentDir = stubCodePath.getParentFile();
+ if (stubParentDir == null) {
+ Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
+ return null;
+ }
+
+ final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
+ final File[] files = compressedPath.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
+ }
+ });
+
+ if (DEBUG_COMPRESSION && files != null && files.length > 0) {
+ Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
+ }
+
+ return files;
+ }
+
+ public static boolean compressedFileExists(String codePath) {
+ final File[] compressedFiles = getCompressedFiles(codePath);
+ return compressedFiles != null && compressedFiles.length > 0;
+ }
}
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index a2099e60..807eb1a8 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -54,6 +54,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IUserManager;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -78,7 +79,6 @@ import dalvik.system.DexFile;
import libcore.io.IoUtils;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -102,8 +102,6 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO
class PackageManagerShellCommand extends ShellCommand {
/** Path for streaming APK content */
private static final String STDIN_PATH = "-";
- /** Whether or not APK content must be streamed from stdin */
- private static final boolean FORCE_STREAM_INSTALL = true;
final IPackageManager mInterface;
final private WeakHashMap<String, Resources> mResourceCache =
@@ -255,30 +253,27 @@ class PackageManagerShellCommand extends ShellCommand {
}
private void setParamsSize(InstallParams params, String inPath) {
- // If we're forced to stream the package, the params size
- // must be set via command-line argument. There's nothing
- // to do here.
- if (FORCE_STREAM_INSTALL) {
- return;
- }
- final PrintWriter pw = getOutPrintWriter();
if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
- File file = new File(inPath);
- if (file.isFile()) {
+ final ParcelFileDescriptor fd = openFileForSystem(inPath, "r");
+ if (fd == null) {
+ getErrPrintWriter().println("Error: Can't open file: " + inPath);
+ throw new IllegalArgumentException("Error: Can't open file: " + inPath);
+ }
+ try {
+ ApkLite baseApk = PackageParser.parseApkLite(fd.getFileDescriptor(), inPath, 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) {
+ getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
+ throw new IllegalArgumentException(
+ "Error: Failed to parse APK file: " + inPath, e);
+ } finally {
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) {
- pw.println("Error: Failed to parse APK file: " + file);
- throw new IllegalArgumentException(
- "Error: Failed to parse APK file: " + file, e);
+ fd.close();
+ } catch (IOException e) {
}
- } else {
- pw.println("Error: Can't open non-file: " + inPath);
- throw new IllegalArgumentException("Error: Can't open non-file: " + inPath);
}
}
}
@@ -1914,6 +1909,12 @@ class PackageManagerShellCommand extends ShellCommand {
throw new IllegalArgumentException("Missing inherit package name");
}
break;
+ case "--pkg":
+ sessionParams.appPackageName = getNextArg();
+ if (sessionParams.appPackageName == null) {
+ throw new IllegalArgumentException("Missing package name");
+ }
+ break;
case "-S":
final long sizeBytes = Long.parseLong(getNextArg());
if (sizeBytes <= 0) {
@@ -1925,6 +1926,7 @@ class PackageManagerShellCommand extends ShellCommand {
sessionParams.abiOverride = checkAbiArgument(getNextArg());
break;
case "--ephemeral":
+ case "--instant":
case "--instantapp":
sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
break;
@@ -2092,20 +2094,24 @@ class PackageManagerShellCommand extends ShellCommand {
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
- if (FORCE_STREAM_INSTALL && inPath != null && !STDIN_PATH.equals(inPath)) {
- pw.println("Error: APK content must be streamed");
- return 1;
- }
+ final ParcelFileDescriptor fd;
if (STDIN_PATH.equals(inPath)) {
- inPath = null;
+ fd = null;
} else if (inPath != null) {
- final File file = new File(inPath);
- if (file.isFile()) {
- sizeBytes = file.length();
+ fd = openFileForSystem(inPath, "r");
+ if (fd == null) {
+ return -1;
+ }
+ sizeBytes = fd.getStatSize();
+ if (sizeBytes < 0) {
+ getErrPrintWriter().println("Unable to get size of: " + inPath);
+ return -1;
}
+ } else {
+ fd = null;
}
if (sizeBytes <= 0) {
- pw.println("Error: must specify a APK size");
+ getErrPrintWriter().println("Error: must specify a APK size");
return 1;
}
@@ -2118,8 +2124,8 @@ class PackageManagerShellCommand extends ShellCommand {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
- if (inPath != null) {
- in = new FileInputStream(inPath);
+ if (fd != null) {
+ in = new ParcelFileDescriptor.AutoCloseInputStream(fd);
} else {
in = new SizedInputStream(getRawInputStream(), sizeBytes);
}
@@ -2144,7 +2150,7 @@ class PackageManagerShellCommand extends ShellCommand {
}
return 0;
} catch (IOException e) {
- pw.println("Error: failed to write; " + e.getMessage());
+ getErrPrintWriter().println("Error: failed to write; " + e.getMessage());
return 1;
} finally {
IoUtils.closeQuietly(out);
diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java
index a3585bc1..ba97c428 100644
--- a/com/android/server/pm/ShortcutPackage.java
+++ b/com/android/server/pm/ShortcutPackage.java
@@ -615,10 +615,16 @@ class ShortcutPackage extends ShortcutPackageItem {
// Fix up isPinned for the caller. Note we need to do it before the "test" callback,
// since it may check isPinned.
- if (!isPinnedByCaller) {
- clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+ // However, if getPinnedByAnyLauncher is set, we do it after the test.
+ if (!getPinnedByAnyLauncher) {
+ if (!isPinnedByCaller) {
+ clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+ }
}
if (query == null || query.test(clone)) {
+ if (!isPinnedByCaller) {
+ clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+ }
result.add(clone);
}
}
diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java
index 1c002aa4..0907dd7c 100644
--- a/com/android/server/pm/ShortcutService.java
+++ b/com/android/server/pm/ShortcutService.java
@@ -2234,7 +2234,7 @@ public class ShortcutService extends IShortcutService.Stub {
// We override this method in unit tests to do a simpler check.
boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId,
int callingPid, int callingUid) {
- if (injectCheckAccessShortcutsPermission(callingPid, callingUid)) {
+ if (canSeeAnyPinnedShortcut(callingPackage, userId, callingPid, callingUid)) {
return true;
}
final long start = injectElapsedRealtime();
@@ -2245,10 +2245,21 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
+ boolean canSeeAnyPinnedShortcut(@NonNull String callingPackage, int userId,
+ int callingPid, int callingUid) {
+ if (injectHasAccessShortcutsPermission(callingPid, callingUid)) {
+ return true;
+ }
+ synchronized (mLock) {
+ return getUserShortcutsLocked(userId).hasHostPackage(callingPackage);
+ }
+ }
+
/**
* Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
*/
- boolean injectCheckAccessShortcutsPermission(int callingPid, int callingUid) {
+ @VisibleForTesting
+ boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
@@ -2361,6 +2372,16 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
+ public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+ int userId) {
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+ user.setShortcutHostPackage(type, packageName);
+ }
+ }
+
// === House keeping ===
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
@@ -2477,8 +2498,8 @@ public class ShortcutService extends IShortcutService.Stub {
final ArraySet<String> ids = shortcutIds == null ? null
: new ArraySet<>(shortcutIds);
- final ShortcutPackage p = getUserShortcutsLocked(userId)
- .getPackageShortcutsIfExists(packageName);
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+ final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName);
if (p == null) {
return; // No need to instantiate ShortcutPackage.
}
@@ -2486,9 +2507,12 @@ public class ShortcutService extends IShortcutService.Stub {
final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+ final boolean canAccessAllShortcuts =
+ canSeeAnyPinnedShortcut(callingPackage, launcherUserId, callingPid, callingUid);
+
final boolean getPinnedByAnyLauncher =
- ((queryFlags & ShortcutQuery.FLAG_MATCH_ALL_PINNED) != 0)
- && injectCheckAccessShortcutsPermission(callingPid, callingUid);
+ canAccessAllShortcuts &&
+ ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0);
p.findAll(ret,
(ShortcutInfo si) -> {
@@ -2507,7 +2531,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (matchDynamic && si.isDynamic()) {
return true;
}
- if ((matchPinned && si.isPinned()) || getPinnedByAnyLauncher) {
+ if ((matchPinned || getPinnedByAnyLauncher) && si.isPinned()) {
return true;
}
if (matchManifest && si.isDeclaredInManifest()) {
@@ -2600,14 +2624,15 @@ public class ShortcutService extends IShortcutService.Stub {
.attemptToRestoreIfNeededAndSave();
final boolean getPinnedByAnyLauncher =
- injectCheckAccessShortcutsPermission(callingPid, callingUid);
+ canSeeAnyPinnedShortcut(callingPackage, launcherUserId,
+ callingPid, callingUid);
// Make sure the shortcut is actually visible to the launcher.
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId,
getPinnedByAnyLauncher);
// "si == null" should suffice here, but check the flags too just to make sure.
- if (si == null || !si.isEnabled() || !si.isAlive()) {
+ if (si == null || !si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) {
Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
return null;
}
@@ -2697,6 +2722,12 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Override
+ public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+ int userId) {
+ ShortcutService.this.setShortcutHostPackage(type, packageName, userId);
+ }
+
+ @Override
public boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
@Nullable IntentSender resultIntent, int userId) {
diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java
index 48eccd02..1efd765b 100644
--- a/com/android/server/pm/ShortcutUser.java
+++ b/com/android/server/pm/ShortcutUser.java
@@ -23,6 +23,7 @@ import android.content.pm.ShortcutManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -123,6 +124,20 @@ class ShortcutUser {
/** In-memory-cached default launcher. */
private ComponentName mCachedLauncher;
+ /**
+ * Keep track of additional packages that other parts of the system have said are
+ * allowed to access shortcuts. The key is the part of the system it came from,
+ * the value is the package name that has access. We don't persist these because
+ * at boot all relevant system services will push this data back to us they do their
+ * normal evaluation of the state of the world.
+ */
+ private final ArrayMap<String, String> mHostPackages = new ArrayMap<>();
+
+ /**
+ * Set of package name values from above.
+ */
+ private final ArraySet<String> mHostPackageSet = new ArraySet<>();
+
private String mKnownLocales;
private long mLastAppScanTime;
@@ -467,6 +482,23 @@ class ShortcutUser {
return mCachedLauncher;
}
+ public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName) {
+ if (packageName != null) {
+ mHostPackages.put(type, packageName);
+ } else {
+ mHostPackages.remove(type);
+ }
+
+ mHostPackageSet.clear();
+ for (int i = 0; i < mHostPackages.size(); i++) {
+ mHostPackageSet.add(mHostPackages.valueAt(i));
+ }
+ }
+
+ public boolean hasHostPackage(@NonNull String packageName) {
+ return mHostPackageSet.contains(packageName);
+ }
+
public void resetThrottling() {
for (int i = mPackages.size() - 1; i >= 0; i--) {
mPackages.valueAt(i).resetThrottling();
@@ -555,6 +587,18 @@ class ShortcutUser {
pw.print("Last known launcher: ");
pw.print(mLastKnownLauncher);
pw.println();
+
+ if (mHostPackages.size() > 0) {
+ pw.print(prefix);
+ pw.println("Host packages:");
+ for (int i = 0; i < mHostPackages.size(); i++) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(mHostPackages.keyAt(i));
+ pw.print(": ");
+ pw.println(mHostPackages.valueAt(i));
+ }
+ }
}
for (int i = 0; i < mLaunchers.size(); i++) {
diff --git a/com/android/server/pm/UserDataPreparer.java b/com/android/server/pm/UserDataPreparer.java
index bfe09b8e..96c102b5 100644
--- a/com/android/server/pm/UserDataPreparer.java
+++ b/com/android/server/pm/UserDataPreparer.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
@@ -40,8 +42,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
-import static com.android.server.pm.PackageManagerService.logCriticalInfo;
-
/**
* Helper class for preparing and destroying user storage
*/
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index 1e5245cf..11523101 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -1010,6 +1010,23 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ @Override
+ public boolean hasRestrictedProfiles() {
+ checkManageUsersPermission("hasRestrictedProfiles");
+ final int callingUserId = UserHandle.getCallingUserId();
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo profile = mUsers.valueAt(i).info;
+ if (callingUserId != profile.id
+ && profile.restrictedProfileParentId == callingUserId) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
/*
* Should be locked on mUsers before calling this.
*/
diff --git a/com/android/server/pm/UserRestrictionsUtils.java b/com/android/server/pm/UserRestrictionsUtils.java
index c86122f2..4b134043 100644
--- a/com/android/server/pm/UserRestrictionsUtils.java
+++ b/com/android/server/pm/UserRestrictionsUtils.java
@@ -66,6 +66,7 @@ public class UserRestrictionsUtils {
public static final Set<String> USER_RESTRICTIONS = newSetWithUniqueCheck(new String[] {
UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONFIG_LOCALE,
UserManager.DISALLOW_MODIFY_ACCOUNTS,
UserManager.DISALLOW_INSTALL_APPS,
UserManager.DISALLOW_UNINSTALL_APPS,
@@ -112,6 +113,7 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_OEM_UNLOCK,
UserManager.DISALLOW_UNMUTE_DEVICE,
UserManager.DISALLOW_AUTOFILL,
+ UserManager.DISALLOW_USER_SWITCH
});
/**
@@ -143,6 +145,13 @@ public class UserRestrictionsUtils {
);
/**
+ * User restrictions that cannot be set by profile owners. Applied to all users.
+ */
+ private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_USER_SWITCH
+ );
+
+ /**
* User restrictions that can't be changed by device owner or profile owner.
*/
private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
@@ -311,6 +320,7 @@ public class UserRestrictionsUtils {
*/
public static boolean canProfileOwnerChange(String restriction, int userId) {
return !IMMUTABLE_BY_OWNERS.contains(restriction)
+ && !DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)
&& !(userId != UserHandle.USER_SYSTEM
&& PRIMARY_USER_ONLY_RESTRICTIONS.contains(restriction));
}
@@ -363,8 +373,9 @@ public class UserRestrictionsUtils {
*/
private static boolean isGlobal(boolean isDeviceOwner, String key) {
return (isDeviceOwner &&
- (PRIMARY_USER_ONLY_RESTRICTIONS.contains(key)|| GLOBAL_RESTRICTIONS.contains(key)))
- || PROFILE_GLOBAL_RESTRICTIONS.contains(key);
+ (PRIMARY_USER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)))
+ || PROFILE_GLOBAL_RESTRICTIONS.contains(key)
+ || DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key);
}
/**
diff --git a/com/android/server/pm/crossprofile/CrossProfileAppsService.java b/com/android/server/pm/crossprofile/CrossProfileAppsService.java
new file mode 100644
index 00000000..0913269f
--- /dev/null
+++ b/com/android/server/pm/crossprofile/CrossProfileAppsService.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.pm.crossprofile;
+
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+public class CrossProfileAppsService extends SystemService {
+ private CrossProfileAppsServiceImpl mServiceImpl;
+
+ public CrossProfileAppsService(Context context) {
+ super(context);
+ mServiceImpl = new CrossProfileAppsServiceImpl(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.CROSS_PROFILE_APPS_SERVICE, mServiceImpl);
+ }
+}
diff --git a/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
new file mode 100644
index 00000000..854b7043
--- /dev/null
+++ b/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.crossprofile;
+
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.crossprofile.ICrossProfileApps;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
+ private static final String TAG = "CrossProfileAppsService";
+
+ private Context mContext;
+ private Injector mInjector;
+
+ public CrossProfileAppsServiceImpl(Context context) {
+ this(context, new InjectorImpl(context));
+ }
+
+ @VisibleForTesting
+ CrossProfileAppsServiceImpl(Context context, Injector injector) {
+ mContext = context;
+ mInjector = injector;
+ }
+
+ @Override
+ public List<UserHandle> getTargetUserProfiles(String callingPackage) {
+ Preconditions.checkNotNull(callingPackage);
+
+ verifyCallingPackage(callingPackage);
+
+ return getTargetUserProfilesUnchecked(
+ callingPackage, mInjector.getCallingUserId());
+ }
+
+ @Override
+ public void startActivityAsUser(
+ String callingPackage,
+ ComponentName component,
+ Rect sourceBounds,
+ Bundle startActivityOptions,
+ UserHandle user) throws RemoteException {
+ Preconditions.checkNotNull(callingPackage);
+ Preconditions.checkNotNull(component);
+ Preconditions.checkNotNull(user);
+
+ verifyCallingPackage(callingPackage);
+
+ List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked(
+ callingPackage, mInjector.getCallingUserId());
+ if (!allowedTargetUsers.contains(user)) {
+ throw new SecurityException(
+ callingPackage + " cannot access unrelated user " + user.getIdentifier());
+ }
+
+ // Verify that caller package is starting activity in its own package.
+ if (!callingPackage.equals(component.getPackageName())) {
+ throw new SecurityException(
+ callingPackage + " attempts to start an activity in other package - "
+ + component.getPackageName());
+ }
+
+ final int callingUid = mInjector.getCallingUid();
+
+ // Verify that target activity does handle the intent with ACTION_MAIN and
+ // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present.
+ final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+ launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ launchIntent.setSourceBounds(sourceBounds);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ // Only package name is set here, as opposed to component name, because intent action and
+ // category are ignored if component name is present while we are resolving intent.
+ launchIntent.setPackage(component.getPackageName());
+ verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, user);
+
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ launchIntent.setComponent(component);
+ mContext.startActivityAsUser(launchIntent, startActivityOptions, user);
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ private List<UserHandle> getTargetUserProfilesUnchecked(
+ String callingPackage, @UserIdInt int callingUserId) {
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ final int[] enabledProfileIds =
+ mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+
+ List<UserHandle> targetProfiles = new ArrayList<>();
+ for (final int userId : enabledProfileIds) {
+ if (userId == callingUserId) {
+ continue;
+ }
+ if (!isPackageEnabled(callingPackage, userId)) {
+ continue;
+ }
+ targetProfiles.add(UserHandle.of(userId));
+ }
+ return targetProfiles;
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ private boolean isPackageEnabled(String packageName, @UserIdInt int userId) {
+ final int callingUid = mInjector.getCallingUid();
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ final PackageInfo info = mInjector.getPackageManagerInternal()
+ .getPackageInfo(
+ packageName,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ callingUid,
+ userId);
+ return info != null && info.applicationInfo.enabled;
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Verify that the specified intent does resolved to the specified component and the resolved
+ * activity is exported.
+ */
+ private void verifyActivityCanHandleIntentAndExported(
+ Intent launchIntent, ComponentName component, int callingUid, UserHandle user) {
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ final List<ResolveInfo> apps =
+ mInjector.getPackageManagerInternal().queryIntentActivities(
+ launchIntent,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ callingUid,
+ user.getIdentifier());
+ final int size = apps.size();
+ for (int i = 0; i < size; ++i) {
+ final ActivityInfo activityInfo = apps.get(i).activityInfo;
+ if (TextUtils.equals(activityInfo.packageName, component.getPackageName())
+ && TextUtils.equals(activityInfo.name, component.getClassName())
+ && activityInfo.exported) {
+ return;
+ }
+ }
+ throw new SecurityException("Attempt to launch activity without "
+ + " category Intent.CATEGORY_LAUNCHER or activity is not exported" + component);
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Verify that the given calling package is belong to the calling UID.
+ */
+ private void verifyCallingPackage(String callingPackage) {
+ mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
+ }
+
+ private static class InjectorImpl implements Injector {
+ private Context mContext;
+
+ public InjectorImpl(Context context) {
+ mContext = context;
+ }
+
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ public int getCallingUserId() {
+ return UserHandle.getCallingUserId();
+ }
+
+ public UserHandle getCallingUserHandle() {
+ return Binder.getCallingUserHandle();
+ }
+
+ public long clearCallingIdentity() {
+ return Binder.clearCallingIdentity();
+ }
+
+ public void restoreCallingIdentity(long token) {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ public UserManager getUserManager() {
+ return mContext.getSystemService(UserManager.class);
+ }
+
+ public PackageManagerInternal getPackageManagerInternal() {
+ return LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ public PackageManager getPackageManager() {
+ return mContext.getPackageManager();
+ }
+
+ public AppOpsManager getAppOpsManager() {
+ return mContext.getSystemService(AppOpsManager.class);
+ }
+ }
+
+ @VisibleForTesting
+ public interface Injector {
+ int getCallingUid();
+
+ int getCallingUserId();
+
+ UserHandle getCallingUserHandle();
+
+ long clearCallingIdentity();
+
+ void restoreCallingIdentity(long token);
+
+ UserManager getUserManager();
+
+ PackageManagerInternal getPackageManagerInternal();
+
+ PackageManager getPackageManager();
+
+ AppOpsManager getAppOpsManager();
+
+ }
+}
diff --git a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 533b2619..c40d1fad 100644
--- a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -16,6 +16,8 @@
package com.android.server.pm.permission;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -383,6 +385,7 @@ public final class DefaultPermissionGrantPolicy {
MediaStore.AUTHORITY, userId);
if (mediaStorePackage != null) {
grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+ grantRuntimePermissions(mediaStorePackage, PHONE_PERMISSIONS, true, userId);
}
// Downloads provider
@@ -1109,8 +1112,8 @@ public final class DefaultPermissionGrantPolicy {
final String systemPackageName = mServiceInternal.getKnownPackageName(
PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
final PackageParser.Package systemPackage = getPackage(systemPackageName);
- return PackageManagerService.compareSignatures(systemPackage.mSignatures,
- pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+ return compareSignatures(systemPackage.mSignatures, pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH;
}
private void grantDefaultPermissionExceptions(int userId) {
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
index 76805ce3..7d8e2069 100644
--- a/com/android/server/pm/permission/PermissionManagerService.java
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -37,6 +37,7 @@ import android.content.pm.PackageParser;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.PackageParser.Package;
+import android.metrics.LogMaker;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -92,36 +93,6 @@ import java.util.Set;
public class PermissionManagerService {
private static final String TAG = "PackageManager";
- /** All dangerous permission names in the same order as the events in MetricsEvent */
- private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
- Manifest.permission.READ_CALENDAR,
- Manifest.permission.WRITE_CALENDAR,
- Manifest.permission.CAMERA,
- Manifest.permission.READ_CONTACTS,
- Manifest.permission.WRITE_CONTACTS,
- Manifest.permission.GET_ACCOUNTS,
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION,
- Manifest.permission.RECORD_AUDIO,
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.CALL_PHONE,
- Manifest.permission.READ_CALL_LOG,
- Manifest.permission.WRITE_CALL_LOG,
- Manifest.permission.ADD_VOICEMAIL,
- Manifest.permission.USE_SIP,
- Manifest.permission.PROCESS_OUTGOING_CALLS,
- Manifest.permission.READ_CELL_BROADCASTS,
- Manifest.permission.BODY_SENSORS,
- Manifest.permission.SEND_SMS,
- Manifest.permission.RECEIVE_SMS,
- Manifest.permission.READ_SMS,
- Manifest.permission.RECEIVE_WAP_PUSH,
- Manifest.permission.RECEIVE_MMS,
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_PHONE_NUMBERS,
- Manifest.permission.ANSWER_PHONE_CALLS);
-
/** Permission grant: not grant the permission. */
private static final int GRANT_DENIED = 1;
/** Permission grant: grant the permission as an install permission. */
@@ -160,6 +131,7 @@ public class PermissionManagerService {
private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Context mContext;
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
/** Internal storage for permissions and related settings */
@GuardedBy("mLock")
@@ -1037,10 +1009,10 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages));
PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
final PackageParser.Package systemPackage =
mPackageManagerInt.getPackage(systemPackageName);
- boolean allowed = (PackageManagerService.compareSignatures(
+ boolean allowed = (PackageManagerServiceUtils.compareSignatures(
bp.getSourceSignatures(), pkg.mSignatures)
== PackageManager.SIGNATURE_MATCH)
- || (PackageManagerService.compareSignatures(
+ || (PackageManagerServiceUtils.compareSignatures(
systemPackage.mSignatures, pkg.mSignatures)
== PackageManager.SIGNATURE_MATCH);
if (!allowed && (privilegedPermission || oemPermission)) {
@@ -1386,7 +1358,7 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages));
}
if (bp.isRuntime()) {
- logPermissionGranted(mContext, permName, packageName);
+ logPermission(MetricsEvent.ACTION_PERMISSION_GRANTED, permName, packageName);
}
if (callback != null) {
@@ -1484,7 +1456,7 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages));
}
if (bp.isRuntime()) {
- logPermissionRevoked(mContext, permName, packageName);
+ logPermission(MetricsEvent.ACTION_PERMISSION_REVOKED, permName, packageName);
}
if (callback != null) {
@@ -1938,63 +1910,18 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages));
}
/**
- * Get the first event id for the permission.
- *
- * <p>There are four events for each permission: <ul>
- * <li>Request permission: first id + 0</li>
- * <li>Grant permission: first id + 1</li>
- * <li>Request for permission denied: first id + 2</li>
- * <li>Revoke permission: first id + 3</li>
- * </ul></p>
- *
- * @param name name of the permission
- *
- * @return The first event id for the permission
- */
- private static int getBaseEventId(@NonNull String name) {
- int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
-
- if (eventIdIndex == -1) {
- if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
- || Build.IS_USER) {
- Log.i(TAG, "Unknown permission " + name);
-
- return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
- } else {
- // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
- //
- // Also update
- // - EventLogger#ALL_DANGEROUS_PERMISSIONS
- // - metrics_constants.proto
- throw new IllegalStateException("Unknown permission " + name);
- }
- }
-
- return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
- }
-
- /**
- * Log that a permission was revoked.
+ * Log that a permission request was granted/revoked.
*
- * @param context Context of the caller
+ * @param action the action performed
* @param name name of the permission
- * @param packageName package permission if for
+ * @param packageName package permission is for
*/
- private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
- @NonNull String packageName) {
- MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
- }
+ private void logPermission(int action, @NonNull String name, @NonNull String packageName) {
+ final LogMaker log = new LogMaker(action);
+ log.setPackageName(packageName);
+ log.addTaggedData(MetricsEvent.FIELD_PERMISSION, name);
- /**
- * Log that a permission request was granted.
- *
- * @param context Context of the caller
- * @param name name of the permission
- * @param packageName package permission if for
- */
- private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
- @NonNull String packageName) {
- MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
+ mMetricsLogger.write(log);
}
private class PermissionManagerInternalImpl extends PermissionManagerInternal {
diff --git a/com/android/server/policy/BurnInProtectionHelper.java b/com/android/server/policy/BurnInProtectionHelper.java
index 92729dc0..6886985e 100644
--- a/com/android/server/policy/BurnInProtectionHelper.java
+++ b/com/android/server/policy/BurnInProtectionHelper.java
@@ -253,7 +253,8 @@ public class BurnInProtectionHelper implements DisplayManager.DisplayListener,
public void onDisplayChanged(int displayId) {
if (displayId == mDisplay.getDisplayId()) {
if (mDisplay.getState() == Display.STATE_DOZE
- || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) {
+ || mDisplay.getState() == Display.STATE_DOZE_SUSPEND
+ || mDisplay.getState() == Display.STATE_ON_SUSPEND) {
startBurnInProtection();
} else {
cancelBurnInProtection();
diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java
index 7a2e630c..3707a5ed 100644
--- a/com/android/server/policy/GlobalActions.java
+++ b/com/android/server/policy/GlobalActions.java
@@ -58,7 +58,7 @@ class GlobalActions implements GlobalActionsListener {
public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
- if (mStatusBarInternal.isGlobalActionsDisabled()) {
+ if (mStatusBarInternal != null && mStatusBarInternal.isGlobalActionsDisabled()) {
return;
}
mKeyguardShowing = keyguardShowing;
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index 6520dc94..9162a97a 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -59,6 +59,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
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.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
@@ -66,6 +68,8 @@ import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
@@ -122,11 +126,8 @@ 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.wm.proto.WindowManagerPolicyProto.STABLE_BOUNDS;
-
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.SleepToken;
import android.app.ActivityThread;
@@ -183,6 +184,7 @@ import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UEventObserver;
@@ -197,6 +199,7 @@ import android.service.dreams.IDreamManager;
import android.service.vr.IPersistentVrStateCallbacks;
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -207,6 +210,7 @@ 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;
@@ -455,6 +459,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private AccessibilityShortcutController mAccessibilityShortcutController;
boolean mSafeMode;
+ private final ArraySet<WindowState> mScreenDecorWindows = new ArraySet<>();
WindowState mStatusBar = null;
int mStatusBarHeight;
WindowState mNavigationBar = null;
@@ -592,47 +597,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
PointerLocationView mPointerLocationView;
- // The current size of the screen; really; extends into the overscan area of
- // the screen and doesn't account for any system elements like the status bar.
- int mOverscanScreenLeft, mOverscanScreenTop;
- int mOverscanScreenWidth, mOverscanScreenHeight;
- // The current visible size of the screen; really; (ir)regardless of whether the status
- // bar can be hidden but not extending into the overscan area.
- int mUnrestrictedScreenLeft, mUnrestrictedScreenTop;
- int mUnrestrictedScreenWidth, mUnrestrictedScreenHeight;
- // Like mOverscanScreen*, but allowed to move into the overscan region where appropriate.
- int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;
- int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;
- // The current size of the screen; these may be different than (0,0)-(dw,dh)
- // if the status bar can't be hidden; in that case it effectively carves out
- // that area of the display from all other windows.
- int mRestrictedScreenLeft, mRestrictedScreenTop;
- int mRestrictedScreenWidth, mRestrictedScreenHeight;
- // During layout, the current screen borders accounting for any currently
- // visible system UI elements.
- int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom;
- // For applications requesting stable content insets, these are them.
- int mStableLeft, mStableTop, mStableRight, mStableBottom;
- // For applications requesting stable content insets but have also set the
- // fullscreen window flag, these are the stable dimensions without the status bar.
- int mStableFullscreenLeft, mStableFullscreenTop;
- int mStableFullscreenRight, mStableFullscreenBottom;
- // During layout, the current screen borders with all outer decoration
- // (status bar, input method dock) accounted for.
- int mCurLeft, mCurTop, mCurRight, mCurBottom;
- // During layout, the frame in which content should be displayed
- // to the user, accounting for all screen decoration except for any
- // space they deem as available for other content. This is usually
- // the same as mCur*, but may be larger if the screen decor has supplied
- // content insets.
- int mContentLeft, mContentTop, mContentRight, mContentBottom;
- // During layout, the frame in which voice content should be displayed
- // to the user, accounting for all screen decoration except for any
- // space they deem as available for other content.
- int mVoiceContentLeft, mVoiceContentTop, mVoiceContentRight, mVoiceContentBottom;
- // During layout, the current screen borders along which input method
- // windows are placed.
- int mDockLeft, mDockTop, mDockRight, mDockBottom;
// During layout, the layer at which the doc window is placed.
int mDockLayer;
// During layout, this is the layer of the status bar.
@@ -730,18 +694,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Display mDisplay;
- private int mDisplayRotation;
-
int mLandscapeRotation = 0; // default landscape rotation
int mSeascapeRotation = 0; // "other" landscape rotation, 180 degrees from mLandscapeRotation
int mPortraitRotation = 0; // default portrait rotation
int mUpsideDownRotation = 0; // "other" portrait rotation
- int mOverscanLeft = 0;
- int mOverscanTop = 0;
- int mOverscanRight = 0;
- int mOverscanBottom = 0;
-
// What we do when the user long presses on home
private int mLongPressOnHomeBehavior;
@@ -1057,7 +1014,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
View.NAVIGATION_BAR_UNHIDE,
View.NAVIGATION_BAR_TRANSLUCENT,
StatusBarManager.WINDOW_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
+ FLAG_TRANSLUCENT_NAVIGATION,
View.NAVIGATION_BAR_TRANSPARENT);
private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
@@ -1168,14 +1125,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
+ ", mOrientationSensorEnabled=" + mOrientationSensorEnabled
+ ", mKeyguardDrawComplete=" + mKeyguardDrawComplete
+ ", mWindowManagerDrawComplete=" + mWindowManagerDrawComplete);
- final boolean keyguardGoingAway = mWindowManagerInternal.isKeyguardGoingAway();
boolean disable = true;
// Note: We postpone the rotating of the screen until the keyguard as well as the
// window manager have reported a draw complete or the keyguard is going away in dismiss
// mode.
- if (mScreenOnEarly && mAwake && ((mKeyguardDrawComplete && mWindowManagerDrawComplete)
- || keyguardGoingAway)) {
+ if (mScreenOnEarly && mAwake && ((mKeyguardDrawComplete && mWindowManagerDrawComplete))) {
if (needSensorRunningLp()) {
disable = false;
//enable listener if not already enabled
@@ -1186,7 +1141,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// the sensor reading was cleared which can cause it to relaunch the app that
// will show in the wrong orientation first before correcting leading to app
// launch delays.
- mOrientationListener.enable(!keyguardGoingAway /* clearCurrentRotation */);
+ mOrientationListener.enable(true /* clearCurrentRotation */);
if(localLOGV) Slog.v(TAG, "Enabling listeners");
mOrientationSensorEnabled = true;
}
@@ -1661,14 +1616,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private long getAccessibilityShortcutTimeout() {
ViewConfiguration config = ViewConfiguration.get(mContext);
- try {
- return Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, mCurrentUserId) == 0
- ? config.getAccessibilityShortcutKeyTimeout()
- : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
- } catch (Settings.SettingNotFoundException e) {
- throw new RuntimeException(e);
- }
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mCurrentUserId) == 0
+ ? config.getAccessibilityShortcutKeyTimeout()
+ : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
}
private long getScreenshotChordLongPressDelay() {
@@ -2070,6 +2021,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
context.registerReceiver(mMultiuserReceiver, filter);
// monitor for system gestures
+ // TODO(multi-display): Needs to be display specific.
mSystemGestures = new SystemGesturesPointerEventListener(context,
new SystemGesturesPointerEventListener.Callbacks() {
@Override
@@ -2316,17 +2268,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return mForceDefaultOrientation;
}
- @Override
- public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
- // TODO(multi-display): Define policy for secondary displays.
- if (display.getDisplayId() == DEFAULT_DISPLAY) {
- mOverscanLeft = left;
- mOverscanTop = top;
- mOverscanRight = right;
- mOverscanBottom = bottom;
- }
- }
-
public void updateSettings() {
ContentResolver resolver = mContext.getContentResolver();
boolean updateRotation = false;
@@ -2612,7 +2553,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
- public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
+ public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+ boolean hasStatusBarServicePermission) {
+
+ final boolean isScreenDecor = (attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
+ if (mScreenDecorWindows.contains(win)) {
+ if (!isScreenDecor) {
+ // No longer has the flag set, so remove from the set.
+ mScreenDecorWindows.remove(win);
+ }
+ } else if (isScreenDecor && hasStatusBarServicePermission) {
+ mScreenDecorWindows.add(win);
+ }
+
switch (attrs.type) {
case TYPE_SYSTEM_OVERLAY:
case TYPE_SECURE_SYSTEM_OVERLAY:
@@ -3073,6 +3026,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
*/
@Override
public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
+
+ if ((attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE,
+ "PhoneWindowManager");
+ mScreenDecorWindows.add(win);
+ }
+
switch (attrs.type) {
case TYPE_STATUS_BAR:
mContext.enforceCallingOrSelfPermission(
@@ -3124,6 +3085,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mNavigationBar = null;
mNavigationBarController.setWindow(null);
}
+ mScreenDecorWindows.remove(win);
}
static final boolean PRINT_ANIM = false;
@@ -4302,12 +4264,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
+ // TODO: Should probably be moved into DisplayFrames.
public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
- int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
- Rect outStableInsets, Rect outOutsets) {
+ DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+ Rect outOutsets) {
final int fl = PolicyControl.getWindowFlags(null, attrs);
final int sysuiVis = PolicyControl.getSystemUiVisibility(null, attrs);
final int systemUiVisibility = (sysuiVis | attrs.subtreeSystemUiVisibility);
+ final int displayRotation = displayFrames.mRotation;
+ final int displayWidth = displayFrames.mDisplayWidth;
+ final int displayHeight = displayFrames.mDisplayHeight;
final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
if (useOutsets) {
@@ -4330,34 +4296,33 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int availRight, availBottom;
if (canHideNavigationBar() &&
(systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
- availRight = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
- availBottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ availRight = displayFrames.mUnrestricted.right;
+ availBottom = displayFrames.mUnrestricted.bottom;
} else {
- availRight = mRestrictedScreenLeft + mRestrictedScreenWidth;
- availBottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+ availRight = displayFrames.mRestricted.right;
+ availBottom = displayFrames.mRestricted.bottom;
}
+ outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top,
+ availRight - displayFrames.mStable.right,
+ availBottom - displayFrames.mStable.bottom);
+
if ((systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
if ((fl & FLAG_FULLSCREEN) != 0) {
- outContentInsets.set(mStableFullscreenLeft, mStableFullscreenTop,
- availRight - mStableFullscreenRight,
- availBottom - mStableFullscreenBottom);
+ outContentInsets.set(displayFrames.mStableFullscreen.left,
+ displayFrames.mStableFullscreen.top,
+ availRight - displayFrames.mStableFullscreen.right,
+ availBottom - displayFrames.mStableFullscreen.bottom);
} else {
- outContentInsets.set(mStableLeft, mStableTop,
- availRight - mStableRight, availBottom - mStableBottom);
+ outContentInsets.set(outStableInsets);
}
} else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
outContentInsets.setEmpty();
- } else if ((systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) == 0) {
- outContentInsets.set(mCurLeft, mCurTop,
- availRight - mCurRight, availBottom - mCurBottom);
} else {
- outContentInsets.set(mCurLeft, mCurTop,
- availRight - mCurRight, availBottom - mCurBottom);
+ outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top,
+ availRight - displayFrames.mCurrent.right,
+ availBottom - displayFrames.mCurrent.bottom);
}
- outStableInsets.set(mStableLeft, mStableTop,
- availRight - mStableRight, availBottom - mStableBottom);
if (taskBounds != null) {
calculateRelevantTaskInsets(taskBounds, outContentInsets,
displayWidth, displayHeight);
@@ -4394,67 +4359,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
- public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
- int displayRotation, int uiMode) {
- mDisplayRotation = displayRotation;
- final int overscanLeft, overscanTop, overscanRight, overscanBottom;
- if (isDefaultDisplay) {
- switch (displayRotation) {
- case Surface.ROTATION_90:
- overscanLeft = mOverscanTop;
- overscanTop = mOverscanRight;
- overscanRight = mOverscanBottom;
- overscanBottom = mOverscanLeft;
- break;
- case Surface.ROTATION_180:
- overscanLeft = mOverscanRight;
- overscanTop = mOverscanBottom;
- overscanRight = mOverscanLeft;
- overscanBottom = mOverscanTop;
- break;
- case Surface.ROTATION_270:
- overscanLeft = mOverscanBottom;
- overscanTop = mOverscanLeft;
- overscanRight = mOverscanTop;
- overscanBottom = mOverscanRight;
- break;
- default:
- overscanLeft = mOverscanLeft;
- overscanTop = mOverscanTop;
- overscanRight = mOverscanRight;
- overscanBottom = mOverscanBottom;
- break;
- }
- } else {
- overscanLeft = 0;
- overscanTop = 0;
- overscanRight = 0;
- overscanBottom = 0;
- }
- mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
- mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
- mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
- mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
- mSystemLeft = 0;
- mSystemTop = 0;
- mSystemRight = displayWidth;
- mSystemBottom = displayHeight;
- mUnrestrictedScreenLeft = overscanLeft;
- mUnrestrictedScreenTop = overscanTop;
- mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
- mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
- mRestrictedScreenLeft = mUnrestrictedScreenLeft;
- mRestrictedScreenTop = mUnrestrictedScreenTop;
- mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
- mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
- mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
- = mCurLeft = mUnrestrictedScreenLeft;
- mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
- = mCurTop = mUnrestrictedScreenTop;
- mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
- = mCurRight = displayWidth - overscanRight;
- mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
- = mCurBottom = displayHeight - overscanBottom;
+ public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
+ displayFrames.onBeginLayout();
+ // TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
+ mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
+ mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
mDockLayer = 0x10000000;
mStatusBarLayer = -1;
@@ -4464,13 +4373,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final Rect of = mTmpOverscanFrame;
final Rect vf = mTmpVisibleFrame;
final Rect dcf = mTmpDecorFrame;
- pf.left = df.left = of.left = vf.left = mDockLeft;
- pf.top = df.top = of.top = vf.top = mDockTop;
- pf.right = df.right = of.right = vf.right = mDockRight;
- pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;
+ vf.set(displayFrames.mDock);
+ of.set(displayFrames.mDock);
+ df.set(displayFrames.mDock);
+ pf.set(displayFrames.mDock);
dcf.setEmpty(); // Decor frame N/A for system bars.
- if (isDefaultDisplay) {
+ if (displayFrames.mDisplayId == DEFAULT_DISPLAY) {
// For purposes of putting out fake window up to steal focus, we will
// drive nav being hidden only by whether it is requested.
final int sysui = mLastSystemUiFlags;
@@ -4489,10 +4398,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
&& mStatusBar.getAttrs().height == MATCH_PARENT
&& mStatusBar.getAttrs().width == MATCH_PARENT;
- // When the navigation bar isn't visible, we put up a fake
- // input window to catch all touch events. This way we can
- // detect when the user presses anywhere to bring back the nav
- // bar and ensure the application doesn't see the event.
+ // When the navigation bar isn't visible, we put up a fake input window to catch all
+ // touch events. This way we can detect when the user presses anywhere to bring back the
+ // nav bar and ensure the application doesn't see the event.
if (navVisible || navAllowedHidden) {
if (mInputConsumer != null) {
mHandler.sendMessage(
@@ -4508,199 +4416,237 @@ public class PhoneWindowManager implements WindowManagerPolicy {
InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
}
- // For purposes of positioning and showing the nav bar, if we have
- // decided that it can't be hidden (because of the screen aspect ratio),
- // then take that into account.
+ // For purposes of positioning and showing the nav bar, if we have decided that it can't
+ // be hidden (because of the screen aspect ratio), then take that into account.
navVisible |= !canHideNavigationBar();
- boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,
- displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
- navAllowedHidden, statusBarExpandedNotKeyguard);
- if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
- mDockLeft, mDockTop, mDockRight, mDockBottom));
- updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing);
+ boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, dcf,
+ navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard);
+ if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
+ updateSysUiVisibility |= layoutStatusBar(
+ displayFrames, pf, df, of, vf, dcf, sysui, isKeyguardShowing);
if (updateSysUiVisibility) {
updateSystemUiVisibilityLw();
}
}
+ layoutScreenDecorWindows(displayFrames, pf, df, dcf);
}
- private boolean layoutStatusBar(Rect pf, Rect df, Rect of, Rect vf, Rect dcf, int sysui,
- boolean isKeyguardShowing) {
+ private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) {
+ if (mScreenDecorWindows.isEmpty()) {
+ return;
+ }
+
+ final int displayId = displayFrames.mDisplayId;
+ final Rect dockFrame = displayFrames.mDock;
+ final int displayHeight = displayFrames.mDisplayHeight;
+ final int displayWidth = displayFrames.mDisplayWidth;
+
+ for (int i = mScreenDecorWindows.size() - 1; i >= 0; --i) {
+ final WindowState w = mScreenDecorWindows.valueAt(i);
+ if (w.getDisplayId() != displayId || !w.isVisibleLw()) {
+ // Skip if not on the same display or not visible.
+ continue;
+ }
+
+ w.computeFrameLw(pf /* parentFrame */, df /* displayFrame */, df /* overlayFrame */,
+ df /* contentFrame */, df /* visibleFrame */, dcf /* decorFrame */,
+ df /* stableFrame */, df /* outsetFrame */);
+ final Rect frame = w.getFrameLw();
+
+ if (frame.left <= 0 && frame.top <= 0) {
+ // Docked at left or top.
+ if (frame.bottom >= displayHeight) {
+ // Docked left.
+ dockFrame.left = Math.max(frame.right, dockFrame.left);
+ } else if (frame.right >= displayWidth ) {
+ // Docked top.
+ dockFrame.top = Math.max(frame.bottom, dockFrame.top);
+ } else {
+ Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+ + " not docked on left or top of display. frame=" + frame
+ + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+ }
+ } else if (frame.right >= displayWidth && frame.bottom >= displayHeight) {
+ // Docked at right or bottom.
+ if (frame.top <= 0) {
+ // Docked right.
+ dockFrame.right = Math.min(frame.left, dockFrame.right);
+ } else if (frame.left <= 0) {
+ // Docked bottom.
+ dockFrame.bottom = Math.min(frame.top, dockFrame.bottom);
+ } else {
+ Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+ + " not docked on right or bottom" + " of display. frame=" + frame
+ + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+ }
+ } else {
+ // Screen decor windows are required to be docked on one of the sides of the screen.
+ Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+ + " not docked on one of the sides of the display. frame=" + frame
+ + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+ }
+ }
+
+ displayFrames.mRestricted.set(dockFrame);
+ displayFrames.mCurrent.set(dockFrame);
+ displayFrames.mVoiceContent.set(dockFrame);
+ displayFrames.mSystem.set(dockFrame);
+ displayFrames.mContent.set(dockFrame);
+ displayFrames.mRestrictedOverscan.set(dockFrame);
+ }
+
+ private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
+ Rect dcf, int sysui, boolean isKeyguardShowing) {
// decide where the status bar goes ahead of time
- if (mStatusBar != null) {
- // apply any navigation bar insets
- pf.left = df.left = of.left = mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = mUnrestrictedScreenTop;
- pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
- pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight
- + mUnrestrictedScreenTop;
- vf.left = mStableLeft;
- vf.top = mStableTop;
- vf.right = mStableRight;
- vf.bottom = mStableBottom;
-
- mStatusBarLayer = mStatusBar.getSurfaceLayer();
-
- // Let the status bar determine its size.
- mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
- vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
- dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);
-
- // For layout, the status bar is always at the top with our fixed height.
- mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
-
- boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
- boolean statusBarTranslucent = (sysui
- & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
- if (!isKeyguardShowing) {
- statusBarTranslucent &= areTranslucentBarsAllowed();
- }
-
- // If the status bar is hidden, we don't want to cause
- // windows behind it to scroll.
- if (mStatusBar.isVisibleLw() && !statusBarTransient) {
- // Status bar may go away, so the screen area it occupies
- // is available to apps but just covering them when the
- // status bar is visible.
- mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
-
- mContentTop = mVoiceContentTop = mCurTop = mDockTop;
- mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
- mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
- mContentRight = mVoiceContentRight = mCurRight = mDockRight;
-
- if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " +
- String.format(
- "dock=[%d,%d][%d,%d] content=[%d,%d][%d,%d] cur=[%d,%d][%d,%d]",
- mDockLeft, mDockTop, mDockRight, mDockBottom,
- mContentLeft, mContentTop, mContentRight, mContentBottom,
- mCurLeft, mCurTop, mCurRight, mCurBottom));
- }
- if (mStatusBar.isVisibleLw() && !mStatusBar.isAnimatingLw()
- && !statusBarTransient && !statusBarTranslucent
+ if (mStatusBar == null) {
+ return false;
+ }
+ // apply any navigation bar insets
+ of.set(displayFrames.mUnrestricted);
+ df.set(displayFrames.mUnrestricted);
+ pf.set(displayFrames.mUnrestricted);
+ vf.set(displayFrames.mStable);
+
+ mStatusBarLayer = mStatusBar.getSurfaceLayer();
+
+ // Let the status bar determine its size.
+ mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
+ vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
+ dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);
+
+ // For layout, the status bar is always at the top with our fixed height.
+ displayFrames.mStable.top = displayFrames.mUnrestricted.top + mStatusBarHeight;
+
+ boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
+ boolean statusBarTranslucent = (sysui
+ & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
+ if (!isKeyguardShowing) {
+ statusBarTranslucent &= areTranslucentBarsAllowed();
+ }
+
+ // If the status bar is hidden, we don't want to cause windows behind it to scroll.
+ if (mStatusBar.isVisibleLw() && !statusBarTransient) {
+ // Status bar may go away, so the screen area it occupies is available to apps but just
+ // covering them when the status bar is visible.
+ final Rect dockFrame = displayFrames.mDock;
+ dockFrame.top = displayFrames.mStable.top;
+ displayFrames.mContent.set(dockFrame);
+ displayFrames.mVoiceContent.set(dockFrame);
+
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
+ "dock=%s content=%s cur=%s", dockFrame.toString(),
+ displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
+
+ if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
&& !mStatusBarController.wasRecentlyTranslucent()) {
- // If the opaque status bar is currently requested to be visible,
- // and not in the process of animating on or off, then
- // we can tell the app that it is covered by it.
- mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
- }
- if (mStatusBarController.checkHiddenLw()) {
- return true;
+ // If the opaque status bar is currently requested to be visible, and not in the
+ // process of animating on or off, then we can tell the app that it is covered by it.
+ displayFrames.mSystem.top = displayFrames.mStable.top;
}
}
- return false;
+ return mStatusBarController.checkHiddenLw();
}
- private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation,
- int uiMode, int overscanLeft, int overscanRight, int overscanBottom, Rect dcf,
+ private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, Rect dcf,
boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
boolean statusBarExpandedNotKeyguard) {
- if (mNavigationBar != null) {
- boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
- // Force the navigation bar to its appropriate place and
- // size. We need to do this directly, instead of relying on
- // it to bubble up from the nav bar, because this needs to
- // change atomically with screen rotations.
- mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight,
- displayRotation);
- if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
- // It's a system nav bar or a portrait screen; nav bar goes on bottom.
- int top = displayHeight - overscanBottom
- - getNavigationBarHeight(displayRotation, uiMode);
- mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
- mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
- if (transientNavBarShowing) {
- mNavigationBarController.setBarShowingLw(true);
- } else if (navVisible) {
- mNavigationBarController.setBarShowingLw(true);
- mDockBottom = mTmpNavigationFrame.top;
- mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
- mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
- } else {
- // We currently want to hide the navigation UI - unless we expanded the status
- // bar.
- mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
- }
- if (navVisible && !navTranslucent && !navAllowedHidden
- && !mNavigationBar.isAnimatingLw()
- && !mNavigationBarController.wasRecentlyTranslucent()) {
- // If the opaque nav bar is currently requested to be visible,
- // and not in the process of animating on or off, then
- // we can tell the app that it is covered by it.
- mSystemBottom = mTmpNavigationFrame.top;
- }
- } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
- // Landscape screen; nav bar goes to the right.
- int left = displayWidth - overscanRight
- - getNavigationBarWidth(displayRotation, uiMode);
- mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
- mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
- if (transientNavBarShowing) {
- mNavigationBarController.setBarShowingLw(true);
- } else if (navVisible) {
- mNavigationBarController.setBarShowingLw(true);
- mDockRight = mTmpNavigationFrame.left;
- mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
- mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
- } else {
- // We currently want to hide the navigation UI - unless we expanded the status
- // bar.
- mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
- }
- if (navVisible && !navTranslucent && !navAllowedHidden
- && !mNavigationBar.isAnimatingLw()
- && !mNavigationBarController.wasRecentlyTranslucent()) {
- // If the nav bar is currently requested to be visible,
- // and not in the process of animating on or off, then
- // we can tell the app that it is covered by it.
- mSystemRight = mTmpNavigationFrame.left;
- }
- } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
- // Seascape screen; nav bar goes to the left.
- int right = overscanLeft + getNavigationBarWidth(displayRotation, uiMode);
- mTmpNavigationFrame.set(overscanLeft, 0, right, displayHeight);
- mStableLeft = mStableFullscreenLeft = mTmpNavigationFrame.right;
- if (transientNavBarShowing) {
- mNavigationBarController.setBarShowingLw(true);
- } else if (navVisible) {
- mNavigationBarController.setBarShowingLw(true);
- mDockLeft = mTmpNavigationFrame.right;
- // TODO: not so sure about those:
- mRestrictedScreenLeft = mRestrictedOverscanScreenLeft = mDockLeft;
- mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
- mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
- } else {
- // We currently want to hide the navigation UI - unless we expanded the status
- // bar.
- mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
- }
- if (navVisible && !navTranslucent && !navAllowedHidden
- && !mNavigationBar.isAnimatingLw()
- && !mNavigationBarController.wasRecentlyTranslucent()) {
- // If the nav bar is currently requested to be visible,
- // and not in the process of animating on or off, then
- // we can tell the app that it is covered by it.
- mSystemLeft = mTmpNavigationFrame.right;
- }
+ if (mNavigationBar == null) {
+ return false;
+ }
+ boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
+ // Force the navigation bar to its appropriate place and size. We need to do this directly,
+ // instead of relying on it to bubble up from the nav bar, because this needs to change
+ // atomically with screen rotations.
+ final int rotation = displayFrames.mRotation;
+ final int displayHeight = displayFrames.mDisplayHeight;
+ final int displayWidth = displayFrames.mDisplayWidth;
+ final Rect dockFrame = displayFrames.mDock;
+ mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);
+
+ if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
+ // It's a system nav bar or a portrait screen; nav bar goes on bottom.
+ final int top = displayFrames.mUnrestricted.bottom
+ - getNavigationBarHeight(rotation, uiMode);
+ mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
+ displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
+ if (transientNavBarShowing) {
+ mNavigationBarController.setBarShowingLw(true);
+ } else if (navVisible) {
+ mNavigationBarController.setBarShowingLw(true);
+ dockFrame.bottom = displayFrames.mRestricted.bottom
+ = displayFrames.mRestrictedOverscan.bottom = top;
+ } else {
+ // We currently want to hide the navigation UI - unless we expanded the status bar.
+ mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+ }
+ if (navVisible && !navTranslucent && !navAllowedHidden
+ && !mNavigationBar.isAnimatingLw()
+ && !mNavigationBarController.wasRecentlyTranslucent()) {
+ // If the opaque nav bar is currently requested to be visible and not in the process
+ // of animating on or off, then we can tell the app that it is covered by it.
+ displayFrames.mSystem.bottom = top;
+ }
+ } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+ // Landscape screen; nav bar goes to the right.
+ final int left = displayFrames.mUnrestricted.right
+ - getNavigationBarWidth(rotation, uiMode);
+ mTmpNavigationFrame.set(left, 0, displayFrames.mUnrestricted.right, displayHeight);
+ displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
+ if (transientNavBarShowing) {
+ mNavigationBarController.setBarShowingLw(true);
+ } else if (navVisible) {
+ mNavigationBarController.setBarShowingLw(true);
+ dockFrame.right = displayFrames.mRestricted.right
+ = displayFrames.mRestrictedOverscan.right = left;
+ } else {
+ // We currently want to hide the navigation UI - unless we expanded the status bar.
+ mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+ }
+ if (navVisible && !navTranslucent && !navAllowedHidden
+ && !mNavigationBar.isAnimatingLw()
+ && !mNavigationBarController.wasRecentlyTranslucent()) {
+ // If the nav bar is currently requested to be visible, and not in the process of
+ // animating on or off, then we can tell the app that it is covered by it.
+ displayFrames.mSystem.right = left;
+ }
+ } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+ // Seascape screen; nav bar goes to the left.
+ final int right = displayFrames.mUnrestricted.left
+ + getNavigationBarWidth(rotation, uiMode);
+ mTmpNavigationFrame.set(displayFrames.mUnrestricted.left, 0, right, displayHeight);
+ displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
+ if (transientNavBarShowing) {
+ mNavigationBarController.setBarShowingLw(true);
+ } else if (navVisible) {
+ mNavigationBarController.setBarShowingLw(true);
+ dockFrame.left = displayFrames.mRestricted.left =
+ displayFrames.mRestrictedOverscan.left = right;
+ } else {
+ // We currently want to hide the navigation UI - unless we expanded the status bar.
+ mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
}
- // Make sure the content and current rectangles are updated to
- // account for the restrictions from the navigation bar.
- mContentTop = mVoiceContentTop = mCurTop = mDockTop;
- mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
- mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
- mContentRight = mVoiceContentRight = mCurRight = mDockRight;
- mStatusBarLayer = mNavigationBar.getSurfaceLayer();
- // And compute the final frame.
- mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
- mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
- mTmpNavigationFrame, mTmpNavigationFrame);
- if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
- if (mNavigationBarController.checkHiddenLw()) {
- return true;
+ if (navVisible && !navTranslucent && !navAllowedHidden
+ && !mNavigationBar.isAnimatingLw()
+ && !mNavigationBarController.wasRecentlyTranslucent()) {
+ // If the nav bar is currently requested to be visible, and not in the process of
+ // animating on or off, then we can tell the app that it is covered by it.
+ displayFrames.mSystem.left = right;
}
}
- return false;
+
+ // Make sure the content and current rectangles are updated to account for the restrictions
+ // from the navigation bar.
+ displayFrames.mCurrent.set(dockFrame);
+ displayFrames.mVoiceContent.set(dockFrame);
+ displayFrames.mContent.set(dockFrame);
+ mStatusBarLayer = mNavigationBar.getSurfaceLayer();
+ // And compute the final frame.
+ mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
+ mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
+ mTmpNavigationFrame, mTmpNavigationFrame);
+ if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
+ return mNavigationBarController.checkHiddenLw();
}
private int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
@@ -4728,32 +4674,26 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return 0;
}
- @Override
- public void getContentRectLw(Rect r) {
- r.set(mContentLeft, mContentTop, mContentRight, mContentBottom);
- }
-
- void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
- boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf) {
+ private void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
+ boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf,
+ DisplayFrames displayFrames) {
if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) {
- // Here's a special case: if this attached window is a panel that is
- // above the dock window, and the window it is attached to is below
- // the dock window, then the frames we computed for the window it is
- // attached to can not be used because the dock is effectively part
- // of the underlying window and the attached window is floating on top
- // of the whole thing. So, we ignore the attached window and explicitly
- // compute the frames that would be appropriate without the dock.
- df.left = of.left = cf.left = vf.left = mDockLeft;
- df.top = of.top = cf.top = vf.top = mDockTop;
- df.right = of.right = cf.right = vf.right = mDockRight;
- df.bottom = of.bottom = cf.bottom = vf.bottom = mDockBottom;
+ // Here's a special case: if this attached window is a panel that is above the dock
+ // window, and the window it is attached to is below the dock window, then the frames we
+ // computed for the window it is attached to can not be used because the dock is
+ // effectively part of the underlying window and the attached window is floating on top
+ // of the whole thing. So, we ignore the attached window and explicitly compute the
+ // frames that would be appropriate without the dock.
+ vf.set(displayFrames.mDock);
+ cf.set(displayFrames.mDock);
+ of.set(displayFrames.mDock);
+ df.set(displayFrames.mDock);
} else {
- // The effective display frame of the attached window depends on
- // whether it is taking care of insetting its content. If not,
- // we need to use the parent's content frame so that the entire
- // window is positioned within that content. Otherwise we can use
- // the overscan frame and let the attached window take care of
- // positioning its content appropriately.
+ // The effective display frame of the attached window depends on whether it is taking
+ // care of insetting its content. If not, we need to use the parent's content frame so
+ // that the entire window is positioned within that content. Otherwise we can use the
+ // overscan frame and let the attached window take care of positioning its content
+ // appropriately.
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
// Set the content frame of the attached window to the parent's decor frame
// (same as content frame when IME isn't present) if specifically requested by
@@ -4762,51 +4702,37 @@ public class PhoneWindowManager implements WindowManagerPolicy {
cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
? attached.getContentFrameLw() : attached.getOverscanFrameLw());
} else {
- // If the window is resizing, then we want to base the content
- // frame on our attached content frame to resize... however,
- // things can be tricky if the attached window is NOT in resize
- // mode, in which case its content frame will be larger.
- // Ungh. So to deal with that, make sure the content frame
- // we end up using is not covering the IM dock.
+ // If the window is resizing, then we want to base the content frame on our attached
+ // content frame to resize...however, things can be tricky if the attached window is
+ // NOT in resize mode, in which case its content frame will be larger.
+ // Ungh. So to deal with that, make sure the content frame we end up using is not
+ // covering the IM dock.
cf.set(attached.getContentFrameLw());
if (attached.isVoiceInteraction()) {
- if (cf.left < mVoiceContentLeft) cf.left = mVoiceContentLeft;
- if (cf.top < mVoiceContentTop) cf.top = mVoiceContentTop;
- if (cf.right > mVoiceContentRight) cf.right = mVoiceContentRight;
- if (cf.bottom > mVoiceContentBottom) cf.bottom = mVoiceContentBottom;
+ cf.intersectUnchecked(displayFrames.mVoiceContent);
} else if (attached.getSurfaceLayer() < mDockLayer) {
- if (cf.left < mContentLeft) cf.left = mContentLeft;
- if (cf.top < mContentTop) cf.top = mContentTop;
- if (cf.right > mContentRight) cf.right = mContentRight;
- if (cf.bottom > mContentBottom) cf.bottom = mContentBottom;
+ cf.intersectUnchecked(displayFrames.mContent);
}
}
df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
of.set(insetDecors ? attached.getOverscanFrameLw() : cf);
vf.set(attached.getVisibleFrameLw());
}
- // The LAYOUT_IN_SCREEN flag is used to determine whether the attached
- // window should be positioned relative to its parent or the entire
- // screen.
- pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0
- ? attached.getFrameLw() : df);
- }
-
- private void applyStableConstraints(int sysui, int fl, Rect r) {
- if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
- // If app is requesting a stable layout, don't let the
- // content insets go below the stable values.
- if ((fl & FLAG_FULLSCREEN) != 0) {
- if (r.left < mStableFullscreenLeft) r.left = mStableFullscreenLeft;
- if (r.top < mStableFullscreenTop) r.top = mStableFullscreenTop;
- if (r.right > mStableFullscreenRight) r.right = mStableFullscreenRight;
- if (r.bottom > mStableFullscreenBottom) r.bottom = mStableFullscreenBottom;
- } else {
- if (r.left < mStableLeft) r.left = mStableLeft;
- if (r.top < mStableTop) r.top = mStableTop;
- if (r.right > mStableRight) r.right = mStableRight;
- if (r.bottom > mStableBottom) r.bottom = mStableBottom;
- }
+ // The LAYOUT_IN_SCREEN flag is used to determine whether the attached window should be
+ // positioned relative to its parent or the entire screen.
+ pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
+ }
+
+ private void applyStableConstraints(int sysui, int fl, Rect r, DisplayFrames displayFrames) {
+ if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) == 0) {
+ return;
+ }
+ // If app is requesting a stable layout, don't let the content insets go below the stable
+ // values.
+ if ((fl & FLAG_FULLSCREEN) != 0) {
+ r.intersectUnchecked(displayFrames.mStableFullscreen);
+ } else {
+ r.intersectUnchecked(displayFrames.mStable);
}
}
@@ -4821,10 +4747,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
- public void layoutWindowLw(WindowState win, WindowState attached) {
- // We've already done the navigation bar and status bar. If the status bar can receive
- // input, we need to layout it again to accomodate for the IME window.
- if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
+ public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
+ // We've already done the navigation bar, status bar, and all screen decor windows. If the
+ // status bar can receive input, we need to layout it again to accommodate for the IME
+ // window.
+ if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar
+ || mScreenDecorWindows.contains(win)) {
return;
}
final WindowManager.LayoutParams attrs = win.getAttrs();
@@ -4833,9 +4761,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
(win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
if (needsToOffsetInputMethodTarget) {
if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
- offsetInputMethodWindowLw(mLastInputMethodWindow);
+ offsetInputMethodWindowLw(mLastInputMethodWindow, displayFrames);
}
+ final int type = attrs.type;
final int fl = PolicyControl.getWindowFlags(win, attrs);
final int pfl = attrs.privateFlags;
final int sim = attrs.softInputMode;
@@ -4856,119 +4785,83 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
- if (isDefaultDisplay) {
- sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
- } else {
- sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
- }
+ sf.set(displayFrames.mStable);
- if (!isDefaultDisplay) {
- if (attached != null) {
- // If this window is attached to another, our display
- // frame is the same as the one we are attached to.
- setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
- } else {
- // Give the window full screen.
- pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
- pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
- pf.right = df.right = of.right = cf.right
- = mOverscanScreenLeft + mOverscanScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom
- = mOverscanScreenTop + mOverscanScreenHeight;
- }
- } else if (attrs.type == TYPE_INPUT_METHOD) {
- pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
- pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
- pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
+ if (type == TYPE_INPUT_METHOD) {
+ vf.set(displayFrames.mDock);
+ cf.set(displayFrames.mDock);
+ of.set(displayFrames.mDock);
+ df.set(displayFrames.mDock);
+ pf.set(displayFrames.mDock);
// IM dock windows layout below the nav bar...
- pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
// ...with content insets above the nav bar
- cf.bottom = vf.bottom = mStableBottom;
+ cf.bottom = vf.bottom = displayFrames.mStable.bottom;
if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
// The status bar forces the navigation bar while it's visible. Make sure the IME
// avoids the navigation bar in that case.
if (mNavigationBarPosition == NAV_BAR_RIGHT) {
- pf.right = df.right = of.right = cf.right = vf.right = mStableRight;
+ pf.right = df.right = of.right = cf.right = vf.right =
+ displayFrames.mStable.right;
} else if (mNavigationBarPosition == NAV_BAR_LEFT) {
- pf.left = df.left = of.left = cf.left = vf.left = mStableLeft;
+ pf.left = df.left = of.left = cf.left = vf.left = displayFrames.mStable.left;
}
}
// IM dock windows always go to the bottom of the screen.
attrs.gravity = Gravity.BOTTOM;
mDockLayer = win.getSurfaceLayer();
- } else if (attrs.type == TYPE_VOICE_INTERACTION) {
- pf.left = df.left = of.left = mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = mUnrestrictedScreenTop;
- pf.right = df.right = of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ } else if (type == TYPE_VOICE_INTERACTION) {
+ of.set(displayFrames.mUnrestricted);
+ df.set(displayFrames.mUnrestricted);
+ pf.set(displayFrames.mUnrestricted);
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
- cf.left = mDockLeft;
- cf.top = mDockTop;
- cf.right = mDockRight;
- cf.bottom = mDockBottom;
+ cf.set(displayFrames.mDock);
} else {
- cf.left = mContentLeft;
- cf.top = mContentTop;
- cf.right = mContentRight;
- cf.bottom = mContentBottom;
+ cf.set(displayFrames.mContent);
}
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
- vf.left = mCurLeft;
- vf.top = mCurTop;
- vf.right = mCurRight;
- vf.bottom = mCurBottom;
+ vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
- } else if (attrs.type == TYPE_WALLPAPER) {
- layoutWallpaper(win, pf, df, of, cf);
+ } else if (type == TYPE_WALLPAPER) {
+ layoutWallpaper(displayFrames, pf, df, of, cf);
} else if (win == mStatusBar) {
- pf.left = df.left = of.left = mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = mUnrestrictedScreenTop;
- pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
- pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight + mUnrestrictedScreenTop;
- cf.left = vf.left = mStableLeft;
- cf.top = vf.top = mStableTop;
- cf.right = vf.right = mStableRight;
- vf.bottom = mStableBottom;
+ of.set(displayFrames.mUnrestricted);
+ df.set(displayFrames.mUnrestricted);
+ pf.set(displayFrames.mUnrestricted);
+ cf.set(displayFrames.mStable);
+ vf.set(displayFrames.mStable);
if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
- cf.bottom = mContentBottom;
+ cf.bottom = displayFrames.mContent.bottom;
} else {
- cf.bottom = mDockBottom;
- vf.bottom = mContentBottom;
+ cf.bottom = displayFrames.mDock.bottom;
+ vf.bottom = displayFrames.mContent.bottom;
}
} else {
-
- // Default policy decor for the default display
- dcf.left = mSystemLeft;
- dcf.top = mSystemTop;
- dcf.right = mSystemRight;
- dcf.bottom = mSystemBottom;
- final boolean inheritTranslucentDecor = (attrs.privateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
+ dcf.set(displayFrames.mSystem);
+ final boolean inheritTranslucentDecor =
+ (attrs.privateFlags & PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
final boolean isAppWindow =
- attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&
- attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+ type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW;
final boolean topAtRest =
win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
- && (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0
- && (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0
- && (fl & WindowManager.LayoutParams.
- FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
+ && (fl & FLAG_FULLSCREEN) == 0
+ && (fl & FLAG_TRANSLUCENT_STATUS) == 0
+ && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
// Ensure policy decor includes status bar
- dcf.top = mStableTop;
+ dcf.top = displayFrames.mStable.top;
}
- if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0
+ if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0
&& (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
- && (fl & WindowManager.LayoutParams.
- FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
+ && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
// Ensure policy decor includes navigation bar
- dcf.bottom = mStableBottom;
- dcf.right = mStableRight;
+ dcf.bottom = displayFrames.mStable.bottom;
+ dcf.right = displayFrames.mStable.right;
}
}
@@ -4976,117 +4869,83 @@ public class PhoneWindowManager implements WindowManagerPolicy {
== (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ "): IN_SCREEN, INSET_DECOR");
- // This is the case for a normal activity window: we want it
- // to cover all of the screen space, and it can take care of
- // moving its contents to account for screen decorations that
- // intrude into that space.
+ // This is the case for a normal activity window: we want it to cover all of the
+ // screen space, and it can take care of moving its contents to account for screen
+ // decorations that intrude into that space.
if (attached != null) {
// If this window is attached to another, our display
// frame is the same as the one we are attached to.
- setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
+ setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf,
+ displayFrames);
} else {
- if (attrs.type == TYPE_STATUS_BAR_PANEL
- || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
- // Status bar panels are the only windows who can go on top of
- // the status bar. They are protected by the STATUS_BAR_SERVICE
- // permission, so they have the same privileges as the status
- // bar itself.
+ if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+ // Status bar panels are the only windows who can go on top of the status
+ // bar. They are protected by the STATUS_BAR_SERVICE permission, so they
+ // have the same privileges as the status bar itself.
//
// However, they should still dodge the navigation bar if it exists.
pf.left = df.left = of.left = hasNavBar
- ? mDockLeft : mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = mUnrestrictedScreenTop;
+ ? displayFrames.mDock.left : displayFrames.mUnrestricted.left;
+ pf.top = df.top = of.top = displayFrames.mUnrestricted.top;
pf.right = df.right = of.right = hasNavBar
- ? mRestrictedScreenLeft+mRestrictedScreenWidth
- : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ ? displayFrames.mRestricted.right
+ : displayFrames.mUnrestricted.right;
pf.bottom = df.bottom = of.bottom = hasNavBar
- ? mRestrictedScreenTop+mRestrictedScreenHeight
- : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ ? displayFrames.mRestricted.bottom
+ : displayFrames.mUnrestricted.bottom;
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
} else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
- && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
- && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
// Asking to layout into the overscan region, so give it that pure
// unrestricted area.
- pf.left = df.left = of.left = mOverscanScreenLeft;
- pf.top = df.top = of.top = mOverscanScreenTop;
- pf.right = df.right = of.right = mOverscanScreenLeft + mOverscanScreenWidth;
- pf.bottom = df.bottom = of.bottom = mOverscanScreenTop
- + mOverscanScreenHeight;
+ of.set(displayFrames.mOverscan);
+ df.set(displayFrames.mOverscan);
+ pf.set(displayFrames.mOverscan);
} else if (canHideNavigationBar()
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
- && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
- && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
- // Asking for layout as if the nav bar is hidden, lets the
- // application extend into the unrestricted overscan screen area. We
- // only do this for application windows to ensure no window that
- // can be above the nav bar can do this.
- pf.left = df.left = mOverscanScreenLeft;
- pf.top = df.top = mOverscanScreenTop;
- pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
- pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
- // We need to tell the app about where the frame inside the overscan
- // is, so it can inset its content by that amount -- it didn't ask
- // to actually extend itself into the overscan region.
- of.left = mUnrestrictedScreenLeft;
- of.top = mUnrestrictedScreenTop;
- of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
- of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+ // Asking for layout as if the nav bar is hidden, lets the application
+ // extend into the unrestricted overscan screen area. We only do this for
+ // application windows to ensure no window that can be above the nav bar can
+ // do this.
+ df.set(displayFrames.mOverscan);
+ pf.set(displayFrames.mOverscan);
+ // We need to tell the app about where the frame inside the overscan is, so
+ // it can inset its content by that amount -- it didn't ask to actually
+ // extend itself into the overscan region.
+ of.set(displayFrames.mUnrestricted);
} else {
- pf.left = df.left = mRestrictedOverscanScreenLeft;
- pf.top = df.top = mRestrictedOverscanScreenTop;
- pf.right = df.right = mRestrictedOverscanScreenLeft
- + mRestrictedOverscanScreenWidth;
- pf.bottom = df.bottom = mRestrictedOverscanScreenTop
- + mRestrictedOverscanScreenHeight;
+ df.set(displayFrames.mRestrictedOverscan);
+ pf.set(displayFrames.mRestrictedOverscan);
// We need to tell the app about where the frame inside the overscan
// is, so it can inset its content by that amount -- it didn't ask
// to actually extend itself into the overscan region.
- of.left = mUnrestrictedScreenLeft;
- of.top = mUnrestrictedScreenTop;
- of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
- of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ of.set(displayFrames.mUnrestricted);
}
if ((fl & FLAG_FULLSCREEN) == 0) {
if (win.isVoiceInteraction()) {
- cf.left = mVoiceContentLeft;
- cf.top = mVoiceContentTop;
- cf.right = mVoiceContentRight;
- cf.bottom = mVoiceContentBottom;
+ cf.set(displayFrames.mVoiceContent);
} else {
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
- cf.left = mDockLeft;
- cf.top = mDockTop;
- cf.right = mDockRight;
- cf.bottom = mDockBottom;
+ cf.set(displayFrames.mDock);
} else {
- cf.left = mContentLeft;
- cf.top = mContentTop;
- cf.right = mContentRight;
- cf.bottom = mContentBottom;
+ cf.set(displayFrames.mContent);
}
}
} else {
- // Full screen windows are always given a layout that is as if the
- // status bar and other transient decors are gone. This is to avoid
- // bad states when moving from a window that is not hding the
- // status bar to one that is.
- cf.left = mRestrictedScreenLeft;
- cf.top = mRestrictedScreenTop;
- cf.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
- cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+ // Full screen windows are always given a layout that is as if the status
+ // bar and other transient decors are gone. This is to avoid bad states when
+ // moving from a window that is not hiding the status bar to one that is.
+ cf.set(displayFrames.mRestricted);
}
- applyStableConstraints(sysUiFl, fl, cf);
+ applyStableConstraints(sysUiFl, fl, cf, displayFrames);
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
- vf.left = mCurLeft;
- vf.top = mCurTop;
- vf.right = mCurRight;
- vf.bottom = mCurBottom;
+ vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
@@ -5094,86 +4953,70 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
& (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
- if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
- "): IN_SCREEN");
+ if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ + "): IN_SCREEN");
// A window that has requested to fill the entire screen just
// gets everything, period.
- if (attrs.type == TYPE_STATUS_BAR_PANEL
- || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
- pf.left = df.left = of.left = cf.left = hasNavBar
- ? mDockLeft : mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
- pf.right = df.right = of.right = cf.right = hasNavBar
- ? mRestrictedScreenLeft + mRestrictedScreenWidth
- : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom = hasNavBar
- ? mRestrictedScreenTop + mRestrictedScreenHeight
- : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+ cf.set(displayFrames.mUnrestricted);
+ of.set(displayFrames.mUnrestricted);
+ df.set(displayFrames.mUnrestricted);
+ pf.set(displayFrames.mUnrestricted);
+ if (hasNavBar) {
+ pf.left = df.left = of.left = cf.left = displayFrames.mDock.left;
+ pf.right = df.right = of.right = cf.right = displayFrames.mRestricted.right;
+ pf.bottom = df.bottom = of.bottom = cf.bottom =
+ displayFrames.mRestricted.bottom;
+ }
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
- } else if (attrs.type == TYPE_VOLUME_OVERLAY) {
+ } else if (type == TYPE_VOLUME_OVERLAY) {
// Volume overlay covers everything, including the status and navbar
- pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
- pf.right = df.right = of.right = cf.right =
- mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom =
- mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ cf.set(displayFrames.mUnrestricted);
+ of.set(displayFrames.mUnrestricted);
+ df.set(displayFrames.mUnrestricted);
+ pf.set(displayFrames.mUnrestricted);
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
- } else if (attrs.type == TYPE_NAVIGATION_BAR
- || attrs.type == TYPE_NAVIGATION_BAR_PANEL) {
+ } else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) {
// The navigation bar has Real Ultimate Power.
- pf.left = df.left = of.left = mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = mUnrestrictedScreenTop;
- pf.right = df.right = of.right = mUnrestrictedScreenLeft
- + mUnrestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop
- + mUnrestrictedScreenHeight;
+ of.set(displayFrames.mUnrestricted);
+ df.set(displayFrames.mUnrestricted);
+ pf.set(displayFrames.mUnrestricted);
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out navigation bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
- } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
- || attrs.type == TYPE_BOOT_PROGRESS
- || attrs.type == TYPE_SCREENSHOT)
+ } else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT)
&& ((fl & FLAG_FULLSCREEN) != 0)) {
// Fullscreen secure system overlays get what they ask for. Screenshot region
// selection overlay should also expand to full screen.
- pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
- pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
- pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
- + mOverscanScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
- + mOverscanScreenHeight;
- } else if (attrs.type == TYPE_BOOT_PROGRESS) {
+ cf.set(displayFrames.mOverscan);
+ of.set(displayFrames.mOverscan);
+ df.set(displayFrames.mOverscan);
+ pf.set(displayFrames.mOverscan);
+ } else if (type == TYPE_BOOT_PROGRESS) {
// Boot progress screen always covers entire display.
- pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
- pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
- pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
- + mOverscanScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
- + mOverscanScreenHeight;
+ cf.set(displayFrames.mOverscan);
+ of.set(displayFrames.mOverscan);
+ df.set(displayFrames.mOverscan);
+ pf.set(displayFrames.mOverscan);
} else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
- && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
- && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
- // Asking to layout into the overscan region, so give it that pure
- // unrestricted area.
- pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
- pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
- pf.right = df.right = of.right = cf.right
- = mOverscanScreenLeft + mOverscanScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom
- = mOverscanScreenTop + mOverscanScreenHeight;
+ && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+ // Asking to layout into the overscan region, so give it that pure unrestricted
+ // area.
+ cf.set(displayFrames.mOverscan);
+ of.set(displayFrames.mOverscan);
+ df.set(displayFrames.mOverscan);
+ pf.set(displayFrames.mOverscan);
} else if (canHideNavigationBar()
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
- && (attrs.type == TYPE_STATUS_BAR
- || attrs.type == TYPE_TOAST
- || attrs.type == TYPE_DOCK_DIVIDER
- || attrs.type == TYPE_VOICE_INTERACTION_STARTING
- || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
- && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
+ && (type == TYPE_STATUS_BAR
+ || type == TYPE_TOAST
+ || type == TYPE_DOCK_DIVIDER
+ || type == TYPE_VOICE_INTERACTION_STARTING
+ || (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) {
// Asking for layout as if the nav bar is hidden, lets the
// application extend into the unrestricted screen area. We
// only do this for application windows (or toasts) to ensure no window that
@@ -5181,102 +5024,76 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// XXX This assumes that an app asking for this will also
// ask for layout in only content. We can't currently figure out
// what the screen would be if only laying out to hide the nav bar.
- pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
- pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
- pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft
- + mUnrestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
- + mUnrestrictedScreenHeight;
+ cf.set(displayFrames.mUnrestricted);
+ of.set(displayFrames.mUnrestricted);
+ df.set(displayFrames.mUnrestricted);
+ pf.set(displayFrames.mUnrestricted);
} else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
- pf.left = df.left = of.left = mRestrictedScreenLeft;
- pf.top = df.top = of.top = mRestrictedScreenTop;
- pf.right = df.right = of.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = mRestrictedScreenTop
- + mRestrictedScreenHeight;
+ of.set(displayFrames.mRestricted);
+ df.set(displayFrames.mRestricted);
+ pf.set(displayFrames.mRestricted);
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
- cf.left = mDockLeft;
- cf.top = mDockTop;
- cf.right = mDockRight;
- cf.bottom = mDockBottom;
+ cf.set(displayFrames.mDock);
} else {
- cf.left = mContentLeft;
- cf.top = mContentTop;
- cf.right = mContentRight;
- cf.bottom = mContentBottom;
+ cf.set(displayFrames.mContent);
}
} else {
- pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
- pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
- pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
- + mRestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
- + mRestrictedScreenHeight;
+ cf.set(displayFrames.mRestricted);
+ of.set(displayFrames.mRestricted);
+ df.set(displayFrames.mRestricted);
+ pf.set(displayFrames.mRestricted);
}
- applyStableConstraints(sysUiFl, fl, cf);
+ applyStableConstraints(sysUiFl, fl, cf,displayFrames);
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
- vf.left = mCurLeft;
- vf.top = mCurTop;
- vf.right = mCurRight;
- vf.bottom = mCurBottom;
+ vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
} else if (attached != null) {
- if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
- "): attached to " + attached);
+ if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ + "): attached to " + attached);
// A child window should be placed inside of the same visible
// frame that its parent had.
- setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);
+ setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf,
+ displayFrames);
} else {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
"): normal window");
// Otherwise, a normal window must be placed inside the content
// of all screen decorations.
- if (attrs.type == TYPE_STATUS_BAR_PANEL || attrs.type == TYPE_VOLUME_OVERLAY) {
+ if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_VOLUME_OVERLAY) {
// Status bar panels and the volume dialog are the only windows who can go on
- // top of the status bar. They are protected by the STATUS_BAR_SERVICE
- // permission, so they have the same privileges as the status
- // bar itself.
- pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
- pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
- pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
- + mRestrictedScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
- + mRestrictedScreenHeight;
- } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT) {
+ // top of the status bar. They are protected by the STATUS_BAR_SERVICE
+ // permission, so they have the same privileges as the status bar itself.
+ cf.set(displayFrames.mRestricted);
+ of.set(displayFrames.mRestricted);
+ df.set(displayFrames.mRestricted);
+ pf.set(displayFrames.mRestricted);
+ } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
// These dialogs are stable to interim decor changes.
- pf.left = df.left = of.left = cf.left = mStableLeft;
- pf.top = df.top = of.top = cf.top = mStableTop;
- pf.right = df.right = of.right = cf.right = mStableRight;
- pf.bottom = df.bottom = of.bottom = cf.bottom = mStableBottom;
+ cf.set(displayFrames.mStable);
+ of.set(displayFrames.mStable);
+ df.set(displayFrames.mStable);
+ pf.set(displayFrames.mStable);
} else {
- pf.left = mContentLeft;
- pf.top = mContentTop;
- pf.right = mContentRight;
- pf.bottom = mContentBottom;
+ pf.set(displayFrames.mContent);
if (win.isVoiceInteraction()) {
- df.left = of.left = cf.left = mVoiceContentLeft;
- df.top = of.top = cf.top = mVoiceContentTop;
- df.right = of.right = cf.right = mVoiceContentRight;
- df.bottom = of.bottom = cf.bottom = mVoiceContentBottom;
+ cf.set(displayFrames.mVoiceContent);
+ of.set(displayFrames.mVoiceContent);
+ df.set(displayFrames.mVoiceContent);
} else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
- df.left = of.left = cf.left = mDockLeft;
- df.top = of.top = cf.top = mDockTop;
- df.right = of.right = cf.right = mDockRight;
- df.bottom = of.bottom = cf.bottom = mDockBottom;
+ cf.set(displayFrames.mDock);
+ of.set(displayFrames.mDock);
+ df.set(displayFrames.mDock);
} else {
- df.left = of.left = cf.left = mContentLeft;
- df.top = of.top = cf.top = mContentTop;
- df.right = of.right = cf.right = mContentRight;
- df.bottom = of.bottom = cf.bottom = mContentBottom;
+ cf.set(displayFrames.mContent);
+ of.set(displayFrames.mContent);
+ df.set(displayFrames.mContent);
}
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
- vf.left = mCurLeft;
- vf.top = mCurTop;
- vf.right = mCurRight;
- vf.bottom = mCurBottom;
+ vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
@@ -5286,11 +5103,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
// Also, we don't allow windows in multi-window mode to extend out of the screen.
- if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR
+ if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
&& !win.isInMultiWindowMode()) {
df.left = df.top = -10000;
df.right = df.bottom = 10000;
- if (attrs.type != TYPE_WALLPAPER) {
+ if (type != TYPE_WALLPAPER) {
of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
}
@@ -5306,7 +5123,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
osf.set(cf.left, cf.top, cf.right, cf.bottom);
int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
if (outset > 0) {
- int rotation = mDisplayRotation;
+ int rotation = displayFrames.mRotation;
if (rotation == Surface.ROTATION_0) {
osf.bottom += outset;
} else if (rotation == Surface.ROTATION_90) {
@@ -5323,7 +5140,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
+ ": sim=#" + Integer.toHexString(sim)
- + " attach=" + attached + " type=" + attrs.type
+ + " attach=" + attached + " type=" + type
+ String.format(" flags=0x%08x", fl)
+ " pf=" + pf.toShortString() + " df=" + df.toShortString()
+ " of=" + of.toShortString()
@@ -5336,62 +5153,42 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Dock windows carve out the bottom of the screen, so normal windows
// can't appear underneath them.
- if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleLw()
+ if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
&& !win.getGivenInsetsPendingLw()) {
setLastInputMethodWindowLw(null, null);
- offsetInputMethodWindowLw(win);
+ offsetInputMethodWindowLw(win, displayFrames);
}
- if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
+ if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
&& !win.getGivenInsetsPendingLw()) {
- offsetVoiceInputWindowLw(win);
+ offsetVoiceInputWindowLw(win, displayFrames);
}
}
- private void layoutWallpaper(WindowState win, Rect pf, Rect df, Rect of, Rect cf) {
-
- // The wallpaper also has Real Ultimate Power, but we want to tell
- // it about the overscan area.
- pf.left = df.left = mOverscanScreenLeft;
- pf.top = df.top = mOverscanScreenTop;
- pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
- pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
- of.left = cf.left = mUnrestrictedScreenLeft;
- of.top = cf.top = mUnrestrictedScreenTop;
- of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
- of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ private void layoutWallpaper(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect cf) {
+ // The wallpaper has Real Ultimate Power, but we want to tell it about the overscan area.
+ df.set(displayFrames.mOverscan);
+ pf.set(displayFrames.mOverscan);
+ cf.set(displayFrames.mUnrestricted);
+ of.set(displayFrames.mUnrestricted);
}
- private void offsetInputMethodWindowLw(WindowState win) {
+ private void offsetInputMethodWindowLw(WindowState win, DisplayFrames displayFrames) {
int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
top += win.getGivenContentInsetsLw().top;
- if (mContentBottom > top) {
- mContentBottom = top;
- }
- if (mVoiceContentBottom > top) {
- mVoiceContentBottom = top;
- }
+ displayFrames.mContent.bottom = Math.min(displayFrames.mContent.bottom, top);
+ displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
top = win.getVisibleFrameLw().top;
top += win.getGivenVisibleInsetsLw().top;
- if (mCurBottom > top) {
- mCurBottom = top;
- }
+ displayFrames.mCurrent.bottom = Math.min(displayFrames.mCurrent.bottom, top);
if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom="
- + mDockBottom + " mContentBottom="
- + mContentBottom + " mCurBottom=" + mCurBottom);
+ + displayFrames.mDock.bottom + " mContentBottom="
+ + displayFrames.mContent.bottom + " mCurBottom=" + displayFrames.mCurrent.bottom);
}
- private void offsetVoiceInputWindowLw(WindowState win) {
+ private void offsetVoiceInputWindowLw(WindowState win, DisplayFrames displayFrames) {
int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
top += win.getGivenContentInsetsLw().top;
- if (mVoiceContentBottom > top) {
- mVoiceContentBottom = top;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public void finishLayoutLw() {
- return;
+ displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
}
/** {@inheritDoc} */
@@ -5798,6 +5595,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
void initializeHdmiState() {
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
+ try {
+ initializeHdmiStateInternal();
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ }
+
+ void initializeHdmiStateInternal() {
boolean plugged = false;
// watch for HDMI plug messages if the hdmi switch exists
if (new File("/sys/devices/virtual/switch/hdmi/state").exists()) {
@@ -8235,11 +8041,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
- public int getInputMethodWindowVisibleHeightLw() {
- return mDockBottom - mCurBottom;
- }
-
- @Override
public void setCurrentUserLw(int newUserId) {
mCurrentUserId = newUserId;
if (mKeyguardDelegate != null) {
@@ -8328,8 +8129,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- new Rect(mStableLeft, mStableTop, mStableRight, mStableBottom).writeToProto(proto,
- STABLE_BOUNDS);
proto.end(token);
}
@@ -8435,58 +8234,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
- pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft);
- pw.print(","); pw.print(mOverscanScreenTop);
- pw.print(") "); pw.print(mOverscanScreenWidth);
- pw.print("x"); pw.println(mOverscanScreenHeight);
- if (mOverscanLeft != 0 || mOverscanTop != 0
- || mOverscanRight != 0 || mOverscanBottom != 0) {
- pw.print(prefix); pw.print("mOverscan left="); pw.print(mOverscanLeft);
- pw.print(" top="); pw.print(mOverscanTop);
- pw.print(" right="); pw.print(mOverscanRight);
- pw.print(" bottom="); pw.println(mOverscanBottom);
- }
- pw.print(prefix); pw.print("mRestrictedOverscanScreen=(");
- pw.print(mRestrictedOverscanScreenLeft);
- pw.print(","); pw.print(mRestrictedOverscanScreenTop);
- pw.print(") "); pw.print(mRestrictedOverscanScreenWidth);
- pw.print("x"); pw.println(mRestrictedOverscanScreenHeight);
- pw.print(prefix); pw.print("mUnrestrictedScreen=("); pw.print(mUnrestrictedScreenLeft);
- pw.print(","); pw.print(mUnrestrictedScreenTop);
- pw.print(") "); pw.print(mUnrestrictedScreenWidth);
- pw.print("x"); pw.println(mUnrestrictedScreenHeight);
- pw.print(prefix); pw.print("mRestrictedScreen=("); pw.print(mRestrictedScreenLeft);
- pw.print(","); pw.print(mRestrictedScreenTop);
- pw.print(") "); pw.print(mRestrictedScreenWidth);
- pw.print("x"); pw.println(mRestrictedScreenHeight);
- pw.print(prefix); pw.print("mStableFullscreen=("); pw.print(mStableFullscreenLeft);
- pw.print(","); pw.print(mStableFullscreenTop);
- pw.print(")-("); pw.print(mStableFullscreenRight);
- pw.print(","); pw.print(mStableFullscreenBottom); pw.println(")");
- pw.print(prefix); pw.print("mStable=("); pw.print(mStableLeft);
- pw.print(","); pw.print(mStableTop);
- pw.print(")-("); pw.print(mStableRight);
- pw.print(","); pw.print(mStableBottom); pw.println(")");
- pw.print(prefix); pw.print("mSystem=("); pw.print(mSystemLeft);
- pw.print(","); pw.print(mSystemTop);
- pw.print(")-("); pw.print(mSystemRight);
- pw.print(","); pw.print(mSystemBottom); pw.println(")");
- pw.print(prefix); pw.print("mCur=("); pw.print(mCurLeft);
- pw.print(","); pw.print(mCurTop);
- pw.print(")-("); pw.print(mCurRight);
- pw.print(","); pw.print(mCurBottom); pw.println(")");
- pw.print(prefix); pw.print("mContent=("); pw.print(mContentLeft);
- pw.print(","); pw.print(mContentTop);
- pw.print(")-("); pw.print(mContentRight);
- pw.print(","); pw.print(mContentBottom); pw.println(")");
- pw.print(prefix); pw.print("mVoiceContent=("); pw.print(mVoiceContentLeft);
- pw.print(","); pw.print(mVoiceContentTop);
- pw.print(")-("); pw.print(mVoiceContentRight);
- pw.print(","); pw.print(mVoiceContentBottom); pw.println(")");
- pw.print(prefix); pw.print("mDock=("); pw.print(mDockLeft);
- pw.print(","); pw.print(mDockTop);
- pw.print(")-("); pw.print(mDockRight);
- pw.print(","); pw.print(mDockBottom); pw.println(")");
pw.print(prefix); pw.print("mDockLayer="); pw.print(mDockLayer);
pw.print(" mStatusBarLayer="); pw.println(mStatusBarLayer);
pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
diff --git a/com/android/server/power/BatterySaverPolicy.java b/com/android/server/power/BatterySaverPolicy.java
index 1781d8c6..15121b80 100644
--- a/com/android/server/power/BatterySaverPolicy.java
+++ b/com/android/server/power/BatterySaverPolicy.java
@@ -15,50 +15,33 @@
*/
package com.android.server.power;
-import android.annotation.IntDef;
import android.content.ContentResolver;
+import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerSaveState;
import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.Slog;
-import android.os.PowerSaveState;
+
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
/**
* Class to decide whether to turn on battery saver mode for specific service
+ *
+ * Test: atest BatterySaverPolicyTest
*/
public class BatterySaverPolicy extends ContentObserver {
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({ServiceType.GPS,
- ServiceType.VIBRATION,
- ServiceType.ANIMATION,
- ServiceType.FULL_BACKUP,
- ServiceType.KEYVALUE_BACKUP,
- ServiceType.NETWORK_FIREWALL,
- ServiceType.SCREEN_BRIGHTNESS,
- ServiceType.SOUND,
- ServiceType.BATTERY_STATS,
- ServiceType.DATA_SAVER})
- public @interface ServiceType {
- int NULL = 0;
- int GPS = 1;
- int VIBRATION = 2;
- int ANIMATION = 3;
- int FULL_BACKUP = 4;
- int KEYVALUE_BACKUP = 5;
- int NETWORK_FIREWALL = 6;
- int SCREEN_BRIGHTNESS = 7;
- int SOUND = 8;
- int BATTERY_STATS = 9;
- int DATA_SAVER = 10;
- }
-
private static final String TAG = "BatterySaverPolicy";
// Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
@@ -79,8 +62,16 @@ public class BatterySaverPolicy extends ContentObserver {
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_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 final KeyValueListParser mParser = new KeyValueListParser(',');
+ private static String mSettings;
+ private static String mDeviceSpecificSettings;
+ private static String mDeviceSpecificSettingsSource; // For dump() only.
/**
* {@code true} if vibration is disabled in battery saver mode.
@@ -164,51 +155,189 @@ public class BatterySaverPolicy extends ContentObserver {
*/
private float mAdjustBrightnessFactor;
+ /**
+ * Whether to put all apps in the stand-by mode or not for job scheduler.
+ */
+ private boolean mForceAllAppsStandbyJobs;
+
+ /**
+ * Whether to put all apps in the stand-by mode or not for alarms.
+ */
+ private boolean mForceAllAppsStandbyAlarms;
+
+ /**
+ * Weather to show non-essential sensors (e.g. edge sensors) or not.
+ */
+ private boolean mOptionalSensorsDisabled;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private Context mContext;
+
+ @GuardedBy("mLock")
private ContentResolver mContentResolver;
+ @GuardedBy("mLock")
+ private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>();
+
+ /**
+ * List of [Filename -> content] that should be written when battery saver is activated
+ * and the screen is on.
+ *
+ * We use this to change the max CPU frequencies.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<String, String> mScreenOnFiles;
+
+ /**
+ * List of [Filename -> content] that should be written when battery saver is activated
+ * and the screen is off.
+ *
+ * We use this to change the max CPU frequencies.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<String, String> mScreenOffFiles;
+
+ public interface BatterySaverPolicyListener {
+ void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
+ }
+
public BatterySaverPolicy(Handler handler) {
super(handler);
}
- public void start(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ public void systemReady(Context context) {
+ synchronized (mLock) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
- mContentResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS), false, this);
+ }
+ onChange(true, null);
+ }
+
+ public void addListener(BatterySaverPolicyListener listener) {
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ @VisibleForTesting
+ String getGlobalSetting(String key) {
+ return Settings.Global.getString(mContentResolver, key);
+ }
+
+ @VisibleForTesting
+ int getDeviceSpecificConfigResId() {
+ return R.string.config_batterySaverDeviceSpecificConfig;
+ }
+
+ @VisibleForTesting
+ void onChangeForTest() {
onChange(true, null);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
- final String value = Settings.Global.getString(mContentResolver,
- Settings.Global.BATTERY_SAVER_CONSTANTS);
- updateConstants(value);
+ final BatterySaverPolicyListener[] listeners;
+ synchronized (mLock) {
+ // Load the non-device-specific setting.
+ final String setting = getGlobalSetting(Settings.Global.BATTERY_SAVER_CONSTANTS);
+
+ // Load the device specific setting.
+ // We first check the global setting, and if it's empty or the string "null" is set,
+ // use the default value from config.xml.
+ String deviceSpecificSetting = getGlobalSetting(
+ Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS);
+ mDeviceSpecificSettingsSource =
+ Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS;
+
+ if (TextUtils.isEmpty(deviceSpecificSetting) || "null".equals(deviceSpecificSetting)) {
+ deviceSpecificSetting =
+ mContext.getString(getDeviceSpecificConfigResId());
+ mDeviceSpecificSettingsSource = "(overlay)";
+ }
+
+ // Update.
+ updateConstantsLocked(setting, deviceSpecificSetting);
+
+ listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
+ }
+
+ // Notify the listeners.
+ for (BatterySaverPolicyListener listener : listeners) {
+ listener.onBatterySaverPolicyChanged(this);
+ }
}
@VisibleForTesting
- void updateConstants(final String value) {
- synchronized (BatterySaverPolicy.this) {
- try {
- mParser.setString(value);
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Bad battery saver constants");
+ void updateConstantsLocked(final String setting, final String deviceSpecificSetting) {
+ mSettings = setting;
+ mDeviceSpecificSettings = deviceSpecificSetting;
+
+ final KeyValueListParser parser = new KeyValueListParser(',');
+
+ // Non-device-specific parameters.
+ try {
+ parser.setString(setting);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad battery saver constants: " + setting);
+ }
+
+ mVibrationDisabled = parser.getBoolean(KEY_VIBRATION_DISABLED, true);
+ mAnimationDisabled = parser.getBoolean(KEY_ANIMATION_DISABLED, true);
+ mSoundTriggerDisabled = parser.getBoolean(KEY_SOUNDTRIGGER_DISABLED, true);
+ mFullBackupDeferred = parser.getBoolean(KEY_FULLBACKUP_DEFERRED, true);
+ mKeyValueBackupDeferred = parser.getBoolean(KEY_KEYVALUE_DEFERRED, true);
+ mFireWallDisabled = parser.getBoolean(KEY_FIREWALL_DISABLED, false);
+ 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);
+ mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
+
+ // Get default value from Settings.Secure
+ final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
+ GPS_MODE_NO_CHANGE);
+ mGpsMode = parser.getInt(KEY_GPS_MODE, defaultGpsMode);
+
+ // Non-device-specific parameters.
+ try {
+ parser.setString(deviceSpecificSetting);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad device specific battery saver constants: "
+ + 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;
}
- mVibrationDisabled = mParser.getBoolean(KEY_VIBRATION_DISABLED, true);
- mAnimationDisabled = mParser.getBoolean(KEY_ANIMATION_DISABLED, true);
- mSoundTriggerDisabled = mParser.getBoolean(KEY_SOUNDTRIGGER_DISABLED, true);
- mFullBackupDeferred = mParser.getBoolean(KEY_FULLBACKUP_DEFERRED, true);
- mKeyValueBackupDeferred = mParser.getBoolean(KEY_KEYVALUE_DEFERRED, true);
- mFireWallDisabled = mParser.getBoolean(KEY_FIREWALL_DISABLED, false);
- mAdjustBrightnessDisabled = mParser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
- mAdjustBrightnessFactor = mParser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
- mDataSaverDisabled = mParser.getBoolean(KEY_DATASAVER_DISABLED, true);
-
- // Get default value from Settings.Secure
- final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
- GPS_MODE_DISABLED_WHEN_SCREEN_OFF);
- mGpsMode = mParser.getInt(KEY_GPS_MODE, defaultGpsMode);
+ ret.put(path, parser.getString(key, ""));
}
+ return ret;
}
/**
@@ -221,7 +350,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @return State data that contains battery saver data
*/
public PowerSaveState getBatterySaverPolicy(@ServiceType int type, boolean realMode) {
- synchronized (BatterySaverPolicy.this) {
+ synchronized (mLock) {
final PowerSaveState.Builder builder = new PowerSaveState.Builder()
.setGlobalBatterySaverEnabled(realMode);
if (!realMode) {
@@ -258,6 +387,15 @@ public class BatterySaverPolicy extends ContentObserver {
case ServiceType.VIBRATION:
return builder.setBatterySaverEnabled(mVibrationDisabled)
.build();
+ case ServiceType.FORCE_ALL_APPS_STANDBY_JOBS:
+ return builder.setBatterySaverEnabled(mForceAllAppsStandbyJobs)
+ .build();
+ case ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS:
+ return builder.setBatterySaverEnabled(mForceAllAppsStandbyAlarms)
+ .build();
+ case ServiceType.OPTIONAL_SENSORS:
+ return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
+ .build();
default:
return builder.setBatterySaverEnabled(realMode)
.build();
@@ -265,23 +403,57 @@ public class BatterySaverPolicy extends ContentObserver {
}
}
+ public ArrayMap<String, String> getFileValues(boolean screenOn) {
+ synchronized (mLock) {
+ return screenOn ? mScreenOnFiles : mScreenOffFiles;
+ }
+ }
+
public void dump(PrintWriter pw) {
- pw.println();
- pw.println("Battery saver policy");
- pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
- pw.println(" value: " + Settings.Global.getString(mContentResolver,
- Settings.Global.BATTERY_SAVER_CONSTANTS));
-
- pw.println();
- pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
- pw.println(" " + KEY_ANIMATION_DISABLED + "=" + mAnimationDisabled);
- pw.println(" " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
- pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
- pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
- pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
- pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
- pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
- pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
+ 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();
+ pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
+ pw.println(" " + KEY_ANIMATION_DISABLED + "=" + mAnimationDisabled);
+ pw.println(" " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
+ pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
+ pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
+ pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+ 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_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
+ pw.println();
+ pw.print(" Screen On Files:\n");
+ dumpMap(pw, " ", mScreenOnFiles);
+ pw.println();
+
+ pw.print(" Screen Off Files:\n");
+ dumpMap(pw, " ", mScreenOffFiles);
+ pw.println();
+ }
+ }
+
+ private void dumpMap(PrintWriter pw, String prefix, ArrayMap<String, String> map) {
+ if (map == null) {
+ return;
+ }
+ final int size = map.size();
+ for (int i = 0; i < size; i++) {
+ pw.print(prefix);
+ pw.print(map.keyAt(i));
+ pw.print(": '");
+ pw.print(map.valueAt(i));
+ pw.println("'");
+ }
}
}
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index 2494bded..a47b8095 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -43,6 +43,7 @@ import android.os.IPowerManager;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
@@ -62,7 +63,6 @@ import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.util.EventLog;
import android.util.KeyValueListParser;
-import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -70,6 +70,7 @@ 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;
import com.android.internal.app.IBatteryStats;
@@ -89,11 +90,8 @@ 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.power.BatterySaverPolicy.ServiceType;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
+import com.android.server.power.batterysaver.BatterySaverController;
+
import libcore.util.Objects;
import java.io.FileDescriptor;
@@ -226,6 +224,7 @@ public final class PowerManagerService extends SystemService
private final PowerManagerHandler mHandler;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatterySaverPolicy mBatterySaverPolicy;
+ private final BatterySaverController mBatterySaverController;
private LightsManager mLightsManager;
private BatteryManagerInternal mBatteryManagerInternal;
@@ -553,9 +552,6 @@ public final class PowerManagerService extends SystemService
// True if double tap to wake is enabled
private boolean mDoubleTapWakeEnabled;
- private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners
- = new ArrayList<PowerManagerInternal.LowPowerModeListener>();
-
// True if we are currently in VR Mode.
private boolean mIsVrModeEnabled;
@@ -643,7 +639,10 @@ public final class PowerManagerService extends SystemService
mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
mConstants = new Constants(mHandler);
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+
mBatterySaverPolicy = new BatterySaverPolicy(mHandler);
+ mBatterySaverController = new BatterySaverController(mContext,
+ BackgroundThread.get().getLooper(), mBatterySaverPolicy);
synchronized (mLock) {
mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -668,7 +667,6 @@ public final class PowerManagerService extends SystemService
PowerManagerService(Context context, BatterySaverPolicy batterySaverPolicy) {
super(context);
- mBatterySaverPolicy = batterySaverPolicy;
mContext = context;
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
@@ -678,6 +676,10 @@ public final class PowerManagerService extends SystemService
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
mDisplaySuspendBlocker = null;
mWakeLockSuspendBlocker = null;
+
+ mBatterySaverPolicy = batterySaverPolicy;
+ mBatterySaverController = new BatterySaverController(context,
+ BackgroundThread.getHandler().getLooper(), batterySaverPolicy);
}
@Override
@@ -750,6 +752,7 @@ public final class PowerManagerService extends SystemService
mDisplayManagerInternal.initPowerManagement(
mDisplayPowerCallbacks, mHandler, sensorManager);
+
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -759,7 +762,9 @@ public final class PowerManagerService extends SystemService
final ContentResolver resolver = mContext.getContentResolver();
mConstants.start(resolver);
- mBatterySaverPolicy.start(resolver);
+
+ mBatterySaverController.systemReady();
+ mBatterySaverPolicy.systemReady(mContext);
// Register for settings changes.
resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -994,36 +999,9 @@ public final class PowerManagerService extends SystemService
if (mLowPowerModeEnabled != lowPowerModeEnabled) {
mLowPowerModeEnabled = lowPowerModeEnabled;
- powerHintInternal(PowerHint.LOW_POWER, lowPowerModeEnabled ? 1 : 0);
- postAfterBootCompleted(new Runnable() {
- @Override
- public void run() {
- Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
- .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, mLowPowerModeEnabled)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcast(intent);
- ArrayList<PowerManagerInternal.LowPowerModeListener> listeners;
- synchronized (mLock) {
- listeners = new ArrayList<PowerManagerInternal.LowPowerModeListener>(
- mLowPowerModeListeners);
- }
- for (int i = 0; i < listeners.size(); i++) {
- final PowerManagerInternal.LowPowerModeListener listener = listeners.get(i);
- final PowerSaveState result =
- mBatterySaverPolicy.getBatterySaverPolicy(
- listener.getServiceType(), lowPowerModeEnabled);
- listener.onLowPowerModeChanged(result);
- }
- intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- // Send internal version that requires signature permission.
- intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.DEVICE_POWER);
- }
- });
+
+ postAfterBootCompleted(() ->
+ mBatterySaverController.enableBatterySaver(mLowPowerModeEnabled));
}
}
@@ -2364,9 +2342,13 @@ public final class PowerManagerService extends SystemService
if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
- if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
- && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
- mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+ if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
+ mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ }
+ if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
+ mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
+ }
}
mDisplayPowerRequest.dozeScreenBrightness =
mDozeScreenBrightnessOverrideFromDreamManager;
@@ -3123,7 +3105,7 @@ public final class PowerManagerService extends SystemService
mIsVrModeEnabled = enabled;
}
- private void powerHintInternal(int hintId, int data) {
+ public static void powerHintInternal(int hintId, int data) {
nativeSendPowerHint(hintId, data);
}
@@ -4392,7 +4374,7 @@ public final class PowerManagerService extends SystemService
* Gets the reason for the last time the phone had to reboot.
*
* @return The reason the phone last shut down as an int or
- * {@link PowerManager.SHUTDOWN_REASON_UNKNOWN} if the file could not be opened.
+ * {@link PowerManager#SHUTDOWN_REASON_UNKNOWN} if the file could not be opened.
*/
@Override // Binder call
public int getLastShutdownReason() {
@@ -4676,6 +4658,7 @@ public final class PowerManagerService extends SystemService
case Display.STATE_OFF:
case Display.STATE_DOZE:
case Display.STATE_DOZE_SUSPEND:
+ case Display.STATE_ON_SUSPEND:
case Display.STATE_ON:
case Display.STATE_VR:
break;
@@ -4714,9 +4697,7 @@ public final class PowerManagerService extends SystemService
@Override
public void registerLowPowerModeObserver(LowPowerModeListener listener) {
- synchronized (mLock) {
- mLowPowerModeListeners.add(listener);
- }
+ mBatterySaverController.addListener(listener);
}
@Override
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 755c5f09..6bf725e1 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -68,7 +68,8 @@ import java.nio.charset.StandardCharsets;
public final class ShutdownThread extends Thread {
// constants
private static final String TAG = "ShutdownThread";
- private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
+ private static final int ACTION_DONE_POLL_WAIT_MS = 500;
+ private static final int RADIOS_STATE_POLL_SLEEP_MS = 100;
// maximum time we wait for the shutdown broadcast before going on.
private static final int MAX_BROADCAST_TIME = 10*1000;
private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
@@ -471,7 +472,7 @@ public final class ShutdownThread extends Thread {
sInstance.setRebootProgress(status, null);
}
try {
- mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
+ mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
} catch (InterruptedException e) {
}
}
@@ -565,7 +566,7 @@ public final class ShutdownThread extends Thread {
sInstance.setRebootProgress(status, null);
}
try {
- mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
+ mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
} catch (InterruptedException e) {
}
}
@@ -710,8 +711,7 @@ public final class ShutdownThread extends Thread {
done[0] = true;
break;
}
- SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
-
+ SystemClock.sleep(RADIOS_STATE_POLL_SLEEP_MS);
delay = endTime - SystemClock.elapsedRealtime();
}
}
diff --git a/com/android/server/power/batterysaver/BatterySaverController.java b/com/android/server/power/batterysaver/BatterySaverController.java
new file mode 100644
index 00000000..b3e85383
--- /dev/null
+++ b/com/android/server/power/batterysaver/BatterySaverController.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal.LowPowerModeListener;
+import android.os.PowerSaveState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.widget.Toast;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.power.BatterySaverPolicy;
+import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
+import com.android.server.power.PowerManagerService;
+
+import java.util.ArrayList;
+
+/**
+ * Responsible for battery saver mode transition logic.
+ */
+public class BatterySaverController implements BatterySaverPolicyListener {
+ static final String TAG = "BatterySaverController";
+
+ static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final MyHandler mHandler;
+ private final FileUpdater mFileUpdater;
+
+ private PowerManager mPowerManager;
+
+ private final BatterySaverPolicy mBatterySaverPolicy;
+
+ @GuardedBy("mLock")
+ private final ArrayList<LowPowerModeListener> mListeners = new ArrayList<>();
+
+ @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();
+ break;
+ }
+ }
+ };
+
+ /**
+ * Constructor.
+ */
+ public BatterySaverController(Context context, Looper looper, BatterySaverPolicy policy) {
+ mContext = context;
+ mHandler = new MyHandler(looper);
+ mBatterySaverPolicy = policy;
+ mBatterySaverPolicy.addListener(this);
+ mFileUpdater = new FileUpdater(context);
+ }
+
+ /**
+ * Add a listener.
+ */
+ public void addListener(LowPowerModeListener listener) {
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Called by {@link PowerManagerService} on system ready..
+ */
+ public void systemReady() {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ private PowerManager getPowerManager() {
+ if (mPowerManager == null) {
+ mPowerManager =
+ Preconditions.checkNotNull(mContext.getSystemService(PowerManager.class));
+ }
+ return mPowerManager;
+ }
+
+ @Override
+ public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
+ mHandler.postStateChanged();
+ }
+
+ private class MyHandler extends Handler {
+ private final int MSG_STATE_CHANGED = 1;
+
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void postStateChanged() {
+ obtainMessage(MSG_STATE_CHANGED).sendToTarget();
+ }
+
+ @Override
+ public void dispatchMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_STATE_CHANGED:
+ handleBatterySaverStateChanged();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called by {@link PowerManagerService} to update the battery saver stete.
+ */
+ public void enableBatterySaver(boolean enable) {
+ synchronized (mLock) {
+ if (mEnabled == enable) {
+ return;
+ }
+ mEnabled = enable;
+
+ mHandler.postStateChanged();
+ }
+ }
+
+ /**
+ * Dispatch power save events to the listeners.
+ *
+ * This is always called on the handler thread.
+ */
+ void handleBatterySaverStateChanged() {
+ final LowPowerModeListener[] listeners;
+
+ final boolean wasEnabled;
+ final boolean enabled;
+ final boolean isScreenOn = getPowerManager().isInteractive();
+ final ArrayMap<String, String> fileValues;
+
+ synchronized (mLock) {
+ Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn);
+
+ listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
+ wasEnabled = mWasEnabled;
+ enabled = mEnabled;
+
+ if (enabled) {
+ fileValues = mBatterySaverPolicy.getFileValues(isScreenOn);
+ } 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();
+ }
+
+ if (fileValues == null || fileValues.size() == 0) {
+ mFileUpdater.restoreDefault();
+ } else {
+ mFileUpdater.writeFiles(fileValues);
+ }
+
+ if (enabled != wasEnabled) {
+ if (DEBUG) {
+ Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
+ }
+
+ // Send the broadcasts and notify the listeners. We only do this when the battery saver
+ // mode changes, but not when only the screen state changes.
+ Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
+ .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcast(intent);
+
+ intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ // Send internal version that requires signature permission.
+ intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ Manifest.permission.DEVICE_POWER);
+
+
+ for (LowPowerModeListener listener : listeners) {
+ final PowerSaveState result =
+ mBatterySaverPolicy.getBatterySaverPolicy(
+ listener.getServiceType(), enabled);
+ listener.onLowPowerModeChanged(result);
+ }
+ }
+
+ synchronized (mLock) {
+ mWasEnabled = enabled;
+ }
+ }
+}
diff --git a/com/android/server/power/batterysaver/FileUpdater.java b/com/android/server/power/batterysaver/FileUpdater.java
new file mode 100644
index 00000000..cfe8fc49
--- /dev/null
+++ b/com/android/server/power/batterysaver/FileUpdater.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.Context;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+/**
+ * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
+ * with retry and to restore the original values.
+ *
+ * TODO Implement it
+ */
+public class FileUpdater {
+ private static final String TAG = BatterySaverController.TAG;
+
+ private static final boolean DEBUG = BatterySaverController.DEBUG;
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+
+ public FileUpdater(Context context) {
+ mContext = context;
+ }
+
+ 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) + "'");
+ }
+ }
+ }
+
+ public void restoreDefault() {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting file default values");
+ }
+ }
+}
diff --git a/com/android/server/print/UserState.java b/com/android/server/print/UserState.java
index 58833bee..e8ae020c 100644
--- a/com/android/server/print/UserState.java
+++ b/com/android/server/print/UserState.java
@@ -597,6 +597,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
PrintJobStateChangeListenerRecord record =
mPrintJobStateChangeListenerRecords.get(i);
if (record.listener.asBinder().equals(listener.asBinder())) {
+ record.destroy();
mPrintJobStateChangeListenerRecords.remove(i);
break;
}
@@ -639,6 +640,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
ListenerRecord<IPrintServicesChangeListener> record =
mPrintServicesChangeListenerRecords.get(i);
if (record.listener.asBinder().equals(listener.asBinder())) {
+ record.destroy();
mPrintServicesChangeListenerRecords.remove(i);
break;
}
@@ -686,6 +688,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
ListenerRecord<IRecommendationsChangeListener> record =
mPrintServiceRecommendationsChangeListenerRecords.get(i);
if (record.listener.asBinder().equals(listener.asBinder())) {
+ record.destroy();
mPrintServiceRecommendationsChangeListenerRecords.remove(i);
break;
}
@@ -1285,6 +1288,10 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
listener.asBinder().linkToDeath(this, 0);
}
+ public void destroy() {
+ listener.asBinder().unlinkToDeath(this, 0);
+ }
+
@Override
public void binderDied() {
listener.asBinder().unlinkToDeath(this, 0);
@@ -1302,6 +1309,10 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
listener.asBinder().linkToDeath(this, 0);
}
+ public void destroy() {
+ listener.asBinder().unlinkToDeath(this, 0);
+ }
+
@Override
public void binderDied() {
listener.asBinder().unlinkToDeath(this, 0);
diff --git a/com/android/server/soundtrigger/SoundTriggerHelper.java b/com/android/server/soundtrigger/SoundTriggerHelper.java
index f53eb15d..25c54b3e 100644
--- a/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -36,12 +36,12 @@ import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
import android.hardware.soundtrigger.SoundTriggerModule;
import android.os.DeadObjectException;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.RemoteException;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Slog;
import com.android.internal.logging.MetricsLogger;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
index 22d2bcfc..dafab770 100644
--- a/com/android/server/stats/StatsCompanionService.java
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -24,7 +24,8 @@ import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.content.IntentFilter;
+import android.net.NetworkStats;
+import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -37,15 +38,19 @@ import android.os.StatsLogEventWrapper;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.os.KernelWakelockReader;
import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.PowerProfile;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
@@ -57,6 +62,8 @@ import java.util.Map;
public class StatsCompanionService extends IStatsCompanionService.Stub {
static final String TAG = "StatsCompanionService";
static final boolean DEBUG = true;
+ public static final String ACTION_TRIGGER_COLLECTION =
+ "com.android.server.stats.action.TRIGGER_COLLECTION";
private final Context mContext;
private final AlarmManager mAlarmManager;
@@ -65,9 +72,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
private static final Object sStatsdLock = new Object();
private final PendingIntent mAnomalyAlarmIntent;
- private final PendingIntent mPollingAlarmIntent;
+ private final PendingIntent mPullingAlarmIntent;
private final BroadcastReceiver mAppUpdateReceiver;
private final BroadcastReceiver mUserUpdateReceiver;
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+ private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
public StatsCompanionService(Context context) {
super();
@@ -76,8 +86,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
new Intent(mContext, AnomalyAlarmReceiver.class), 0);
- mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(mContext, PollingAlarmReceiver.class), 0);
+ mPullingAlarmIntent = PendingIntent.getBroadcast(
+ mContext, 0, new Intent(mContext, PullingAlarmReceiver.class), 0);
mAppUpdateReceiver = new AppUpdateReceiver();
mUserUpdateReceiver = new BroadcastReceiver() {
@Override
@@ -100,6 +110,22 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
};
Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
+ PowerProfile powerProfile = new PowerProfile(context);
+ final int numClusters = powerProfile.getNumCpuClusters();
+ mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+ int firstCpuOfCluster = 0;
+ for (int i = 0; i < numClusters; i++) {
+ final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+ mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+ numSpeedSteps);
+ firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+ }
+ }
+
+ @Override
+ public void sendBroadcast(String pkg, String cls) {
+ mContext.sendBroadcastAsUser(new Intent(ACTION_TRIGGER_COLLECTION).setClassName(pkg, cls),
+ UserHandle.SYSTEM);
}
private final static int[] toIntArray(List<Integer> list) {
@@ -144,18 +170,19 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
public final static class AppUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
/**
* App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
* waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+ * If we can't find the value for EXTRA_REPLACING, we default to false.
*/
- if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
+ && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
return; // Keep only replacing or normal add and remove.
}
+ Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
synchronized (sStatsdLock) {
if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+ Slog.w(TAG, "Could not access statsd to inform it of an app update");
return;
}
try {
@@ -205,24 +232,25 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
}
- public final static class PollingAlarmReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Slog.d(TAG, "Time to poll something.");
- synchronized (sStatsdLock) {
- if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
- return;
- }
- try {
- // Two-way call to statsd to retain AlarmManager wakelock
- sStatsd.informPollAlarmFired();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
- }
- }
- // AlarmManager releases its own wakelock here.
+ public final static class PullingAlarmReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG)
+ Slog.d(TAG, "Time to poll something.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of 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);
+ }
}
+ // AlarmManager releases its own wakelock here.
+ }
}
@Override // Binder call
@@ -253,70 +281,212 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
@Override // Binder call
- public void setPollingAlarms(long timestampMs, long intervalMs) {
- enforceCallingPermission();
- if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
- + " every " + intervalMs + "ms");
- final long callingToken = Binder.clearCallingIdentity();
- try {
- // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
- // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
- // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
- mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
- mPollingAlarmIntent);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
+ public void setPullingAlarms(long timestampMs, long intervalMs) {
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Setting pulling alarm for " + timestampMs + " every " + intervalMs + "ms");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+ // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+ // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+ mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, mPullingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
}
@Override // Binder call
- public void cancelPollingAlarms() {
- enforceCallingPermission();
- if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
- final long callingToken = Binder.clearCallingIdentity();
- try {
- mAlarmManager.cancel(mPollingAlarmIntent);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
+ public void cancelPullingAlarms() {
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Cancelling pulling alarm");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.cancel(mPullingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
}
- // These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
- // TODO: pull the constant from stats_events.proto instead
- private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
+ private StatsLogEventWrapper[] addNetworkStats(int tag, NetworkStats stats, boolean withFGBG) {
+ List<StatsLogEventWrapper> ret = new ArrayList<>();
+ int size = stats.size();
+ NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+ for (int j = 0; j < size; j++) {
+ stats.getValues(j, entry);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tag, withFGBG ? 6 : 5);
+ e.writeInt(entry.uid);
+ if (withFGBG) {
+ e.writeInt(entry.set);
+ }
+ e.writeLong(entry.rxBytes);
+ e.writeLong(entry.rxPackets);
+ e.writeLong(entry.txBytes);
+ e.writeLong(entry.txPackets);
+ ret.add(e);
+ }
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+ }
- private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
- private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+ /**
+ * Allows rollups per UID but keeping the set (foreground/background) slicing.
+ * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+ */
+ private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+ final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.iface = NetworkStats.IFACE_ALL;
+ entry.tag = NetworkStats.TAG_NONE;
+ entry.metered = NetworkStats.METERED_ALL;
+ entry.roaming = NetworkStats.ROAMING_ALL;
+
+ int size = stats.size();
+ NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+ for (int i = 0; i < size; i++) {
+ stats.getValues(i, recycle);
+
+ // Skip specific tags, since already counted in TAG_NONE
+ if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+ entry.set = recycle.set; // Allows slicing by background/foreground
+ entry.uid = recycle.uid;
+ entry.rxBytes = recycle.rxBytes;
+ entry.rxPackets = recycle.rxPackets;
+ entry.txBytes = recycle.txBytes;
+ entry.txPackets = recycle.txPackets;
+ // Operations purposefully omitted since we don't use them for statsd.
+ ret.combineValues(entry);
+ }
+ return ret;
+ }
@Override // Binder call
- public StatsLogEventWrapper[] pullData(int pullCode) {
+ public StatsLogEventWrapper[] pullData(int tagId) {
enforceCallingPermission();
- if (DEBUG) {
- Slog.d(TAG, "Pulling " + pullCode);
- }
+ if (DEBUG)
+ Slog.d(TAG, "Pulling " + tagId);
- List<StatsLogEventWrapper> ret = new ArrayList<>();
- switch (pullCode) {
- case PULL_CODE_KERNEL_WAKELOCKS: {
+ switch (tagId) {
+ case StatsLog.WIFI_BYTES_TRANSFERRED: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ // TODO: Consider caching the following call to get BatteryStatsInternal.
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null).groupedByUid();
+ return addNetworkStats(tagId, stats, false);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.MOBILE_BYTES_TRANSFERRED: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null).groupedByUid();
+ return addNetworkStats(tagId, stats, false);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.WIFI_BYTES_TRANSFERRED_BY_FG_BG: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null));
+ return addNetworkStats(tagId, stats, true);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.MOBILE_BYTES_TRANSFERRED_BY_FG_BG: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null));
+ return addNetworkStats(tagId, stats, true);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.KERNEL_WAKELOCK_PULLED: {
final KernelWakelockStats wakelockStats =
mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+ List<StatsLogEventWrapper> ret = new ArrayList();
for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
String name = ent.getKey();
KernelWakelockStats.Entry kws = ent.getValue();
- StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 4);
+ e.writeString(name);
e.writeInt(kws.mCount);
e.writeInt(kws.mVersion);
e.writeLong(kws.mTotalTime);
- e.writeString(name);
ret.add(e);
}
- break;
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+ }
+ case StatsLog.CPU_TIME_PER_FREQ_PULLED: {
+ List<StatsLogEventWrapper> ret = new ArrayList();
+ for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+ long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readDelta();
+ if (clusterTimeMs != null) {
+ for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
+ e.writeInt(tagId);
+ e.writeInt(speed);
+ e.writeLong(clusterTimeMs[speed]);
+ ret.add(e);
+ }
+ }
+ }
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
}
default:
- Slog.w(TAG, "No such pollable data as " + pullCode);
+ Slog.w(TAG, "No such tagId data as " + tagId);
return null;
}
- return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+ return null;
}
@Override // Binder call
@@ -440,7 +610,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
mContext.unregisterReceiver(mAppUpdateReceiver);
mContext.unregisterReceiver(mUserUpdateReceiver);
cancelAnomalyAlarm();
- cancelPollingAlarms();
+ cancelPullingAlarms();
}
}
diff --git a/com/android/server/updates/TzDataInstallReceiver.java b/com/android/server/updates/TzDataInstallReceiver.java
deleted file mode 100644
index cabce183..00000000
--- a/com/android/server/updates/TzDataInstallReceiver.java
+++ /dev/null
@@ -1,58 +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.updates;
-
-import com.android.timezone.distro.TimeZoneDistro;
-import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
-
-import android.util.Slog;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * An install receiver responsible for installing timezone data updates.
- */
-public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver {
-
- private static final String TAG = "TZDataInstallReceiver";
-
- private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
- private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
- private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
- private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
- private static final String UPDATE_VERSION_FILE_NAME = "version";
- private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_distro.zip";
-
- private final TimeZoneDistroInstaller installer;
-
- public TzDataInstallReceiver() {
- super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
- UPDATE_VERSION_FILE_NAME);
- installer = new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR);
- }
-
- @Override
- protected void install(byte[] content, int version) throws IOException {
- TimeZoneDistro distro = new TimeZoneDistro(content);
- boolean valid = installer.install(distro);
- Slog.i(TAG, "Timezone data install valid for this device: " + valid);
- // Even if !valid, we call super.install(). Only in the event of an exception should we
- // not. If we didn't do this we could attempt to install repeatedly.
- super.install(content, version);
- }
-}
diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java
index e5d3915d..c5ca3304 100644
--- a/com/android/server/usage/AppIdleHistory.java
+++ b/com/android/server/usage/AppIdleHistory.java
@@ -103,7 +103,7 @@ public class AppIdleHistory {
long lastUsedScreenTime;
@StandbyBuckets int currentBucket;
String bucketingReason;
- int lastInformedState;
+ int lastInformedBucket;
}
AppIdleHistory(File storageDir, long elapsedRealtime) {
@@ -333,13 +333,12 @@ public class AppIdleHistory {
}
boolean shouldInformListeners(String packageName, int userId,
- long elapsedRealtime, boolean isIdle) {
+ long elapsedRealtime, int bucket) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, true);
- int targetState = isIdle? STATE_IDLE : STATE_ACTIVE;
- if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) {
- appUsageHistory.lastInformedState = targetState;
+ if (appUsageHistory.lastInformedBucket != bucket) {
+ appUsageHistory.lastInformedBucket = bucket;
return true;
}
return false;
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
index 17fde579..5623a68f 100644
--- a/com/android/server/usage/AppStandbyController.java
+++ b/com/android/server/usage/AppStandbyController.java
@@ -160,7 +160,6 @@ public class AppStandbyController {
private final Context mContext;
// TODO: Provide a mechanism to set an external bucketing service
- private boolean mUseInternalBucketingHeuristics = true;
private AppWidgetManager mAppWidgetManager;
private PowerManager mPowerManager;
@@ -367,29 +366,33 @@ public class AppStandbyController {
Slog.d(TAG, " Checking idle state for " + packageName);
}
if (isSpecial) {
- maybeInformListeners(packageName, userId, elapsedRealtime, false);
- } else if (mUseInternalBucketingHeuristics) {
+ maybeInformListeners(packageName, userId, elapsedRealtime,
+ AppStandby.STANDBY_BUCKET_ACTIVE);
+ } else {
synchronized (mAppIdleLock) {
- int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
- elapsedRealtime);
String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName,
userId, elapsedRealtime);
- if (bucketingReason != null
- && (bucketingReason.equals(AppStandby.REASON_FORCED)
- || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) {
+ // If the bucket was forced by the developer, leave it alone
+ if (AppStandby.REASON_FORCED.equals(bucketingReason)) {
continue;
}
- int newBucket = getBucketForLocked(packageName, userId,
- elapsedRealtime);
- if (DEBUG) {
- Slog.d(TAG, " Old bucket=" + oldBucket
- + ", newBucket=" + newBucket);
- }
- if (oldBucket != newBucket) {
- mAppIdleHistory.setAppStandbyBucket(packageName, userId,
- elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
- maybeInformListeners(packageName, userId, elapsedRealtime,
- newBucket >= AppStandby.STANDBY_BUCKET_RARE);
+ // If the bucket was moved up due to usage, let the timeouts apply.
+ if (AppStandby.REASON_USAGE.equals(bucketingReason)
+ || AppStandby.REASON_TIMEOUT.equals(bucketingReason)) {
+ int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
+ elapsedRealtime);
+ int newBucket = getBucketForLocked(packageName, userId,
+ elapsedRealtime);
+ if (DEBUG) {
+ Slog.d(TAG, " Old bucket=" + oldBucket
+ + ", newBucket=" + newBucket);
+ }
+ if (oldBucket < newBucket) {
+ mAppIdleHistory.setAppStandbyBucket(packageName, userId,
+ elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
+ maybeInformListeners(packageName, userId, elapsedRealtime,
+ newBucket);
+ }
}
}
}
@@ -403,12 +406,12 @@ public class AppStandbyController {
}
private void maybeInformListeners(String packageName, int userId,
- long elapsedRealtime, boolean isIdle) {
+ long elapsedRealtime, int bucket) {
synchronized (mAppIdleLock) {
if (mAppIdleHistory.shouldInformListeners(packageName, userId,
- elapsedRealtime, isIdle)) {
+ elapsedRealtime, bucket)) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
- userId, isIdle ? 1 : 0, packageName));
+ userId, bucket, packageName));
}
}
}
@@ -461,11 +464,13 @@ public class AppStandbyController {
if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
boolean paroled = false;
synchronized (mAppIdleLock) {
- final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
+ final long timeSinceLastParole =
+ mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
if (!deviceIdle
&& timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
if (DEBUG) {
- Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
+ Slog.i(TAG,
+ "Bringing idle apps out of inactive state due to deviceIdleMode=false");
}
paroled = true;
} else if (deviceIdle) {
@@ -491,7 +496,8 @@ public class AppStandbyController {
|| event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
if (previouslyIdle) {
- maybeInformListeners(event.mPackage, userId, elapsedRealtime, false);
+ maybeInformListeners(event.mPackage, userId, elapsedRealtime,
+ AppStandby.STANDBY_BUCKET_ACTIVE);
notifyBatteryStats(event.mPackage, userId, false);
}
}
@@ -729,7 +735,8 @@ public class AppStandbyController {
void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
String reason, long elapsedRealtime) {
- mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason);
+ mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
+ reason);
}
private boolean isActiveDeviceAdmin(String packageName, int userId) {
@@ -786,9 +793,10 @@ public class AppStandbyController {
return packageName != null && packageName.equals(activeScorer);
}
- void informListeners(String packageName, int userId, boolean isIdle) {
+ void informListeners(String packageName, int userId, int bucket) {
+ final boolean idle = bucket >= AppStandby.STANDBY_BUCKET_RARE;
for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
- listener.onAppIdleStateChanged(packageName, userId, isIdle);
+ listener.onAppIdleStateChanged(packageName, userId, idle, bucket);
}
}
@@ -1037,7 +1045,7 @@ public class AppStandbyController {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INFORM_LISTENERS:
- informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+ informListeners((String) msg.obj, msg.arg1, msg.arg2);
break;
case MSG_FORCE_IDLE_STATE:
@@ -1187,7 +1195,8 @@ public class AppStandbyController {
mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
SCREEN_TIME_THRESHOLDS);
- String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null);
+ String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS,
+ null);
mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
ELAPSED_TIME_THRESHOLDS);
}
diff --git a/com/android/server/usb/UsbDeviceManager.java b/com/android/server/usb/UsbDeviceManager.java
index e65d3608..1b057f9b 100644
--- a/com/android/server/usb/UsbDeviceManager.java
+++ b/com/android/server/usb/UsbDeviceManager.java
@@ -893,7 +893,7 @@ public class UsbDeviceManager {
updateCurrentAccessory();
}
if (mBootCompleted) {
- if (!mConnected) {
+ if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)) {
// restore defaults when USB is disconnected
setEnabledFunctions(null, !mAdbEnabled, false);
}
diff --git a/com/android/server/usb/UsbProfileGroupSettingsManager.java b/com/android/server/usb/UsbProfileGroupSettingsManager.java
index ebb5a62c..917e651b 100644
--- a/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -32,9 +32,10 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.XmlResourceParser;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.AsyncTask;
import android.os.Environment;
@@ -58,7 +59,6 @@ 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;
@@ -71,7 +71,6 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
class UsbProfileGroupSettingsManager {
private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
@@ -157,404 +156,6 @@ class UsbProfileGroupSettingsManager {
}
}
- // This class is used to describe a USB device.
- // When used in HashMaps all values must be specified,
- // but wildcards can be used for any of the fields in
- // the package meta-data.
- private static class DeviceFilter {
- // USB Vendor ID (or -1 for unspecified)
- public final int mVendorId;
- // USB Product ID (or -1 for unspecified)
- public final int mProductId;
- // USB device or interface class (or -1 for unspecified)
- public final int mClass;
- // USB device subclass (or -1 for unspecified)
- public final int mSubclass;
- // USB device protocol (or -1 for unspecified)
- public final int mProtocol;
- // USB device manufacturer name string (or null for unspecified)
- public final String mManufacturerName;
- // USB device product name string (or null for unspecified)
- public final String mProductName;
- // USB device serial number string (or null for unspecified)
- public final String mSerialNumber;
-
- public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
- String manufacturer, String product, String serialnum) {
- mVendorId = vid;
- mProductId = pid;
- mClass = clasz;
- mSubclass = subclass;
- mProtocol = protocol;
- mManufacturerName = manufacturer;
- mProductName = product;
- mSerialNumber = serialnum;
- }
-
- public DeviceFilter(UsbDevice device) {
- mVendorId = device.getVendorId();
- mProductId = device.getProductId();
- mClass = device.getDeviceClass();
- mSubclass = device.getDeviceSubclass();
- mProtocol = device.getDeviceProtocol();
- mManufacturerName = device.getManufacturerName();
- mProductName = device.getProductName();
- mSerialNumber = device.getSerialNumber();
- }
-
- public static DeviceFilter read(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- int vendorId = -1;
- int productId = -1;
- int deviceClass = -1;
- int deviceSubclass = -1;
- int deviceProtocol = -1;
- String manufacturerName = null;
- String productName = null;
- String serialNumber = null;
-
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- String name = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
- // Attribute values are ints or strings
- if ("manufacturer-name".equals(name)) {
- manufacturerName = value;
- } else if ("product-name".equals(name)) {
- productName = value;
- } else if ("serial-number".equals(name)) {
- serialNumber = value;
- } else {
- int intValue;
- int radix = 10;
- if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
- (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
- // allow hex values starting with 0x or 0X
- radix = 16;
- value = value.substring(2);
- }
- try {
- intValue = Integer.parseInt(value, radix);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "invalid number for field " + name, e);
- continue;
- }
- if ("vendor-id".equals(name)) {
- vendorId = intValue;
- } else if ("product-id".equals(name)) {
- productId = intValue;
- } else if ("class".equals(name)) {
- deviceClass = intValue;
- } else if ("subclass".equals(name)) {
- deviceSubclass = intValue;
- } else if ("protocol".equals(name)) {
- deviceProtocol = intValue;
- }
- }
- }
- return new DeviceFilter(vendorId, productId,
- deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, serialNumber);
- }
-
- public void write(XmlSerializer serializer) throws IOException {
- serializer.startTag(null, "usb-device");
- if (mVendorId != -1) {
- serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
- }
- if (mProductId != -1) {
- serializer.attribute(null, "product-id", Integer.toString(mProductId));
- }
- if (mClass != -1) {
- serializer.attribute(null, "class", Integer.toString(mClass));
- }
- if (mSubclass != -1) {
- serializer.attribute(null, "subclass", Integer.toString(mSubclass));
- }
- if (mProtocol != -1) {
- serializer.attribute(null, "protocol", Integer.toString(mProtocol));
- }
- if (mManufacturerName != null) {
- serializer.attribute(null, "manufacturer-name", mManufacturerName);
- }
- if (mProductName != null) {
- serializer.attribute(null, "product-name", mProductName);
- }
- if (mSerialNumber != null) {
- serializer.attribute(null, "serial-number", mSerialNumber);
- }
- serializer.endTag(null, "usb-device");
- }
-
- private boolean matches(int clasz, int subclass, int protocol) {
- return ((mClass == -1 || clasz == mClass) &&
- (mSubclass == -1 || subclass == mSubclass) &&
- (mProtocol == -1 || protocol == mProtocol));
- }
-
- public boolean matches(UsbDevice device) {
- if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
- if (mProductId != -1 && device.getProductId() != mProductId) return false;
- if (mManufacturerName != null && device.getManufacturerName() == null) return false;
- if (mProductName != null && device.getProductName() == null) return false;
- if (mSerialNumber != null && device.getSerialNumber() == null) return false;
- if (mManufacturerName != null && device.getManufacturerName() != null &&
- !mManufacturerName.equals(device.getManufacturerName())) return false;
- if (mProductName != null && device.getProductName() != null &&
- !mProductName.equals(device.getProductName())) return false;
- if (mSerialNumber != null && device.getSerialNumber() != null &&
- !mSerialNumber.equals(device.getSerialNumber())) return false;
-
- // check device class/subclass/protocol
- if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
- device.getDeviceProtocol())) return true;
-
- // if device doesn't match, check the interfaces
- int count = device.getInterfaceCount();
- for (int i = 0; i < count; i++) {
- UsbInterface intf = device.getInterface(i);
- if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
- intf.getInterfaceProtocol())) return true;
- }
-
- return false;
- }
-
- /**
- * If the device described by {@code device} covered by this filter?
- *
- * @param device The device
- *
- * @return {@code true} iff this filter covers the {@code device}
- */
- public boolean contains(DeviceFilter device) {
- // -1 and null means "match anything"
-
- if (mVendorId != -1 && device.mVendorId != mVendorId) return false;
- if (mProductId != -1 && device.mProductId != mProductId) return false;
- if (mManufacturerName != null && !Objects.equals(mManufacturerName,
- device.mManufacturerName)) {
- return false;
- }
- if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) {
- return false;
- }
- if (mSerialNumber != null
- && !Objects.equals(mSerialNumber, device.mSerialNumber)) {
- return false;
- }
-
- // check device class/subclass/protocol
- return matches(device.mClass, device.mSubclass, device.mProtocol);
- }
-
- @Override
- public boolean equals(Object obj) {
- // can't compare if we have wildcard strings
- if (mVendorId == -1 || mProductId == -1 ||
- mClass == -1 || mSubclass == -1 || mProtocol == -1) {
- return false;
- }
- if (obj instanceof DeviceFilter) {
- DeviceFilter filter = (DeviceFilter)obj;
-
- if (filter.mVendorId != mVendorId ||
- filter.mProductId != mProductId ||
- filter.mClass != mClass ||
- filter.mSubclass != mSubclass ||
- filter.mProtocol != mProtocol) {
- return(false);
- }
- if ((filter.mManufacturerName != null &&
- mManufacturerName == null) ||
- (filter.mManufacturerName == null &&
- mManufacturerName != null) ||
- (filter.mProductName != null &&
- mProductName == null) ||
- (filter.mProductName == null &&
- mProductName != null) ||
- (filter.mSerialNumber != null &&
- mSerialNumber == null) ||
- (filter.mSerialNumber == null &&
- mSerialNumber != null)) {
- return(false);
- }
- if ((filter.mManufacturerName != null &&
- mManufacturerName != null &&
- !mManufacturerName.equals(filter.mManufacturerName)) ||
- (filter.mProductName != null &&
- mProductName != null &&
- !mProductName.equals(filter.mProductName)) ||
- (filter.mSerialNumber != null &&
- mSerialNumber != null &&
- !mSerialNumber.equals(filter.mSerialNumber))) {
- return false;
- }
- return true;
- }
- if (obj instanceof UsbDevice) {
- UsbDevice device = (UsbDevice)obj;
- if (device.getVendorId() != mVendorId ||
- device.getProductId() != mProductId ||
- device.getDeviceClass() != mClass ||
- device.getDeviceSubclass() != mSubclass ||
- device.getDeviceProtocol() != mProtocol) {
- return(false);
- }
- if ((mManufacturerName != null && device.getManufacturerName() == null) ||
- (mManufacturerName == null && device.getManufacturerName() != null) ||
- (mProductName != null && device.getProductName() == null) ||
- (mProductName == null && device.getProductName() != null) ||
- (mSerialNumber != null && device.getSerialNumber() == null) ||
- (mSerialNumber == null && device.getSerialNumber() != null)) {
- return(false);
- }
- if ((device.getManufacturerName() != null &&
- !mManufacturerName.equals(device.getManufacturerName())) ||
- (device.getProductName() != null &&
- !mProductName.equals(device.getProductName())) ||
- (device.getSerialNumber() != null &&
- !mSerialNumber.equals(device.getSerialNumber()))) {
- return false;
- }
- return true;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return (((mVendorId << 16) | mProductId) ^
- ((mClass << 16) | (mSubclass << 8) | mProtocol));
- }
-
- @Override
- public String toString() {
- return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
- ",mClass=" + mClass + ",mSubclass=" + mSubclass +
- ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
- ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
- "]";
- }
- }
-
- // This class is used to describe a USB accessory.
- // When used in HashMaps all values must be specified,
- // but wildcards can be used for any of the fields in
- // the package meta-data.
- private static class AccessoryFilter {
- // USB accessory manufacturer (or null for unspecified)
- public final String mManufacturer;
- // USB accessory model (or null for unspecified)
- public final String mModel;
- // USB accessory version (or null for unspecified)
- public final String mVersion;
-
- public AccessoryFilter(String manufacturer, String model, String version) {
- mManufacturer = manufacturer;
- mModel = model;
- mVersion = version;
- }
-
- public AccessoryFilter(UsbAccessory accessory) {
- mManufacturer = accessory.getManufacturer();
- mModel = accessory.getModel();
- mVersion = accessory.getVersion();
- }
-
- public static AccessoryFilter read(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- String manufacturer = null;
- String model = null;
- String version = null;
-
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- String name = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
-
- if ("manufacturer".equals(name)) {
- manufacturer = value;
- } else if ("model".equals(name)) {
- model = value;
- } else if ("version".equals(name)) {
- version = value;
- }
- }
- return new AccessoryFilter(manufacturer, model, version);
- }
-
- public void write(XmlSerializer serializer)throws IOException {
- serializer.startTag(null, "usb-accessory");
- if (mManufacturer != null) {
- serializer.attribute(null, "manufacturer", mManufacturer);
- }
- if (mModel != null) {
- serializer.attribute(null, "model", mModel);
- }
- if (mVersion != null) {
- serializer.attribute(null, "version", mVersion);
- }
- serializer.endTag(null, "usb-accessory");
- }
-
- public boolean matches(UsbAccessory acc) {
- if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
- if (mModel != null && !acc.getModel().equals(mModel)) return false;
- return !(mVersion != null && !acc.getVersion().equals(mVersion));
- }
-
- /**
- * Is the accessories described {@code accessory} covered by this filter?
- *
- * @param accessory A filter describing the accessory
- *
- * @return {@code true} iff this the filter covers the accessory
- */
- public boolean contains(AccessoryFilter accessory) {
- if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) {
- return false;
- }
- if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false;
- return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion));
- }
-
- @Override
- public boolean equals(Object obj) {
- // can't compare if we have wildcard strings
- if (mManufacturer == null || mModel == null || mVersion == null) {
- return false;
- }
- if (obj instanceof AccessoryFilter) {
- AccessoryFilter filter = (AccessoryFilter)obj;
- return (mManufacturer.equals(filter.mManufacturer) &&
- mModel.equals(filter.mModel) &&
- mVersion.equals(filter.mVersion));
- }
- if (obj instanceof UsbAccessory) {
- UsbAccessory accessory = (UsbAccessory)obj;
- return (mManufacturer.equals(accessory.getManufacturer()) &&
- mModel.equals(accessory.getModel()) &&
- mVersion.equals(accessory.getVersion()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
- (mModel == null ? 0 : mModel.hashCode()) ^
- (mVersion == null ? 0 : mVersion.hashCode()));
- }
-
- @Override
- public String toString() {
- return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
- "\", mModel=\"" + mModel +
- "\", mVersion=\"" + mVersion + "\"]";
- }
- }
-
private class MyPackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1569ac32..44e5314f 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -30,6 +30,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.ShortcutServiceInternal;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
@@ -44,6 +45,7 @@ import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -53,6 +55,7 @@ import android.service.voice.VoiceInteractionServiceInfo;
import android.service.voice.VoiceInteractionSession;
import android.speech.RecognitionService;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -63,6 +66,7 @@ import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
@@ -84,7 +88,9 @@ public class VoiceInteractionManagerService extends SystemService {
final ContentResolver mResolver;
final DatabaseHelper mDbHelper;
final ActivityManagerInternal mAmInternal;
- final TreeSet<Integer> mLoadedKeyphraseIds;
+ final UserManager mUserManager;
+ final ArraySet<Integer> mLoadedKeyphraseIds = new ArraySet<>();
+ ShortcutServiceInternal mShortcutServiceInternal;
SoundTriggerInternal mSoundTriggerInternal;
private final RemoteCallbackList<IVoiceInteractionSessionListener>
@@ -96,8 +102,10 @@ public class VoiceInteractionManagerService extends SystemService {
mResolver = context.getContentResolver();
mDbHelper = new DatabaseHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
- mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
- mLoadedKeyphraseIds = new TreeSet<Integer>();
+ mAmInternal = Preconditions.checkNotNull(
+ LocalServices.getService(ActivityManagerInternal.class));
+ mUserManager = Preconditions.checkNotNull(
+ context.getSystemService(UserManager.class));
PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
@@ -124,6 +132,8 @@ public class VoiceInteractionManagerService extends SystemService {
@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
+ mShortcutServiceInternal = Preconditions.checkNotNull(
+ LocalServices.getService(ShortcutServiceInternal.class));
mSoundTriggerInternal = LocalServices.getService(SoundTriggerInternal.class);
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mServiceStub.systemRunning(isSafeMode());
@@ -180,6 +190,7 @@ public class VoiceInteractionManagerService extends SystemService {
private boolean mSafeMode;
private int mCurUser;
+ private boolean mCurUserUnlocked;
private final boolean mEnableService;
VoiceInteractionManagerServiceStub() {
@@ -381,6 +392,7 @@ public class VoiceInteractionManagerService extends SystemService {
public void switchUser(int userHandle) {
synchronized (this) {
mCurUser = userHandle;
+ mCurUserUnlocked = false;
switchImplementationIfNeededLocked(false);
}
}
@@ -409,13 +421,24 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
+ final boolean hasComponent = serviceComponent != null && serviceInfo != null;
+
+ if (mUserManager.isUserUnlockingOrUnlocked(mCurUser)) {
+ if (hasComponent) {
+ mShortcutServiceInternal.setShortcutHostPackage(TAG,
+ serviceComponent.getPackageName(), mCurUser);
+ } else {
+ mShortcutServiceInternal.setShortcutHostPackage(TAG, null, mCurUser);
+ }
+ }
+
if (force || mImpl == null || mImpl.mUser != mCurUser
|| !mImpl.mComponent.equals(serviceComponent)) {
unloadAllKeyphraseModels();
if (mImpl != null) {
mImpl.shutdownLocked();
}
- if (serviceComponent != null && serviceInfo != null) {
+ if (hasComponent) {
mImpl = new VoiceInteractionManagerServiceImpl(mContext,
UiThread.getHandler(), this, mCurUser, serviceComponent);
mImpl.startLocked();
@@ -953,12 +976,14 @@ public class VoiceInteractionManagerService extends SystemService {
}
private synchronized void unloadAllKeyphraseModels() {
- for (int keyphraseId : mLoadedKeyphraseIds) {
+ for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
final long caller = Binder.clearCallingIdentity();
try {
- int status = mSoundTriggerInternal.unloadKeyphraseModel(keyphraseId);
+ int status = mSoundTriggerInternal.unloadKeyphraseModel(
+ mLoadedKeyphraseIds.valueAt(i));
if (status != SoundTriggerInternal.STATUS_OK) {
- Slog.w(TAG, "Failed to unload keyphrase " + keyphraseId + ":" + status);
+ Slog.w(TAG, "Failed to unload keyphrase " + mLoadedKeyphraseIds.valueAt(i)
+ + ":" + status);
}
} finally {
Binder.restoreCallingIdentity(caller);
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b040a632..7541b92d 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -55,6 +55,7 @@ import com.android.server.LocalServices;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
@@ -162,13 +163,16 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mInfo.getServiceInfo().applicationInfo.uid, mHandler);
}
List<IBinder> activityTokens = null;
- if (activityToken == null) {
+ if (activityToken != null) {
+ activityTokens = new ArrayList<>();
+ activityTokens.add(activityToken);
+ } else {
// Let's get top activities from all visible stacks
activityTokens = LocalServices.getService(ActivityManagerInternal.class)
.getTopVisibleActivities();
}
return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
- activityToken, activityTokens);
+ activityTokens);
}
public boolean hideSessionLocked() {
diff --git a/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index d394d631..e0d9c739 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -16,6 +16,16 @@
package com.android.server.voiceinteraction;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -43,31 +53,24 @@ import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionSession;
import android.util.Slog;
import android.view.IWindowManager;
-import android.view.WindowManager;
import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
+import com.android.server.am.AssistDataRequester;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-
-final class VoiceInteractionSessionConnection implements ServiceConnection {
+final class VoiceInteractionSessionConnection implements ServiceConnection,
+ AssistDataRequesterCallbacks {
final static String TAG = "VoiceInteractionServiceManager";
- private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
- private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
-
final IBinder mToken = new Binder();
final Object mLock;
final ComponentName mSessionComponentName;
@@ -90,27 +93,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
IVoiceInteractionSessionService mService;
IVoiceInteractionSession mSession;
IVoiceInteractor mInteractor;
- boolean mHaveAssistData;
- int mPendingAssistDataCount;
- ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
- boolean mHaveScreenshot;
- Bitmap mScreenshot;
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
-
- static class AssistDataForActivity {
- int activityIndex;
- int activityCount;
- Bundle data;
-
- public AssistDataForActivity(Bundle data) {
- this.data = data;
- Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
- if (receiverExtras != null) {
- activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
- activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
- }
- }
- }
+ AssistDataRequester mAssistDataRequester;
IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -146,32 +130,6 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
}
};
- final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
- @Override
- public void send(int resultCode, Bundle resultData) throws RemoteException {
- synchronized (mLock) {
- if (mShown) {
- mHaveAssistData = true;
- mAssistData.add(new AssistDataForActivity(resultData));
- deliverSessionDataLocked();
- }
- }
- }
- };
-
- final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
- @Override
- public void send(Bitmap screenshot) throws RemoteException {
- synchronized (mLock) {
- if (mShown) {
- mHaveScreenshot = true;
- mScreenshot = screenshot;
- deliverSessionDataLocked();
- }
- }
- }
- };
-
public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
Context context, Callback callback, int callingUid, Handler handler) {
mLock = lock;
@@ -185,6 +143,9 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mAppOps = context.getSystemService(AppOpsManager.class);
+ mAssistDataRequester = new AssistDataRequester(mContext, mAm, mIWindowManager,
+ (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
+ this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
IBinder permOwner = null;
try {
permOwner = mAm.newUriPermissionOwner("voicesession:"
@@ -224,8 +185,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
}
public boolean showLocked(Bundle args, int flags, int disabledContext,
- IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
- List<IBinder> topActivities) {
+ IVoiceInteractionSessionShowCallback showCallback, List<IBinder> topActivities) {
if (mBound) {
if (!mFullyBound) {
mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -233,75 +193,21 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
| Context.BIND_FOREGROUND_SERVICE,
new UserHandle(mUser));
}
+
mShown = true;
- boolean isAssistDataAllowed = true;
- try {
- isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
- } catch (RemoteException e) {
- }
- disabledContext |= getUserDisabledShowContextLocked();
- boolean structureEnabled = isAssistDataAllowed
- && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
- boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
- && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
mShowArgs = args;
mShowFlags = flags;
- mHaveAssistData = false;
- mPendingAssistDataCount = 0;
- boolean needDisclosure = false;
- if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
- mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
- && structureEnabled) {
- mAssistData.clear();
- final int count = activityToken != null ? 1 : topActivities.size();
- for (int i = 0; i < count; i++) {
- IBinder topActivity = count == 1 ? activityToken : topActivities.get(i);
- try {
- MetricsLogger.count(mContext, "assist_with_context", 1);
- Bundle receiverExtras = new Bundle();
- receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
- receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, count);
- if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
- mAssistReceiver, receiverExtras, topActivity,
- /* focused= */ i == 0, /* newSessionId= */ i == 0)) {
- needDisclosure = true;
- mPendingAssistDataCount++;
- } else if (i == 0) {
- // Wasn't allowed... given that, let's not do the screenshot either.
- mHaveAssistData = true;
- mAssistData.clear();
- screenshotEnabled = false;
- break;
- }
- } catch (RemoteException e) {
- }
- }
- } else {
- mHaveAssistData = true;
- mAssistData.clear();
- }
- } else {
- mAssistData.clear();
- }
- mHaveScreenshot = false;
- if ((flags&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
- mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
- && screenshotEnabled) {
- try {
- MetricsLogger.count(mContext, "assist_with_screen", 1);
- needDisclosure = true;
- mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
- } catch (RemoteException e) {
- }
- } else {
- mHaveScreenshot = true;
- mScreenshot = null;
- }
- } else {
- mScreenshot = null;
- }
+
+ disabledContext |= getUserDisabledShowContextLocked();
+ mAssistDataRequester.requestAssistData(topActivities,
+ (flags & VoiceInteractionSession.SHOW_WITH_ASSIST) != 0,
+ (flags & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0,
+ (disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
+ (disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
+ mCallingUid, mSessionComponentName.getPackageName());
+
+ boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
+ || mAssistDataRequester.getPendingScreenshotCount() > 0;
if (needDisclosure && AssistUtils.shouldDisclose(mContext, mSessionComponentName)) {
mHandler.post(mShowAssistDisclosureRunnable);
}
@@ -312,7 +218,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
mShowFlags = 0;
} catch (RemoteException e) {
}
- deliverSessionDataLocked();
+ mAssistDataRequester.processPendingAssistData();
} else if (showCallback != null) {
mPendingShowCallbacks.add(showCallback);
}
@@ -328,6 +234,67 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
return false;
}
+ @Override
+ public boolean canHandleReceivedAssistDataLocked() {
+ return mSession != null;
+ }
+
+ @Override
+ public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+ // Return early if we have no session
+ if (mSession == null) {
+ return;
+ }
+
+ if (data == null) {
+ try {
+ mSession.handleAssist(null, null, null, 0, 0);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ } else {
+ final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
+ final AssistStructure structure = data.getParcelable(ASSIST_KEY_STRUCTURE);
+ final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+ final int uid = data.getInt(Intent.EXTRA_ASSIST_UID, -1);
+ if (uid >= 0 && content != null) {
+ Intent intent = content.getIntent();
+ if (intent != null) {
+ ClipData clipData = intent.getClipData();
+ if (clipData != null && Intent.isAccessUriMode(intent.getFlags())) {
+ grantClipDataPermissions(clipData, intent.getFlags(), uid,
+ mCallingUid, mSessionComponentName.getPackageName());
+ }
+ }
+ ClipData clipData = content.getClipData();
+ if (clipData != null) {
+ grantClipDataPermissions(clipData, FLAG_GRANT_READ_URI_PERMISSION,
+ uid, mCallingUid, mSessionComponentName.getPackageName());
+ }
+ }
+ try {
+ mSession.handleAssist(assistData, structure, content, activityIndex,
+ activityCount);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+ }
+
+ @Override
+ public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+ // Return early if we have no session
+ if (mSession == null) {
+ return;
+ }
+
+ try {
+ mSession.handleScreenshot(screenshot);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
if (!"content".equals(uri.getScheme())) {
return;
@@ -341,7 +308,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
uri = ContentProvider.getUriWithoutUserId(uri);
mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
- uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
+ uri, FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
} catch (RemoteException e) {
} catch (SecurityException e) {
Slog.w(TAG, "Can't propagate permission", e);
@@ -370,89 +337,13 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
}
}
- void deliverSessionDataLocked() {
- if (mSession == null) {
- return;
- }
- if (mHaveAssistData) {
- AssistDataForActivity assistData;
- if (mAssistData.isEmpty()) {
- // We're not actually going to get any data, deliver some nothing
- try {
- mSession.handleAssist(null, null, null, 0, 0);
- } catch (RemoteException e) {
- }
- } else {
- while (!mAssistData.isEmpty()) {
- if (mPendingAssistDataCount <= 0) {
- Slog.e(TAG, "mPendingAssistDataCount is " + mPendingAssistDataCount);
- }
- mPendingAssistDataCount--;
- assistData = mAssistData.remove(0);
- if (assistData.data == null) {
- try {
- mSession.handleAssist(null, null, null, assistData.activityIndex,
- assistData.activityCount);
- } catch (RemoteException e) {
- }
- } else {
- deliverSessionDataLocked(assistData);
- }
- }
- }
- if (mPendingAssistDataCount <= 0) {
- mHaveAssistData = false;
- } // else, more to come
- }
- if (mHaveScreenshot) {
- try {
- mSession.handleScreenshot(mScreenshot);
- } catch (RemoteException e) {
- }
- mScreenshot = null;
- mHaveScreenshot = false;
- }
- }
-
- private void deliverSessionDataLocked(AssistDataForActivity assistDataForActivity) {
- Bundle assistData = assistDataForActivity.data.getBundle(
- VoiceInteractionSession.KEY_DATA);
- AssistStructure structure = assistDataForActivity.data.getParcelable(
- VoiceInteractionSession.KEY_STRUCTURE);
- AssistContent content = assistDataForActivity.data.getParcelable(
- VoiceInteractionSession.KEY_CONTENT);
- int uid = assistDataForActivity.data.getInt(Intent.EXTRA_ASSIST_UID, -1);
- if (uid >= 0 && content != null) {
- Intent intent = content.getIntent();
- if (intent != null) {
- ClipData data = intent.getClipData();
- if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
- grantClipDataPermissions(data, intent.getFlags(), uid,
- mCallingUid, mSessionComponentName.getPackageName());
- }
- }
- ClipData data = content.getClipData();
- if (data != null) {
- grantClipDataPermissions(data,
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
- uid, mCallingUid, mSessionComponentName.getPackageName());
- }
- }
- try {
- mSession.handleAssist(assistData, structure, content,
- assistDataForActivity.activityIndex, assistDataForActivity.activityCount);
- } catch (RemoteException e) {
- }
- }
-
public boolean hideLocked() {
if (mBound) {
if (mShown) {
mShown = false;
mShowArgs = null;
mShowFlags = 0;
- mHaveAssistData = false;
- mAssistData.clear();
+ mAssistDataRequester.cancel();
mPendingShowCallbacks.clear();
if (mSession != null) {
try {
@@ -462,8 +353,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
}
try {
mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION,
mUser);
} catch (RemoteException e) {
}
@@ -529,7 +419,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
mShowFlags = 0;
} catch (RemoteException e) {
}
- deliverSessionDataLocked();
+ mAssistDataRequester.processPendingAssistData();
}
return true;
}
@@ -587,10 +477,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
pw.print(prefix); pw.print("mSession="); pw.println(mSession);
pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
}
- pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
- if (mHaveAssistData) {
- pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
- }
+ mAssistDataRequester.dump(prefix, pw);
}
private Runnable mShowAssistDisclosureRunnable = new Runnable() {
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index e7e4efcc..5493207d 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -166,6 +166,8 @@ public class VrManagerService extends SystemService implements EnabledComponentC
private boolean mUserUnlocked;
private Vr2dDisplay mVr2dDisplay;
private boolean mBootsToVr;
+ private boolean mStandby;
+ private boolean mUseStandbyToExitVrMode;
// Handles events from the managed services (e.g. VrListenerService and any bound VR compositor
// service).
@@ -203,7 +205,10 @@ public class VrManagerService extends SystemService implements EnabledComponentC
*
*/
private void updateVrModeAllowedLocked() {
- boolean allowed = mSystemSleepFlags == FLAG_ALL && mUserUnlocked;
+ boolean ignoreSleepFlags = mBootsToVr && mUseStandbyToExitVrMode;
+ boolean disallowedByStandby = mStandby && mUseStandbyToExitVrMode;
+ boolean allowed = (mSystemSleepFlags == FLAG_ALL || ignoreSleepFlags) && mUserUnlocked
+ && !disallowedByStandby;
if (mVrModeAllowed != allowed) {
mVrModeAllowed = allowed;
if (DBG) Slog.d(TAG, "VR mode is " + ((allowed) ? "allowed" : "disallowed"));
@@ -273,6 +278,17 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
}
+ private void setStandbyEnabled(boolean standby) {
+ synchronized(mLock) {
+ if (!mBootsToVr) {
+ Slog.e(TAG, "Attempting to set standby mode on a non-standalone device");
+ return;
+ }
+ mStandby = standby;
+ updateVrModeAllowedLocked();
+ }
+ }
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -587,6 +603,12 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
@Override
+ public void setStandbyEnabled(boolean standby) {
+ enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER);
+ VrManagerService.this.setStandbyEnabled(standby);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -733,6 +755,8 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
mBootsToVr = SystemProperties.getBoolean("ro.boot.vr", false);
+ mUseStandbyToExitVrMode = mBootsToVr
+ && SystemProperties.getBoolean("persist.vr.use_standby_to_exit_vr_mode", true);
publishLocalService(VrManagerInternal.class, new LocalService());
publishBinderService(Context.VR_SERVICE, mVrManager.asBinder());
}
diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java
index 7a1e8d9e..0cc735af 100644
--- a/com/android/server/wifi/HalDeviceManager.java
+++ b/com/android/server/wifi/HalDeviceManager.java
@@ -35,9 +35,9 @@ import android.hidl.manager.V1_0.IServiceNotification;
import android.os.Handler;
import android.os.HwRemoteBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.util.LongSparseArray;
import android.util.MutableBoolean;
import android.util.MutableInt;
import android.util.SparseArray;
@@ -70,8 +70,12 @@ public class HalDeviceManager {
@VisibleForTesting
public static final String HAL_INSTANCE_NAME = "default";
+ private final Clock mClock;
+
// public API
- public HalDeviceManager() {
+ public HalDeviceManager(Clock clock) {
+ mClock = clock;
+
mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashSet<>());
mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashSet<>());
mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashSet<>());
@@ -98,12 +102,13 @@ public class HalDeviceManager {
* single copy kept.
*
* @param listener ManagerStatusListener listener object.
- * @param looper Looper on which to dispatch listener. Null implies current looper.
+ * @param handler Handler on which to dispatch listener. Null implies a new Handler based on
+ * the current looper.
*/
- public void registerStatusListener(ManagerStatusListener listener, Looper looper) {
+ public void registerStatusListener(ManagerStatusListener listener, Handler handler) {
synchronized (mLock) {
if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
- looper == null ? Looper.myLooper() : looper))) {
+ handler == null ? new Handler(Looper.myLooper()) : handler))) {
Log.w(TAG, "registerStatusListener: duplicate registration ignored");
}
}
@@ -192,37 +197,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 looper The looper on which to dispatch the listener. A null value indicates the
- * current thread.
+ * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
+ * based on the current looper.
* @return A newly created interface - or null if the interface could not be created.
*/
public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
- Looper looper) {
- return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, looper);
+ Handler handler) {
+ return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler);
}
/**
* Create AP interface if possible (see createStaIface doc).
*/
public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
- Looper looper) {
- return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, looper);
+ Handler handler) {
+ return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler);
}
/**
* Create P2P interface if possible (see createStaIface doc).
*/
public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
- Looper looper) {
- return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, looper);
+ Handler handler) {
+ return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler);
}
/**
* Create NAN interface if possible (see createStaIface doc).
*/
public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
- Looper looper) {
- return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, looper);
+ Handler handler) {
+ return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
}
/**
@@ -263,11 +268,11 @@ 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 looper - or on the current looper if a null is passed.
+ * Listener called-back on the specified handler - or on the current looper if a null is passed.
*/
public boolean registerDestroyedListener(IWifiIface iface,
InterfaceDestroyedListener destroyedListener,
- Looper looper) {
+ Handler handler) {
String name = getName(iface);
if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
@@ -279,8 +284,7 @@ public class HalDeviceManager {
}
return cacheEntry.destroyedListeners.add(
- new InterfaceDestroyedListenerProxy(destroyedListener,
- looper == null ? Looper.myLooper() : looper));
+ new InterfaceDestroyedListenerProxy(destroyedListener, handler));
}
}
@@ -299,17 +303,16 @@ 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 looper The looper on which to dispatch the listener. A null value indicates the
- * current thread.
+ * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
+ * on the current looper.
*/
public void registerInterfaceAvailableForRequestListener(int ifaceType,
- InterfaceAvailableForRequestListener listener, Looper looper) {
+ InterfaceAvailableForRequestListener listener, Handler handler) {
if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
synchronized (mLock) {
mInterfaceAvailableForRequestListeners.get(ifaceType).add(
- new InterfaceAvailableForRequestListenerProxy(listener,
- looper == null ? Looper.myLooper() : looper));
+ new InterfaceAvailableForRequestListenerProxy(listener, handler));
}
WifiChipInfo[] chipInfos = getAllChipInfo();
@@ -474,12 +477,14 @@ public class HalDeviceManager {
public String name;
public int type;
public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
+ public long creationTime;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{name=").append(name).append(", type=").append(type)
.append(", destroyedListeners.size()=").append(destroyedListeners.size())
+ .append(", creationTime=").append(creationTime)
.append("}");
return sb.toString();
}
@@ -1208,8 +1213,8 @@ public class HalDeviceManager {
private class ManagerStatusListenerProxy extends
ListenerProxy<ManagerStatusListener> {
ManagerStatusListenerProxy(ManagerStatusListener statusListener,
- Looper looper) {
- super(statusListener, looper, "ManagerStatusListenerProxy");
+ Handler handler) {
+ super(statusListener, handler, true, "ManagerStatusListenerProxy");
}
@Override
@@ -1270,7 +1275,7 @@ public class HalDeviceManager {
}
private IWifiIface createIface(int ifaceType, InterfaceDestroyedListener destroyedListener,
- Looper looper) {
+ Handler handler) {
if (DBG) Log.d(TAG, "createIface: ifaceType=" + ifaceType);
synchronized (mLock) {
@@ -1288,7 +1293,7 @@ public class HalDeviceManager {
}
IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, destroyedListener,
- looper);
+ handler);
if (iface != null) { // means that some configuration has changed
if (!dispatchAvailableForRequestListeners()) {
return null; // catastrophic failure - shut down
@@ -1300,7 +1305,7 @@ public class HalDeviceManager {
}
private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType,
- InterfaceDestroyedListener destroyedListener, Looper looper) {
+ InterfaceDestroyedListener destroyedListener, Handler handler) {
if (DBG) {
Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
+ ", ifaceType=" + ifaceType);
@@ -1341,9 +1346,9 @@ public class HalDeviceManager {
cacheEntry.type = ifaceType;
if (destroyedListener != null) {
cacheEntry.destroyedListeners.add(
- new InterfaceDestroyedListenerProxy(destroyedListener,
- looper == null ? Looper.myLooper() : looper));
+ new InterfaceDestroyedListenerProxy(destroyedListener, handler));
}
+ cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
if (DBG) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
mInterfaceInfoCache.put(cacheEntry.name, cacheEntry);
@@ -1468,7 +1473,8 @@ public class HalDeviceManager {
if (isChipModeChangeProposed) {
for (int type: IFACE_TYPES_BY_PRIORITY) {
if (chipInfo.ifaces[type].length != 0) {
- if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+ if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
+ chipInfo.ifaces[ifaceType].length != 0)) {
if (DBG) {
Log.d(TAG, "Couldn't delete existing type " + type
+ " interfaces for requested type");
@@ -1498,17 +1504,17 @@ public class HalDeviceManager {
}
if (tooManyInterfaces > 0) { // may need to delete some
- if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+ if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
+ chipInfo.ifaces[ifaceType].length != 0)) {
if (DBG) {
Log.d(TAG, "Would need to delete some higher priority interfaces");
}
return null;
}
- // arbitrarily pick the first interfaces to delete
- for (int i = 0; i < tooManyInterfaces; ++i) {
- interfacesToBeRemovedFirst.add(chipInfo.ifaces[type][i]);
- }
+ // delete the most recently created interfaces
+ interfacesToBeRemovedFirst = selectInterfacesToDelete(tooManyInterfaces,
+ chipInfo.ifaces[type]);
}
}
@@ -1576,14 +1582,20 @@ public class HalDeviceManager {
* interface type.
*
* Rules:
- * 1. Request for AP or STA will destroy any other interface (except see #4)
+ * 1. Request for AP or STA will destroy any other interface (except see #4 and #5)
* 2. Request for P2P will destroy NAN-only
* 3. Request for NAN will not destroy any interface
* --
* 4. No interface will be destroyed for a requested interface of the same type
+ * 5. No interface will be destroyed if one of the requested interfaces already exists
*/
private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
- int requestedIfaceType) {
+ int requestedIfaceType, boolean requestedIfaceTypeAlreadyExists) {
+ // rule 5
+ if (requestedIfaceTypeAlreadyExists) {
+ return false;
+ }
+
// rule 4
if (existingIfaceType == requestedIfaceType) {
return false;
@@ -1604,6 +1616,46 @@ public class HalDeviceManager {
}
/**
+ * Selects the interfaces to delete.
+ *
+ * Rule: select the most recently created interfaces in order.
+ *
+ * @param excessInterfaces Number of interfaces which need to be selected.
+ * @param interfaces Array of interfaces.
+ */
+ private List<WifiIfaceInfo> selectInterfacesToDelete(int excessInterfaces,
+ WifiIfaceInfo[] interfaces) {
+ if (DBG) {
+ Log.d(TAG, "selectInterfacesToDelete: excessInterfaces=" + excessInterfaces
+ + ", interfaces=" + Arrays.toString(interfaces));
+ }
+
+ boolean lookupError = false;
+ LongSparseArray<WifiIfaceInfo> orderedList = new LongSparseArray(interfaces.length);
+ for (WifiIfaceInfo info : interfaces) {
+ InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(info.name);
+ if (cacheEntry == null) {
+ Log.e(TAG,
+ "selectInterfacesToDelete: can't find cache entry with name=" + info.name);
+ lookupError = true;
+ break;
+ }
+ orderedList.append(cacheEntry.creationTime, info);
+ }
+
+ if (lookupError) {
+ Log.e(TAG, "selectInterfacesToDelete: falling back to arbitary selection");
+ return Arrays.asList(Arrays.copyOf(interfaces, excessInterfaces));
+ } else {
+ List<WifiIfaceInfo> result = new ArrayList<>(excessInterfaces);
+ for (int i = 0; i < excessInterfaces; ++i) {
+ result.add(orderedList.valueAt(orderedList.size() - i - 1));
+ }
+ return result;
+ }
+ }
+
+ /**
* Performs chip reconfiguration per the input:
* - Removes the specified interfaces
* - Reconfigures the chip to the new chip mode (if necessary)
@@ -1850,6 +1902,7 @@ public class HalDeviceManager {
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
@@ -1864,37 +1917,32 @@ public class HalDeviceManager {
}
void trigger() {
- mHandler.sendMessage(mHandler.obtainMessage(LISTENER_TRIGGERED));
+ if (mFrontOfQueue) {
+ mHandler.postAtFrontOfQueue(() -> {
+ action();
+ });
+ } else {
+ mHandler.post(() -> {
+ action();
+ });
+ }
}
protected abstract void action();
- ListenerProxy(LISTENER listener, Looper looper, String tag) {
+ ListenerProxy(LISTENER listener, Handler handler, boolean frontOfQueue, String tag) {
mListener = listener;
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- if (DBG) {
- Log.d(tag, "ListenerProxy.handleMessage: what=" + msg.what);
- }
- switch (msg.what) {
- case LISTENER_TRIGGERED:
- action();
- break;
- default:
- Log.e(tag, "ListenerProxy.handleMessage: unknown message what="
- + msg.what);
- }
- }
- };
+ mHandler = handler;
+ mFrontOfQueue = frontOfQueue;
}
}
private class InterfaceDestroyedListenerProxy extends
ListenerProxy<InterfaceDestroyedListener> {
InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
- Looper looper) {
- super(destroyedListener, looper, "InterfaceDestroyedListenerProxy");
+ Handler handler) {
+ super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
+ true, "InterfaceDestroyedListenerProxy");
}
@Override
@@ -1906,8 +1954,9 @@ public class HalDeviceManager {
private class InterfaceAvailableForRequestListenerProxy extends
ListenerProxy<InterfaceAvailableForRequestListener> {
InterfaceAvailableForRequestListenerProxy(
- InterfaceAvailableForRequestListener destroyedListener, Looper looper) {
- super(destroyedListener, looper, "InterfaceAvailableForRequestListenerProxy");
+ InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
+ super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
+ false, "InterfaceAvailableForRequestListenerProxy");
}
@Override
diff --git a/com/android/server/wifi/SoftApManager.java b/com/android/server/wifi/SoftApManager.java
index d4a1ea50..e045c396 100644
--- a/com/android/server/wifi/SoftApManager.java
+++ b/com/android/server/wifi/SoftApManager.java
@@ -20,8 +20,12 @@ import static com.android.server.wifi.util.ApConfigUtil.ERROR_GENERIC;
import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL;
import static com.android.server.wifi.util.ApConfigUtil.SUCCESS;
+import android.annotation.NonNull;
+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;
@@ -29,8 +33,11 @@ import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.net.BaseNetworkObserver;
@@ -46,6 +53,8 @@ import java.util.Locale;
public class SoftApManager implements ActiveModeManager {
private static final String TAG = "SoftApManager";
+ private final Context mContext;
+
private final WifiNative mWifiNative;
private final String mCountryCode;
@@ -55,14 +64,29 @@ public class SoftApManager implements ActiveModeManager {
private final Listener mListener;
private final IApInterface mApInterface;
+ private final String mApInterfaceName;
private final INetworkManagementService mNwService;
private final WifiApConfigStore mWifiApConfigStore;
private final WifiMetrics mWifiMetrics;
+ private final int mMode;
private WifiConfiguration mApConfig;
+ private int mNumAssociatedStations = 0;
+
+ /**
+ * Listener for AP Interface events.
+ */
+ public class ApInterfaceListener extends IApInterfaceEventCallback.Stub {
+ @Override
+ public void onNumAssociatedStationsChanged(int numStations) {
+ mStateMachine.sendMessage(
+ SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations);
+ }
+ }
+
/**
* Listener for soft AP state changes.
*/
@@ -75,23 +99,29 @@ public class SoftApManager implements ActiveModeManager {
void onStateChanged(int state, int failureReason);
}
- public SoftApManager(Looper looper,
+ public SoftApManager(Context context,
+ Looper looper,
WifiNative wifiNative,
String countryCode,
Listener listener,
- IApInterface apInterface,
+ @NonNull IApInterface apInterface,
+ @NonNull String ifaceName,
INetworkManagementService nms,
WifiApConfigStore wifiApConfigStore,
- WifiConfiguration config,
+ @NonNull SoftApModeConfiguration apConfig,
WifiMetrics wifiMetrics) {
mStateMachine = new SoftApStateMachine(looper);
+ mContext = context;
mWifiNative = wifiNative;
mCountryCode = countryCode;
mListener = listener;
mApInterface = apInterface;
+ mApInterfaceName = ifaceName;
mNwService = nms;
mWifiApConfigStore = wifiApConfigStore;
+ mMode = apConfig.getTargetMode();
+ WifiConfiguration config = apConfig.getWifiConfiguration();
if (config == null) {
mApConfig = mWifiApConfigStore.getApConfiguration();
} else {
@@ -115,14 +145,52 @@ public class SoftApManager implements ActiveModeManager {
}
/**
+ * Get number of stations associated with this soft AP
+ */
+ @VisibleForTesting
+ public int getNumAssociatedStations() {
+ return mNumAssociatedStations;
+ }
+
+ /**
+ * Set number of stations associated with this soft AP
+ * @param numStations Number of connected stations
+ */
+ private void setNumAssociatedStations(int numStations) {
+ if (mNumAssociatedStations == numStations) {
+ return;
+ }
+ mNumAssociatedStations = numStations;
+ Log.d(TAG, "Number of associated stations changed: " + mNumAssociatedStations);
+
+ // TODO:(b/63906412) send it up to settings.
+ mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(mNumAssociatedStations, mMode);
+ }
+
+ /**
* Update AP state.
- * @param state new AP state
+ * @param newState new AP state
+ * @param currentState current AP state
* @param reason Failure reason if the new AP state is in failure state
*/
- private void updateApState(int state, int reason) {
+ private void updateApState(int newState, int currentState, int reason) {
if (mListener != null) {
- mListener.onStateChanged(state, reason);
+ mListener.onStateChanged(newState, reason);
}
+
+ //send the AP state change broadcast
+ final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, newState);
+ intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, currentState);
+ if (newState == WifiManager.WIFI_AP_STATE_FAILED) {
+ //only set reason number when softAP start failed
+ intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
+ }
+
+ intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName);
+ intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mMode);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
/**
@@ -142,6 +210,7 @@ public class SoftApManager implements ActiveModeManager {
int result = ApConfigUtil.updateApChannelConfig(
mWifiNative, mCountryCode,
mWifiApConfigStore.getAllowed2GChannel(), localConfig);
+
if (result != SUCCESS) {
Log.e(TAG, "Failed to update AP band and channel");
return result;
@@ -180,7 +249,7 @@ public class SoftApManager implements ActiveModeManager {
return ERROR_GENERIC;
}
- success = mApInterface.startHostapd();
+ success = mApInterface.startHostapd(new ApInterfaceListener());
if (!success) {
Log.e(TAG, "Failed to start hostapd.");
return ERROR_GENERIC;
@@ -234,6 +303,7 @@ public class SoftApManager implements ActiveModeManager {
public static final int CMD_STOP = 1;
public static final int CMD_AP_INTERFACE_BINDER_DEATH = 2;
public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
+ public static final int CMD_NUM_ASSOCIATED_STATIONS_CHANGED = 4;
private final State mIdleState = new IdleState();
private final State mStartedState = new StartedState();
@@ -280,10 +350,24 @@ public class SoftApManager implements ActiveModeManager {
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_START:
- updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
+ // first a sanity check on the interface name. If we failed to retrieve it,
+ // we are going to have a hard time setting up routing.
+ if (TextUtils.isEmpty(mApInterfaceName)) {
+ Log.e(TAG, "Not starting softap mode without an interface name.");
+ updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+ WifiManager.WIFI_AP_STATE_DISABLED,
+ WifiManager.SAP_START_FAILURE_GENERAL);
+ mWifiMetrics.incrementSoftApStartResult(
+ false, WifiManager.SAP_START_FAILURE_GENERAL);
+ break;
+ }
+ updateApState(WifiManager.WIFI_AP_STATE_ENABLING,
+ WifiManager.WIFI_AP_STATE_DISABLED, 0);
+ setNumAssociatedStations(0);
if (!mDeathRecipient.linkToDeath(mApInterface.asBinder())) {
mDeathRecipient.unlinkToDeath();
updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+ WifiManager.WIFI_AP_STATE_ENABLING,
WifiManager.SAP_START_FAILURE_GENERAL);
mWifiMetrics.incrementSoftApStartResult(
false, WifiManager.SAP_START_FAILURE_GENERAL);
@@ -291,12 +375,13 @@ public class SoftApManager implements ActiveModeManager {
}
try {
- mNetworkObserver = new NetworkObserver(mApInterface.getInterfaceName());
+ mNetworkObserver = new NetworkObserver(mApInterfaceName);
mNwService.registerObserver(mNetworkObserver);
} catch (RemoteException e) {
mDeathRecipient.unlinkToDeath();
unregisterObserver();
updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+ WifiManager.WIFI_AP_STATE_ENABLING,
WifiManager.SAP_START_FAILURE_GENERAL);
mWifiMetrics.incrementSoftApStartResult(
false, WifiManager.SAP_START_FAILURE_GENERAL);
@@ -311,7 +396,9 @@ public class SoftApManager implements ActiveModeManager {
}
mDeathRecipient.unlinkToDeath();
unregisterObserver();
- updateApState(WifiManager.WIFI_AP_STATE_FAILED, failureReason);
+ updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+ WifiManager.WIFI_AP_STATE_ENABLING,
+ failureReason);
mWifiMetrics.incrementSoftApStartResult(false, failureReason);
break;
}
@@ -347,11 +434,15 @@ public class SoftApManager implements ActiveModeManager {
mIfaceIsUp = isUp;
if (isUp) {
Log.d(TAG, "SoftAp is ready for use");
- updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0);
+ updateApState(WifiManager.WIFI_AP_STATE_ENABLED,
+ WifiManager.WIFI_AP_STATE_ENABLING, 0);
mWifiMetrics.incrementSoftApStartResult(true, 0);
} else {
// TODO: handle the case where the interface was up, but goes down
}
+
+ mWifiMetrics.addSoftApUpChangedEvent(isUp, mMode);
+ setNumAssociatedStations(0);
}
@Override
@@ -359,7 +450,7 @@ public class SoftApManager implements ActiveModeManager {
mIfaceIsUp = false;
InterfaceConfiguration config = null;
try {
- config = mNwService.getInterfaceConfig(mApInterface.getInterfaceName());
+ config = mNwService.getInterfaceConfig(mApInterfaceName);
} catch (RemoteException e) {
}
if (config != null) {
@@ -370,6 +461,13 @@ public class SoftApManager implements ActiveModeManager {
@Override
public boolean processMessage(Message message) {
switch (message.what) {
+ case CMD_NUM_ASSOCIATED_STATIONS_CHANGED:
+ if (message.arg1 < 0) {
+ Log.e(TAG, "Invalid number of associated stations: " + message.arg1);
+ break;
+ }
+ setNumAssociatedStations(message.arg1);
+ break;
case CMD_INTERFACE_STATUS_CHANGED:
if (message.obj != mNetworkObserver) {
// This is from some time before the most recent configuration.
@@ -383,15 +481,23 @@ public class SoftApManager implements ActiveModeManager {
break;
case CMD_AP_INTERFACE_BINDER_DEATH:
case CMD_STOP:
- updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0);
+ updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
+ WifiManager.WIFI_AP_STATE_ENABLED, 0);
+ setNumAssociatedStations(0);
stopSoftAp();
if (message.what == CMD_AP_INTERFACE_BINDER_DEATH) {
updateApState(WifiManager.WIFI_AP_STATE_FAILED,
- WifiManager.SAP_START_FAILURE_GENERAL);
+ WifiManager.WIFI_AP_STATE_DISABLING,
+ WifiManager.SAP_START_FAILURE_GENERAL);
} else {
- updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0);
+ updateApState(WifiManager.WIFI_AP_STATE_DISABLED,
+ WifiManager.WIFI_AP_STATE_DISABLING, 0);
}
transitionTo(mIdleState);
+
+ // Need this here since we are exiting |Started| state and won't handle any
+ // future CMD_INTERFACE_STATUS_CHANGED events after this point
+ mWifiMetrics.addSoftApUpChangedEvent(false, mMode);
break;
default:
return NOT_HANDLED;
@@ -399,6 +505,5 @@ public class SoftApManager implements ActiveModeManager {
return HANDLED;
}
}
-
}
}
diff --git a/com/android/server/wifi/SupplicantStaIfaceHal.java b/com/android/server/wifi/SupplicantStaIfaceHal.java
index c300c3a3..c93e1102 100644
--- a/com/android/server/wifi/SupplicantStaIfaceHal.java
+++ b/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -1938,7 +1938,6 @@ public class SupplicantStaIfaceHal {
}
private class SupplicantStaIfaceHalCallback extends ISupplicantStaIfaceCallback.Stub {
- private static final int WLAN_REASON_IE_IN_4WAY_DIFFERS = 17; // IEEE 802.11i
private boolean mStateIsFourway = false; // Used to help check for PSK password mismatch
/**
@@ -2083,10 +2082,14 @@ public class SupplicantStaIfaceHal {
+ " reasonCode=" + reasonCode);
}
if (mStateIsFourway
- && (!locallyGenerated || reasonCode != WLAN_REASON_IE_IN_4WAY_DIFFERS)) {
+ && (!locallyGenerated || reasonCode != ReasonCode.IE_IN_4WAY_DIFFERS)) {
mWifiMonitor.broadcastAuthenticationFailureEvent(
mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
}
+ if (reasonCode == ReasonCode.IEEE_802_1X_AUTH_FAILED) {
+ mWifiMonitor.broadcastAuthenticationFailureEvent(
+ mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE);
+ }
mWifiMonitor.broadcastNetworkDisconnectionEvent(
mIfaceName, locallyGenerated ? 1 : 0, reasonCode,
NativeUtil.macAddressFromByteArray(bssid));
diff --git a/com/android/server/wifi/VelocityBasedConnectedScore.java b/com/android/server/wifi/VelocityBasedConnectedScore.java
index 9d90332e..69643ce9 100644
--- a/com/android/server/wifi/VelocityBasedConnectedScore.java
+++ b/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -34,7 +34,8 @@ public class VelocityBasedConnectedScore extends ConnectedScore {
private final int mThresholdMinimumRssi24; // -85
private int mFrequency = 5000;
- private int mRssi = 0;
+ private double mThresholdMinimumRssi;
+ private double mThresholdAdjustment;
private final KalmanFilter mFilter;
private long mLastMillis;
@@ -44,6 +45,7 @@ public class VelocityBasedConnectedScore extends ConnectedScore {
R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
mThresholdMinimumRssi24 = context.getResources().getInteger(
R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+ mThresholdMinimumRssi = mThresholdMinimumRssi5;
mFilter = new KalmanFilter();
mFilter.mH = new Matrix(2, new double[]{1.0, 0.0});
mFilter.mR = new Matrix(1, new double[]{1.0});
@@ -69,6 +71,7 @@ public class VelocityBasedConnectedScore extends ConnectedScore {
@Override
public void reset() {
mLastMillis = 0;
+ mThresholdAdjustment = 0.0;
}
/**
@@ -97,6 +100,8 @@ public class VelocityBasedConnectedScore extends ConnectedScore {
mFilter.predict();
mLastMillis = millis;
mFilter.update(new Matrix(1, new double[]{rssi}));
+ mFilteredRssi = mFilter.mx.get(0, 0);
+ mEstimatedRateOfRssiChange = mFilter.mx.get(1, 0);
}
/**
@@ -106,10 +111,63 @@ public class VelocityBasedConnectedScore extends ConnectedScore {
public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
int frequency = wifiInfo.getFrequency();
if (frequency != mFrequency) {
- reset(); // Probably roamed
+ mLastMillis = 0; // Probably roamed; reset filter but retain threshold adjustment
+ // Consider resetting or partially resetting threshold adjustment
+ // Consider checking bssid
mFrequency = frequency;
+ mThresholdMinimumRssi =
+ mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
}
updateUsingRssi(wifiInfo.getRssi(), millis, mDefaultRssiStandardDeviation);
+ adjustThreshold(wifiInfo);
+ }
+
+ private double mFilteredRssi;
+ private double mEstimatedRateOfRssiChange;
+
+ /**
+ * Returns the most recently computed extimate of the RSSI.
+ */
+ public double getFilteredRssi() {
+ return mFilteredRssi;
+ }
+
+ /**
+ * Returns the estimated rate of change of RSSI, in dB/second
+ */
+ public double getEstimatedRateOfRssiChange() {
+ return mEstimatedRateOfRssiChange;
+ }
+
+ /**
+ * Returns the adjusted RSSI threshold
+ */
+ public double getAdjustedRssiThreshold() {
+ return mThresholdMinimumRssi + mThresholdAdjustment;
+ }
+
+ /**
+ * Adjusts the threshold if appropriate
+ * <p>
+ * If the (filtered) rssi is near or below the current effective threshold, and the
+ * rate of rssi change is small, and there is traffic, and the error rate is looking
+ * reasonable, then decrease the effective threshold to keep from dropping a perfectly good
+ * connection.
+ *
+ */
+ private void adjustThreshold(WifiInfo wifiInfo) {
+ if (mThresholdAdjustment < -7) return;
+ if (mFilteredRssi >= getAdjustedRssiThreshold() + 2.0) return;
+ if (Math.abs(mEstimatedRateOfRssiChange) >= 0.2) return;
+ if (wifiInfo.txSuccessRate < 10) return;
+ if (wifiInfo.rxSuccessRate < 10) return;
+ double probabilityOfSuccessfulTx = (
+ wifiInfo.txSuccessRate / (wifiInfo.txSuccessRate + wifiInfo.txBadRate)
+ );
+ if (probabilityOfSuccessfulTx >= 0.2) {
+ // May want this amount to vary with how close to threshold we are
+ mThresholdAdjustment -= 0.5;
+ }
}
/**
@@ -117,7 +175,7 @@ public class VelocityBasedConnectedScore extends ConnectedScore {
*/
@Override
public int generateScore() {
- int badRssi = mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
+ double badRssi = getAdjustedRssiThreshold();
double horizonSeconds = 15.0;
Matrix x = new Matrix(mFilter.mx);
double filteredRssi = x.get(0, 0);
diff --git a/com/android/server/wifi/WifiConfigManager.java b/com/android/server/wifi/WifiConfigManager.java
index b610bd98..8fb61c21 100644
--- a/com/android/server/wifi/WifiConfigManager.java
+++ b/com/android/server/wifi/WifiConfigManager.java
@@ -200,12 +200,6 @@ public class WifiConfigManager {
@VisibleForTesting
public static final int LINK_CONFIGURATION_BSSID_MATCH_LENGTH = 16;
/**
- * Flags to be passed in for |canModifyNetwork| to decide if the change is minor and can
- * bypass the lockdown checks.
- */
- private static final boolean ALLOW_LOCKDOWN_CHECK_BYPASS = true;
- private static final boolean DISALLOW_LOCKDOWN_CHECK_BYPASS = false;
- /**
* Log tag for this class.
*/
private static final String TAG = "WifiConfigManager";
@@ -646,9 +640,8 @@ public class WifiConfigManager {
*
* @param config WifiConfiguration object corresponding to the network to be modified.
* @param uid UID of the app requesting the modification.
- * @param ignoreLockdown Ignore the configuration lockdown checks for connection attempts.
*/
- private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
+ private boolean canModifyNetwork(WifiConfiguration config, int uid) {
// System internals can always update networks; they're typically only
// making meteredHint or meteredOverride changes
if (uid == Process.SYSTEM_UID) {
@@ -685,12 +678,6 @@ public class WifiConfigManager {
final boolean isCreator = (config.creatorUid == uid);
- // Check if the |uid| holds the |NETWORK_SETTINGS| permission if the caller asks us to
- // bypass the lockdown checks.
- if (ignoreLockdown) {
- return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
- }
-
// Check if device has DPM capability. If it has and |dpmi| is still null, then we
// treat this case with suspicion and bail out.
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
@@ -980,7 +967,7 @@ public class WifiConfigManager {
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
// Check for the app's permission before we let it update this network.
- if (!canModifyNetwork(existingInternalConfig, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+ if (!canModifyNetwork(existingInternalConfig, uid)) {
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+ config.configKey());
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
@@ -1150,7 +1137,7 @@ public class WifiConfigManager {
if (config == null) {
return false;
}
- if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+ if (!canModifyNetwork(config, uid)) {
Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
+ config.configKey());
return false;
@@ -1483,7 +1470,7 @@ public class WifiConfigManager {
if (config == null) {
return false;
}
- if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+ if (!canModifyNetwork(config, uid)) {
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+ config.configKey());
return false;
@@ -1518,7 +1505,7 @@ public class WifiConfigManager {
if (config == null) {
return false;
}
- if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+ if (!canModifyNetwork(config, uid)) {
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+ config.configKey());
return false;
@@ -1535,18 +1522,13 @@ public class WifiConfigManager {
}
/**
- * Checks if the |uid| has the necessary permission to force a connection to a network
- * and updates the last connected UID for the provided configuration.
+ * Updates the last connected UID for the provided configuration.
*
* @param networkId network ID corresponding to the network.
* @param uid uid of the app requesting the connection.
- * @return true if |uid| has the necessary permission to trigger explicit connection to the
- * network, false otherwise.
- * Note: This returns true only for the system settings/sysui app which holds the
- * {@link android.Manifest.permission#NETWORK_SETTINGS} permission. We don't want to let
- * any other app force connection to a network.
+ * @return true if the network was found, false otherwise.
*/
- public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
+ public boolean updateLastConnectUid(int networkId, int uid) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Update network last connect UID for " + networkId);
}
@@ -1558,11 +1540,6 @@ public class WifiConfigManager {
if (config == null) {
return false;
}
- if (!canModifyNetwork(config, uid, ALLOW_LOCKDOWN_CHECK_BYPASS)) {
- Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
- + config.configKey());
- return false;
- }
config.lastConnectUid = uid;
return true;
}
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index 1cbb29ef..cc559341 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -16,6 +16,7 @@
package com.android.server.wifi;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
import android.net.NetworkKey;
@@ -23,7 +24,6 @@ import android.net.NetworkScoreManager;
import android.net.wifi.IApInterface;
import android.net.wifi.IWifiScanner;
import android.net.wifi.IWificond;
-import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiScanner;
@@ -163,7 +163,7 @@ public class WifiInjector {
mWifiMetrics = new WifiMetrics(mClock, wifiStateMachineLooper, awareMetrics);
// Modules interacting with Native.
mWifiMonitor = new WifiMonitor(this);
- mHalDeviceManager = new HalDeviceManager();
+ mHalDeviceManager = new HalDeviceManager(mClock);
mWifiVendorHal =
new WifiVendorHal(mHalDeviceManager, mWifiStateMachineHandlerThread.getLooper());
mSupplicantStaIfaceHal = new SupplicantStaIfaceHal(mContext, mWifiMonitor);
@@ -370,16 +370,18 @@ public class WifiInjector {
* changes
* @param listener listener for SoftApManager
* @param apInterface network interface to start hostapd against
- * @param config softAp WifiConfiguration
+ * @param ifaceName name of the ap interface
+ * @param config SoftApModeConfiguration object holding the config and mode
* @return an instance of SoftApManager
*/
public SoftApManager makeSoftApManager(INetworkManagementService nmService,
SoftApManager.Listener listener,
- IApInterface apInterface,
- WifiConfiguration config) {
- return new SoftApManager(mWifiServiceHandlerThread.getLooper(),
+ @NonNull IApInterface apInterface,
+ @NonNull String ifaceName,
+ @NonNull SoftApModeConfiguration config) {
+ return new SoftApManager(mContext, mWifiServiceHandlerThread.getLooper(),
mWifiNative, mCountryCode.getCountryCode(),
- listener, apInterface, nmService,
+ listener, apInterface, ifaceName, nmService,
mWifiApConfigStore, config, mWifiMetrics);
}
diff --git a/com/android/server/wifi/WifiMetrics.java b/com/android/server/wifi/WifiMetrics.java
index 7254ad48..4e277a1d 100644
--- a/com/android/server/wifi/WifiMetrics.java
+++ b/com/android/server/wifi/WifiMetrics.java
@@ -31,6 +31,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wifi.aware.WifiAwareMetrics;
import com.android.server.wifi.hotspot2.ANQPNetworkKey;
import com.android.server.wifi.hotspot2.NetworkDetail;
@@ -41,8 +42,10 @@ import com.android.server.wifi.hotspot2.Utils;
import com.android.server.wifi.nano.WifiMetricsProto;
import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
+import com.android.server.wifi.nano.WifiMetricsProto.SoftApConnectedClientsEvent;
import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
+import com.android.server.wifi.nano.WifiMetricsProto.WpsMetrics;
import com.android.server.wifi.util.InformationElementUtil;
import com.android.server.wifi.util.ScanResultUtil;
@@ -80,6 +83,8 @@ public class WifiMetrics {
public static final long TIMEOUT_RSSI_DELTA_MILLIS = 3000;
private static final int MIN_WIFI_SCORE = 0;
private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
+ @VisibleForTesting
+ static final int LOW_WIFI_SCORE = 50; // Mobile data score
private final Object mLock = new Object();
private static final int MAX_CONNECTION_EVENTS = 256;
// Largest bucket in the NumConnectableNetworkCount histogram,
@@ -92,11 +97,14 @@ public class WifiMetrics {
public static final int MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET = 20;
public static final int MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET = 50;
private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
+ // Max limit for number of soft AP related events, extra events will be dropped.
+ private static final int MAX_NUM_SOFT_AP_EVENTS = 256;
private Clock mClock;
private boolean mScreenOn;
private int mWifiState;
private WifiAwareMetrics mWifiAwareMetrics;
private final PnoScanMetrics mPnoScanMetrics = new PnoScanMetrics();
+ private final WpsMetrics mWpsMetrics = new WpsMetrics();
private Handler mHandler;
private WifiConfigManager mWifiConfigManager;
private WifiNetworkSelector mWifiNetworkSelector;
@@ -167,6 +175,10 @@ public class WifiMetrics {
private boolean mIsWifiNetworksAvailableNotificationOn = false;
private int mNumOpenNetworkConnectMessageFailedToSend = 0;
private int mNumOpenNetworkRecommendationUpdates = 0;
+ /** List of soft AP events related to number of connected clients in tethered mode */
+ private final List<SoftApConnectedClientsEvent> mSoftApEventListTethered = new ArrayList<>();
+ /** List of soft AP events related to number of connected clients in local only mode */
+ private final List<SoftApConnectedClientsEvent> mSoftApEventListLocalOnly = new ArrayList<>();
private final SparseIntArray mObservedHotspotR1ApInScanHistogram = new SparseIntArray();
private final SparseIntArray mObservedHotspotR2ApInScanHistogram = new SparseIntArray();
@@ -479,6 +491,78 @@ public class WifiMetrics {
}
}
+ /**
+ * Increment total number of wps connection attempts
+ */
+ public void incrementWpsAttemptCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsAttempts++;
+ }
+ }
+
+ /**
+ * Increment total number of wps connection success
+ */
+ public void incrementWpsSuccessCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsSuccess++;
+ }
+ }
+
+ /**
+ * Increment total number of wps failure on start
+ */
+ public void incrementWpsStartFailureCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsStartFailure++;
+ }
+ }
+
+ /**
+ * Increment total number of wps overlap failure
+ */
+ public void incrementWpsOverlapFailureCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsOverlapFailure++;
+ }
+ }
+
+ /**
+ * Increment total number of wps timeout failure
+ */
+ public void incrementWpsTimeoutFailureCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsTimeoutFailure++;
+ }
+ }
+
+ /**
+ * Increment total number of other wps failure during connection
+ */
+ public void incrementWpsOtherConnectionFailureCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsOtherConnectionFailure++;
+ }
+ }
+
+ /**
+ * Increment total number of supplicant failure after wps
+ */
+ public void incrementWpsSupplicantFailureCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsSupplicantFailure++;
+ }
+ }
+
+ /**
+ * Increment total number of wps cancellation
+ */
+ public void incrementWpsCancellationCount() {
+ synchronized (mLock) {
+ mWpsMetrics.numWpsCancellation++;
+ }
+ }
+
// Values used for indexing SystemStateEntries
private static final int SCREEN_ON = 1;
private static final int SCREEN_OFF = 0;
@@ -1055,10 +1139,14 @@ public class WifiMetrics {
}
}
+ private boolean mWifiWins = false; // Based on scores, use wifi instead of mobile data?
+
/**
* Increments occurence of a particular wifi score calculated
* in WifiScoreReport by current connected network. Scores are bounded
- * within [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray
+ * within [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray.
+ *
+ * Also records events when the current score breaches significant thresholds.
*/
public void incrementWifiScoreCount(int score) {
if (score < MIN_WIFI_SCORE || score > MAX_WIFI_SCORE) {
@@ -1067,6 +1155,20 @@ public class WifiMetrics {
synchronized (mLock) {
int count = mWifiScoreCounts.get(score);
mWifiScoreCounts.put(score, count + 1);
+
+ boolean wifiWins = mWifiWins;
+ if (mWifiWins && score < LOW_WIFI_SCORE) {
+ wifiWins = false;
+ } else if (!mWifiWins && score > LOW_WIFI_SCORE) {
+ wifiWins = true;
+ }
+ mLastScore = score;
+ if (wifiWins != mWifiWins) {
+ mWifiWins = wifiWins;
+ StaEvent event = new StaEvent();
+ event.type = StaEvent.TYPE_SCORE_BREACH;
+ addStaEvent(event);
+ }
}
}
@@ -1106,6 +1208,53 @@ public class WifiMetrics {
}
/**
+ * Adds a record indicating the current up state of soft AP
+ */
+ public void addSoftApUpChangedEvent(boolean isUp, int mode) {
+ SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
+ event.eventType = isUp ? SoftApConnectedClientsEvent.SOFT_AP_UP :
+ SoftApConnectedClientsEvent.SOFT_AP_DOWN;
+ event.numConnectedClients = 0;
+ addSoftApConnectedClientsEvent(event, mode);
+ }
+
+ /**
+ * Adds a record for current number of associated stations to soft AP
+ */
+ public void addSoftApNumAssociatedStationsChangedEvent(int numStations, int mode) {
+ SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
+ event.eventType = SoftApConnectedClientsEvent.NUM_CLIENTS_CHANGED;
+ event.numConnectedClients = numStations;
+ addSoftApConnectedClientsEvent(event, mode);
+ }
+
+ /**
+ * Adds a record to the corresponding event list based on mode param
+ */
+ private void addSoftApConnectedClientsEvent(SoftApConnectedClientsEvent event, int mode) {
+ synchronized (mLock) {
+ List<SoftApConnectedClientsEvent> softApEventList;
+ switch (mode) {
+ case WifiManager.IFACE_IP_MODE_TETHERED:
+ softApEventList = mSoftApEventListTethered;
+ break;
+ case WifiManager.IFACE_IP_MODE_LOCAL_ONLY:
+ softApEventList = mSoftApEventListLocalOnly;
+ break;
+ default:
+ return;
+ }
+
+ if (softApEventList.size() > MAX_NUM_SOFT_AP_EVENTS) {
+ return;
+ }
+
+ event.timeStampMillis = mClock.getWallClockMillis();
+ softApEventList.add(event);
+ }
+ }
+
+ /**
* Increment number of times the HAL crashed.
*/
public void incrementNumHalCrashes() {
@@ -1634,6 +1783,40 @@ public class WifiMetrics {
+ mObservedHotspotR1ApsPerEssInScanHistogram);
pw.println("mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram="
+ mObservedHotspotR2ApsPerEssInScanHistogram);
+
+ pw.println("mSoftApTetheredEvents:");
+ for (SoftApConnectedClientsEvent event : mSoftApEventListTethered) {
+ StringBuilder eventLine = new StringBuilder();
+ eventLine.append("event_type=" + event.eventType);
+ eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
+ eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+ pw.println(eventLine.toString());
+ }
+ pw.println("mSoftApLocalOnlyEvents:");
+ for (SoftApConnectedClientsEvent event : mSoftApEventListLocalOnly) {
+ StringBuilder eventLine = new StringBuilder();
+ eventLine.append("event_type=" + event.eventType);
+ eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
+ eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+ pw.println(eventLine.toString());
+ }
+
+ pw.println("mWpsMetrics.numWpsAttempts="
+ + mWpsMetrics.numWpsAttempts);
+ pw.println("mWpsMetrics.numWpsSuccess="
+ + mWpsMetrics.numWpsSuccess);
+ pw.println("mWpsMetrics.numWpsStartFailure="
+ + mWpsMetrics.numWpsStartFailure);
+ pw.println("mWpsMetrics.numWpsOverlapFailure="
+ + mWpsMetrics.numWpsOverlapFailure);
+ pw.println("mWpsMetrics.numWpsTimeoutFailure="
+ + mWpsMetrics.numWpsTimeoutFailure);
+ pw.println("mWpsMetrics.numWpsOtherConnectionFailure="
+ + mWpsMetrics.numWpsOtherConnectionFailure);
+ pw.println("mWpsMetrics.numWpsSupplicantFailure="
+ + mWpsMetrics.numWpsSupplicantFailure);
+ pw.println("mWpsMetrics.numWpsCancellation="
+ + mWpsMetrics.numWpsCancellation);
}
}
}
@@ -1906,6 +2089,19 @@ public class WifiMetrics {
mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram =
makeNumConnectableNetworksBucketArray(
mObservedHotspotR2ApsPerEssInScanHistogram);
+
+ if (mSoftApEventListTethered.size() > 0) {
+ mWifiLogProto.softApConnectedClientsEventsTethered =
+ mSoftApEventListTethered.toArray(
+ mWifiLogProto.softApConnectedClientsEventsTethered);
+ }
+ if (mSoftApEventListLocalOnly.size() > 0) {
+ mWifiLogProto.softApConnectedClientsEventsLocalOnly =
+ mSoftApEventListLocalOnly.toArray(
+ mWifiLogProto.softApConnectedClientsEventsLocalOnly);
+ }
+
+ mWifiLogProto.wpsMetrics = mWpsMetrics;
}
}
@@ -1966,6 +2162,9 @@ public class WifiMetrics {
mObservedHotspotR2EssInScanHistogram.clear();
mObservedHotspotR1ApsPerEssInScanHistogram.clear();
mObservedHotspotR2ApsPerEssInScanHistogram.clear();
+ mSoftApEventListTethered.clear();
+ mSoftApEventListLocalOnly.clear();
+ mWpsMetrics.clear();
}
}
@@ -1984,6 +2183,7 @@ public class WifiMetrics {
public void setWifiState(int wifiState) {
synchronized (mLock) {
mWifiState = wifiState;
+ mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
}
}
@@ -2089,6 +2289,7 @@ public class WifiMetrics {
case StaEvent.TYPE_CONNECT_NETWORK:
case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
+ case StaEvent.TYPE_SCORE_BREACH:
break;
default:
Log.e(TAG, "Unknown StaEvent:" + type);
@@ -2109,10 +2310,12 @@ public class WifiMetrics {
staEvent.lastFreq = mLastPollFreq;
staEvent.lastLinkSpeed = mLastPollLinkSpeed;
staEvent.supplicantStateChangesBitmask = mSupplicantStateChangeBitmask;
+ staEvent.lastScore = mLastScore;
mSupplicantStateChangeBitmask = 0;
mLastPollRssi = -127;
mLastPollFreq = -1;
mLastPollLinkSpeed = -1;
+ mLastScore = -1;
mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis()));
// Prune StaEventList if it gets too long
if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
@@ -2268,6 +2471,9 @@ public class WifiMetrics {
.append(" reason=")
.append(frameworkDisconnectReasonToString(event.frameworkDisconnectReason));
break;
+ case StaEvent.TYPE_SCORE_BREACH:
+ sb.append("SCORE_BREACH");
+ break;
default:
sb.append("UNKNOWN " + event.type + ":");
break;
@@ -2275,6 +2481,7 @@ public class WifiMetrics {
if (event.lastRssi != -127) sb.append(" lastRssi=").append(event.lastRssi);
if (event.lastFreq != -1) sb.append(" lastFreq=").append(event.lastFreq);
if (event.lastLinkSpeed != -1) sb.append(" lastLinkSpeed=").append(event.lastLinkSpeed);
+ if (event.lastScore != -1) sb.append(" lastScore=").append(event.lastScore);
if (event.supplicantStateChangesBitmask != 0) {
sb.append(", ").append(supplicantStateChangesBitmaskToString(
event.supplicantStateChangesBitmask));
@@ -2337,11 +2544,12 @@ public class WifiMetrics {
return sb.toString();
}
- public static final int MAX_STA_EVENTS = 512;
+ public static final int MAX_STA_EVENTS = 768;
private LinkedList<StaEventWithTime> mStaEventList = new LinkedList<StaEventWithTime>();
private int mLastPollRssi = -127;
private int mLastPollLinkSpeed = -1;
private int mLastPollFreq = -1;
+ private int mLastScore = -1;
/**
* Converts the first 31 bits of a BitSet to a little endian int
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index eefd2f0d..cda1cf6f 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -213,6 +213,22 @@ public class WifiNative {
}
/**
+ * Query the list of valid frequencies for the provided band.
+ * The result depends on the on the country code that has been set.
+ *
+ * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+ * The following bands are supported:
+ * WifiScanner.WIFI_BAND_24_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+ * @return frequencies vector of valid frequencies (MHz), or null for error.
+ * @throws IllegalArgumentException if band is not recognized.
+ */
+ public int [] getChannelsForBand(int band) {
+ return mWificondControl.getChannelsForBand(band);
+ }
+
+ /**
* Start a scan using wificond for the given parameters.
* @param freqs list of frequencies to scan for, if null scan all supported channels.
* @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
@@ -1135,27 +1151,6 @@ public class WifiNative {
}
/**
- * Query the list of valid frequencies for the provided band.
- * The result depends on the on the country code that has been set.
- *
- * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
- * @return frequencies vector of valid frequencies (MHz), or null for error.
- * @throws IllegalArgumentException if band is not recognized.
- */
- public int [] getChannelsForBand(int band) {
- return mWifiVendorHal.getChannelsForBand(band);
- }
-
- /**
- * Indicates whether getChannelsForBand is supported.
- *
- * @return true if it is.
- */
- public boolean isGetChannelsForBandSupported() {
- return mWifiVendorHal.isGetChannelsForBandSupported();
- }
-
- /**
* RTT (Round Trip Time) measurement capabilities of the device.
*/
public RttManager.RttCapabilities getRttCapabilities() {
diff --git a/com/android/server/wifi/WifiScoreReport.java b/com/android/server/wifi/WifiScoreReport.java
index 324cdba8..e5281ef7 100644
--- a/com/android/server/wifi/WifiScoreReport.java
+++ b/com/android/server/wifi/WifiScoreReport.java
@@ -49,7 +49,7 @@ public class WifiScoreReport {
ConnectedScore mConnectedScore;
ConnectedScore mAggressiveConnectedScore;
- ConnectedScore mFancyConnectedScore;
+ VelocityBasedConnectedScore mFancyConnectedScore;
WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
mClock = clock;
@@ -127,9 +127,9 @@ public class WifiScoreReport {
int s2 = mFancyConnectedScore.generateScore();
if (aggressiveHandover == 0) {
- score = s0;
- } else {
score = s2;
+ } else {
+ score = s2; // TODO Remove aggressive handover plumbing (b/27877641)
}
//sanitize boundaries
@@ -171,25 +171,31 @@ public class WifiScoreReport {
private void logLinkMetrics(WifiInfo wifiInfo, long now, int s0, int s1, int s2) {
if (now < FIRST_REASONABLE_WALL_CLOCK) return;
double rssi = wifiInfo.getRssi();
+ double filteredRssi = mFancyConnectedScore.getFilteredRssi();
+ double rssiThreshold = mFancyConnectedScore.getAdjustedRssiThreshold();
int freq = wifiInfo.getFrequency();
int linkSpeed = wifiInfo.getLinkSpeed();
double txSuccessRate = wifiInfo.txSuccessRate;
double txRetriesRate = wifiInfo.txRetriesRate;
double txBadRate = wifiInfo.txBadRate;
double rxSuccessRate = wifiInfo.rxSuccessRate;
+ String s;
try {
String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
- String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
- "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
- timestamp, mSessionNumber, rssi, freq, linkSpeed,
+ s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
+ "%s,%d,%.1f,%.1f,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
+ timestamp, mSessionNumber, rssi, filteredRssi, rssiThreshold, freq, linkSpeed,
txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
s0, s1, s2);
- mLinkMetricsHistory.add(s);
} catch (Exception e) {
Log.e(TAG, "format problem", e);
+ return;
}
- while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
- mLinkMetricsHistory.removeFirst();
+ synchronized (mLinkMetricsHistory) {
+ mLinkMetricsHistory.add(s);
+ while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
+ mLinkMetricsHistory.removeFirst();
+ }
}
}
@@ -205,9 +211,15 @@ public class WifiScoreReport {
* @param args unused
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
- for (String line : mLinkMetricsHistory) {
+ LinkedList<String> history;
+ synchronized (mLinkMetricsHistory) {
+ history = new LinkedList<>(mLinkMetricsHistory);
+ }
+ pw.println("time,session,rssi,filtered_rssi,rssi_threshold,"
+ + "freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
+ for (String line : history) {
pw.println(line);
}
+ history.clear();
}
}
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index dacc2d47..e86bd541 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -16,6 +16,7 @@
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;
@@ -42,6 +43,7 @@ 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;
@@ -589,7 +591,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
*/
@Override
public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
- enforceChangePermission();
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return;
+ }
mLog.info("startScan uid=%").c(Binder.getCallingUid()).flush();
// Check and throttle background apps for wifi scan.
@@ -733,9 +737,21 @@ public class WifiServiceImpl extends IWifiManager.Stub {
"WifiService");
}
- private void enforceChangePermission() {
+ /**
+ * 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) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
"WifiService");
+
+ return mAppOps.noteOp(AppOpsManager.OP_CHANGE_WIFI_STATE, Binder.getCallingUid(),
+ callingPackage);
}
private void enforceLocationHardwarePermission() {
@@ -779,7 +795,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
@Override
public synchronized boolean setWifiEnabled(String packageName, boolean enable)
throws RemoteException {
- enforceChangePermission();
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
+
Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid() + ", package=" + packageName);
mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
@@ -1173,7 +1192,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- enforceChangePermission();
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return LocalOnlyHotspotCallback.ERROR_GENERIC;
+ }
enforceLocationPermission(packageName, uid);
// also need to verify that Locations services are enabled.
if (mSettingsStore.getLocationModeSetting(mContext) == Settings.Secure.LOCATION_MODE_OFF) {
@@ -1245,9 +1266,12 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* Hotspot.
*/
@Override
- public void stopLocalOnlyHotspot() {
+ public void stopLocalOnlyHotspot(String packageName) {
// first check if the caller has permission to stop a local only hotspot
- enforceChangePermission();
+ 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.
+ }
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -1349,8 +1373,10 @@ 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) {
- enforceChangePermission();
+ public void setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return;
+ }
int uid = Binder.getCallingUid();
// only allow Settings UI to write the stored SoftApConfig
if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) {
@@ -1382,8 +1408,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* see {@link android.net.wifi.WifiManager#disconnect()}
*/
@Override
- public void disconnect() {
- enforceChangePermission();
+ public void disconnect(String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return;
+ }
mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.disconnectCommand();
}
@@ -1392,8 +1420,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* see {@link android.net.wifi.WifiManager#reconnect()}
*/
@Override
- public void reconnect() {
- enforceChangePermission();
+ public void reconnect(String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return;
+ }
mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
}
@@ -1402,8 +1432,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* see {@link android.net.wifi.WifiManager#reassociate()}
*/
@Override
- public void reassociate() {
- enforceChangePermission();
+ public void reassociate(String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return;
+ }
mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.reassociateCommand();
}
@@ -1604,8 +1636,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* network if the operation succeeds, or {@code -1} if it fails
*/
@Override
- public int addOrUpdateNetwork(WifiConfiguration config) {
- enforceChangePermission();
+ public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return -1;
+ }
mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
// Previously, this API is overloaded for installing Passpoint profiles. Now
@@ -1624,7 +1658,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
config.enterpriseConfig.getClientCertificateChain());
passpointConfig.getCredential().setClientPrivateKey(
config.enterpriseConfig.getClientPrivateKey());
- if (!addOrUpdatePasspointConfiguration(passpointConfig)) {
+ if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) {
Slog.e(TAG, "Failed to add Passpoint profile");
return -1;
}
@@ -1675,8 +1709,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean removeNetwork(int netId) {
- enforceChangePermission();
+ public boolean removeNetwork(int netId, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
mLog.info("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
// TODO Add private logging for netId b/33807876
if (mWifiStateMachineChannel != null) {
@@ -1695,8 +1731,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean enableNetwork(int netId, boolean disableOthers) {
- enforceChangePermission();
+ public boolean enableNetwork(int netId, boolean disableOthers, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
// TODO b/33807876 Log netId
mLog.info("enableNetwork uid=% disableOthers=%")
.c(Binder.getCallingUid())
@@ -1718,8 +1756,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean disableNetwork(int netId) {
- enforceChangePermission();
+ public boolean disableNetwork(int netId, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
// TODO b/33807876 Log netId
mLog.info("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
@@ -1777,8 +1817,11 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return true on success or false on failure
*/
@Override
- public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
- enforceChangePermission();
+ public boolean addOrUpdatePasspointConfiguration(
+ PasspointConfiguration config, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
mLog.info("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1795,8 +1838,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return true on success or false on failure
*/
@Override
- public boolean removePasspointConfiguration(String fqdn) {
- enforceChangePermission();
+ public boolean removePasspointConfiguration(String fqdn, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1868,8 +1913,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* TODO: deprecate this
*/
@Override
- public boolean saveConfiguration() {
- enforceChangePermission();
+ public boolean saveConfiguration(String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
mLog.info("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (mWifiStateMachineChannel != null) {
return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
@@ -1882,16 +1929,11 @@ public class WifiServiceImpl extends IWifiManager.Stub {
/**
* Set the country code
* @param countryCode ISO 3166 country code.
- * @param persist {@code true} if the setting should be remembered.
*
- * The persist behavior exists so that wifi can fall back to the last
- * persisted country code on a restart, when the locale information is
- * not available from telephony.
*/
@Override
- public void setCountryCode(String countryCode, boolean persist) {
- Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
- " with persist set to " + persist);
+ public void setCountryCode(String countryCode) {
+ Slog.i(TAG, "WifiService trying to set country code to " + countryCode);
enforceConnectivityInternalPermission();
mLog.info("setCountryCode uid=%").c(Binder.getCallingUid()).flush();
final long token = Binder.clearCallingIdentity();
@@ -2070,9 +2112,13 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* an AsyncChannel communication with WifiService
*/
@Override
- public Messenger getWifiServiceMessenger() {
+ public Messenger getWifiServiceMessenger(String packageName) throws RemoteException {
enforceAccessPermission();
- enforceChangePermission();
+ 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");
+ }
mLog.info("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
return new Messenger(mClientHandler);
}
@@ -2081,9 +2127,11 @@ 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) {
+ public void disableEphemeralNetwork(String SSID, String packageName) {
enforceAccessPermission();
- enforceChangePermission();
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return;
+ }
mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.disableEphemeralNetwork(SSID);
}
@@ -2377,6 +2425,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
@Override
public void enableVerboseLogging(int verbose) {
enforceAccessPermission();
+ enforceNetworkSettingsPermission();
mLog.info("enableVerboseLogging uid=% verbose=%")
.c(Binder.getCallingUid())
.c(verbose).flush();
@@ -2436,8 +2485,10 @@ public class WifiServiceImpl extends IWifiManager.Stub {
}
@Override
- public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
- enforceChangePermission();
+ public boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName) {
+ if (enforceChangePermission(packageName) == MODE_IGNORED) {
+ return false;
+ }
mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%")
.c(Binder.getCallingUid())
.c(enabled).flush();
@@ -2466,7 +2517,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
}
@Override
- public void factoryReset() {
+ public void factoryReset(String packageName) {
enforceConnectivityInternalPermission();
mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush();
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
@@ -2492,9 +2543,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
Binder.getCallingUid(), mWifiStateMachineChannel);
if (networks != null) {
for (WifiConfiguration config : networks) {
- removeNetwork(config.networkId);
+ removeNetwork(config.networkId, packageName);
}
- saveConfiguration();
+ saveConfiguration(packageName);
}
}
}
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index 8e133133..2c8c0b76 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -982,6 +982,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(1024 * 1024);
mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(1024 * 1024);
@@ -1339,7 +1340,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
return false;
}
if (!mWifiConfigManager.enableNetwork(netId, true, uid)
- || !mWifiConfigManager.checkAndUpdateLastConnectUid(netId, uid)) {
+ || !mWifiConfigManager.updateLastConnectUid(netId, uid)) {
logi("connectToUserSelectNetwork Allowing uid " + uid
+ " with insufficient permissions to connect=" + netId);
} else {
@@ -2925,23 +2926,6 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
mWifiApState.set(wifiApState);
if (mVerboseLoggingEnabled) log("setWifiApState: " + syncGetWifiApStateByName());
-
- final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
- intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
- if (wifiApState == WifiManager.WIFI_AP_STATE_FAILED) {
- //only set reason number when softAP start failed
- intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
- }
-
- if (ifaceName == null) {
- loge("Updating wifiApState with a null iface name");
- }
- intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, ifaceName);
- intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mode);
-
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void setScanResults() {
@@ -5342,8 +5326,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
}
break;
case WifiManager.START_WPS:
+ mWifiMetrics.incrementWpsAttemptCount();
WpsInfo wpsInfo = (WpsInfo) message.obj;
if (wpsInfo == null) {
+ mWifiMetrics.incrementWpsStartFailureCount();
loge("Cannot start WPS with null WpsInfo object");
replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
break;
@@ -5389,6 +5375,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
transitionTo(mWpsRunningState);
} else {
+ mWifiMetrics.incrementWpsStartFailureCount();
loge("Failed to start WPS with config " + wpsInfo.toString());
replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
}
@@ -6814,8 +6801,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
int netId = loadResult.second;
if (success) {
message.arg1 = netId;
+ mWifiMetrics.incrementWpsSuccessCount();
replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
} else {
+ mWifiMetrics.incrementWpsSupplicantFailureCount();
replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
WifiManager.ERROR);
}
@@ -6825,6 +6814,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
transitionTo(mDisconnectedState);
break;
case WifiMonitor.WPS_OVERLAP_EVENT:
+ mWifiMetrics.incrementWpsOverlapFailureCount();
replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
WifiManager.WPS_OVERLAP_ERROR);
mSourceMessage.recycle();
@@ -6834,6 +6824,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
case WifiMonitor.WPS_FAIL_EVENT:
// Arg1 has the reason for the failure
if ((message.arg1 != WifiManager.ERROR) || (message.arg2 != 0)) {
+ mWifiMetrics.incrementWpsOtherConnectionFailureCount();
replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, message.arg1);
mSourceMessage.recycle();
mSourceMessage = null;
@@ -6845,6 +6836,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
}
break;
case WifiMonitor.WPS_TIMEOUT_EVENT:
+ mWifiMetrics.incrementWpsTimeoutFailureCount();
replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
WifiManager.WPS_TIMED_OUT);
mSourceMessage.recycle();
@@ -6855,6 +6847,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.IN_PROGRESS);
break;
case WifiManager.CANCEL_WPS:
+ mWifiMetrics.incrementWpsCancellationCount();
if (mWifiNative.cancelWps()) {
replyToMessage(message, WifiManager.CANCEL_WPS_SUCCEDED);
} else {
@@ -6984,10 +6977,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
if (apInterface == null) {
setWifiApState(WIFI_AP_STATE_FAILED,
WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
- /**
- * Transition to InitialState to reset the
- * driver/HAL back to the initial state.
- */
+ // Transition to InitialState to reset the driver/HAL back to the initial state.
transitionTo(mInitialState);
return;
}
@@ -6995,16 +6985,20 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
try {
mIfaceName = apInterface.getInterfaceName();
} catch (RemoteException e) {
- // Failed to get the interface name. The name will not be available for
- // the enabled broadcast, but since we had an error getting the name, we most likely
- // won't be able to fully start softap mode.
+ // Failed to get the interface name. This is not a good sign and we should report
+ // a failure and switch back to the initial state to reset the driver and HAL.
+ setWifiApState(WIFI_AP_STATE_FAILED,
+ WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
+ transitionTo(mInitialState);
+ return;
}
checkAndSetConnectivityInstance();
mSoftApManager = mWifiInjector.makeSoftApManager(mNwService,
new SoftApListener(),
apInterface,
- config.getWifiConfiguration());
+ mIfaceName,
+ config);
mSoftApManager.start();
mWifiStateTracker.updateState(WifiStateTracker.SOFT_AP);
}
diff --git a/com/android/server/wifi/WifiStateMachinePrime.java b/com/android/server/wifi/WifiStateMachinePrime.java
index cd1948f1..c49b6450 100644
--- a/com/android/server/wifi/WifiStateMachinePrime.java
+++ b/com/android/server/wifi/WifiStateMachinePrime.java
@@ -16,6 +16,7 @@
package com.android.server.wifi;
+import android.annotation.NonNull;
import android.net.wifi.IApInterface;
import android.net.wifi.IWificond;
import android.net.wifi.WifiConfiguration;
@@ -50,7 +51,7 @@ public class WifiStateMachinePrime {
private IWificond mWificond;
- private Queue<WifiConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
+ private Queue<SoftApModeConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
/* The base for wifi message types */
static final int BASE = Protocol.BASE_WIFI;
@@ -106,17 +107,13 @@ public class WifiStateMachinePrime {
/**
* Method to enable soft ap for wifi hotspot.
*
- * The WifiConfiguration is generally going to be null to indicate that the
- * currently saved config in WifiApConfigManager should be used. When the config is
- * not null, it will be saved in the WifiApConfigManager. This save is performed in the
- * constructor of SoftApManager.
+ * The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if
+ * the persisted config is to be used) and the target operating mode (ex,
+ * {@link WifiManager.IFACE_IP_MODE_TETHERED} {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}).
*
- * @param wifiConfig WifiConfiguration for the hostapd softap
+ * @param wifiConfig SoftApModeConfiguration for the hostapd softap
*/
- public void enterSoftAPMode(WifiConfiguration wifiConfig) {
- if (wifiConfig == null) {
- wifiConfig = new WifiConfiguration();
- }
+ public void enterSoftAPMode(@NonNull SoftApModeConfiguration wifiConfig) {
mApConfigQueue.offer(wifiConfig);
changeMode(ModeStateMachine.CMD_START_SOFT_AP_MODE);
}
@@ -270,6 +267,7 @@ public class WifiStateMachinePrime {
class SoftAPModeState extends State {
IApInterface mApInterface = null;
+ String mIfaceName = null;
@Override
public void enter() {
@@ -281,23 +279,18 @@ public class WifiStateMachinePrime {
// Continue with setup since we are changing modes
mApInterface = null;
- mWificond = mWifiInjector.makeWificond();
- if (mWificond == null) {
- Log.e(TAG, "Failed to get reference to wificond");
- writeApConfigDueToStartFailure();
- mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
- return;
- }
try {
+ mWificond = mWifiInjector.makeWificond();
mApInterface = mWificond.createApInterface(
mWifiInjector.getWifiNative().getInterfaceName());
- } catch (RemoteException e1) { }
-
- if (mApInterface == null) {
- Log.e(TAG, "Could not get IApInterface instance from wificond");
- writeApConfigDueToStartFailure();
- mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
+ mIfaceName = mApInterface.getInterfaceName();
+ } catch (RemoteException e) {
+ initializationFailed(
+ "Could not get IApInterface instance or its name from wificond");
+ return;
+ } catch (NullPointerException e) {
+ initializationFailed("wificond failure when initializing softap mode");
return;
}
mModeStateMachine.transitionTo(mSoftAPModeActiveState);
@@ -317,6 +310,8 @@ public class WifiStateMachinePrime {
// not in active state, nothing to stop.
break;
case CMD_START_AP_FAILURE:
+ // remove the saved config for the start attempt
+ mApConfigQueue.poll();
Log.e(TAG, "Failed to start SoftApMode. Wait for next mode command.");
break;
case CMD_AP_STOPPED:
@@ -337,12 +332,13 @@ public class WifiStateMachinePrime {
return mApInterface;
}
- private void writeApConfigDueToStartFailure() {
- WifiConfiguration config = mApConfigQueue.poll();
- if (config != null && config.SSID != null) {
- // Save valid configs for future calls.
- mWifiInjector.getWifiApConfigStore().setApConfiguration(config);
- }
+ protected String getInterfaceName() {
+ return mIfaceName;
+ }
+
+ private void initializationFailed(String message) {
+ Log.e(TAG, message);
+ mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
}
}
@@ -410,16 +406,17 @@ public class WifiStateMachinePrime {
@Override
public void enter() {
Log.d(TAG, "Entering SoftApModeActiveState");
- WifiConfiguration config = mApConfigQueue.poll();
+ SoftApModeConfiguration softApModeConfig = mApConfigQueue.poll();
+ WifiConfiguration config = softApModeConfig.getWifiConfiguration();
+ // TODO (b/67601382): add checks for valid softap configs
if (config != null && config.SSID != null) {
Log.d(TAG, "Passing config to SoftApManager! " + config);
} else {
config = null;
}
-
this.mActiveModeManager = mWifiInjector.makeSoftApManager(mNMService,
new SoftApListener(), ((SoftAPModeState) mSoftAPModeState).getInterface(),
- config);
+ ((SoftAPModeState) mSoftAPModeState).getInterfaceName(), softApModeConfig);
mActiveModeManager.start();
}
diff --git a/com/android/server/wifi/WifiVendorHal.java b/com/android/server/wifi/WifiVendorHal.java
index cec36a56..3f39e458 100644
--- a/com/android/server/wifi/WifiVendorHal.java
+++ b/com/android/server/wifi/WifiVendorHal.java
@@ -249,7 +249,8 @@ public class WifiVendorHal {
public boolean initialize(WifiNative.VendorHalDeathEventHandler handler) {
synchronized (sLock) {
mHalDeviceManager.initialize();
- mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks, mLooper);
+ mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks,
+ mHalEventHandler);
mDeathEventHandler = handler;
return true;
}
@@ -422,7 +423,6 @@ public class WifiVendorHal {
mIWifiRttController = null;
mDriverDescription = null;
mFirmwareDescription = null;
- mChannelsForBandSupport = null;
}
/**
@@ -1453,78 +1453,6 @@ public class WifiVendorHal {
}
/**
- * Query the list of valid frequencies for the provided band.
- * <p>
- * The result depends on the on the country code that has been set.
- *
- * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
- * @return frequencies vector of valid frequencies (MHz), or null for error.
- * @throws IllegalArgumentException if band is not recognized.
- */
- public int[] getChannelsForBand(int band) {
- enter("%").c(band).flush();
- class AnswerBox {
- public int[] value = null;
- }
- synchronized (sLock) {
- try {
- AnswerBox box = new AnswerBox();
- int hb = makeWifiBandFromFrameworkBand(band);
- if (mIWifiStaIface != null) {
- mIWifiStaIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
- if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
- mChannelsForBandSupport = false;
- }
- if (!ok(status)) return;
- mChannelsForBandSupport = true;
- box.value = intArrayFromArrayList(frequencies);
- });
- } else if (mIWifiApIface != null) {
- mIWifiApIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
- if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
- mChannelsForBandSupport = false;
- }
- if (!ok(status)) return;
- mChannelsForBandSupport = true;
- box.value = intArrayFromArrayList(frequencies);
- });
- }
- return box.value;
- } catch (RemoteException e) {
- handleRemoteException(e);
- return null;
- }
- }
- }
-
- private int[] intArrayFromArrayList(ArrayList<Integer> in) {
- int[] ans = new int[in.size()];
- int i = 0;
- for (Integer e : in) ans[i++] = e;
- return ans;
- }
-
- /**
- * This holder is null until we know whether or not there is frequency-for-band support.
- * <p>
- * Set as a side-effect of getChannelsForBand.
- */
- @VisibleForTesting
- Boolean mChannelsForBandSupport = null;
-
- /**
- * Indicates whether getChannelsForBand is supported.
- *
- * @return true if it is.
- */
- public boolean isGetChannelsForBandSupported() {
- if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
- getChannelsForBand(WifiBand.BAND_24GHZ);
- if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
- return false;
- }
-
- /**
* Get the APF (Android Packet Filter) capabilities of the device
*/
public ApfCapabilities getApfCapabilities() {
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index df4e785b..aa723d65 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -526,4 +526,38 @@ 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.
+ *
+ * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+ * The following bands are supported:
+ * WifiScanner.WIFI_BAND_24_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+ * @return frequencies vector of valid frequencies (MHz), or null for error.
+ * @throws IllegalArgumentException if band is not recognized.
+ */
+ public int [] getChannelsForBand(int band) {
+ if (mWificond == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return null;
+ }
+ try {
+ switch (band) {
+ case WifiScanner.WIFI_BAND_24_GHZ:
+ return mWificond.getAvailable2gChannels();
+ case WifiScanner.WIFI_BAND_5_GHZ:
+ return mWificond.getAvailable5gNonDFSChannels();
+ case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+ return mWificond.getAvailableDFSChannels();
+ default:
+ throw new IllegalArgumentException("unsupported band " + band);
+ }
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+ }
+ return null;
+ }
}
diff --git a/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index efeca655..82bc2c43 100644
--- a/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -122,6 +122,7 @@ public class WifiAwareDataPathStateManager {
sNetworkCapabilitiesFilter
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
sNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier());
diff --git a/com/android/server/wifi/aware/WifiAwareNativeManager.java b/com/android/server/wifi/aware/WifiAwareNativeManager.java
index e855d810..8659a775 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -20,6 +20,7 @@ import android.hardware.wifi.V1_0.IWifiNanIface;
import android.hardware.wifi.V1_0.IfaceType;
import android.hardware.wifi.V1_0.WifiStatus;
import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
@@ -41,6 +42,7 @@ public class WifiAwareNativeManager {
private WifiAwareStateManager mWifiAwareStateManager;
private HalDeviceManager mHalDeviceManager;
+ private Handler mHandler;
private WifiAwareNativeCallback mWifiAwareNativeCallback;
private IWifiNanIface mWifiNanIface = null;
private InterfaceDestroyedListener mInterfaceDestroyedListener =
@@ -56,7 +58,13 @@ public class WifiAwareNativeManager {
mWifiAwareNativeCallback = wifiAwareNativeCallback;
}
- public void start() {
+ /**
+ * Initialize the class - intended for late initialization.
+ *
+ * @param handler Handler on which to execute interface available callbacks.
+ */
+ public void start(Handler handler) {
+ mHandler = handler;
mHalDeviceManager.initialize();
mHalDeviceManager.registerStatusListener(
new HalDeviceManager.ManagerStatusListener() {
@@ -69,7 +77,7 @@ public class WifiAwareNativeManager {
// 1. no problem registering duplicates - only one will be called
// 2. will be called immediately if available
mHalDeviceManager.registerInterfaceAvailableForRequestListener(
- IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+ IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
} else {
awareIsDown();
}
@@ -77,7 +85,7 @@ public class WifiAwareNativeManager {
}, null);
if (mHalDeviceManager.isStarted()) {
mHalDeviceManager.registerInterfaceAvailableForRequestListener(
- IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+ IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
tryToGetAware();
}
}
@@ -104,7 +112,7 @@ public class WifiAwareNativeManager {
return;
}
IWifiNanIface iface = mHalDeviceManager.createNanIface(mInterfaceDestroyedListener,
- null);
+ mHandler);
if (iface == null) {
if (DBG) Log.d(TAG, "Was not able to obtain an IWifiNanIface");
} else {
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 31bdff8c..0efe7361 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -1625,7 +1625,7 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
waitForResponse = endDataPathLocal(mCurrentTransactionId, msg.arg2);
break;
case COMMAND_TYPE_DELAYED_INITIALIZATION:
- mWifiAwareNativeManager.start();
+ mWifiAwareNativeManager.start(getHandler());
waitForResponse = false;
break;
default:
diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index fa162531..da3da7c2 100644
--- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -529,7 +529,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub {
synchronized (mLock) {
mIWifiP2pIface = null;
}
- }, mP2pStateMachine.getHandler().getLooper());
+ }, mP2pStateMachine.getHandler());
}
return messenger;
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
index f731c195..fe1829f8 100644
--- a/com/android/server/wifi/rtt/RttNative.java
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -74,6 +74,15 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub {
}
}
+ /**
+ * Returns true if Wi-Fi is ready for RTT requests, false otherwise.
+ */
+ public boolean isReady() {
+ synchronized (mLock) {
+ return mIWifiRttController != null;
+ }
+ }
+
private void updateController() {
if (VDBG) Log.v(TAG, "updateController: mIWifiRttController=" + mIWifiRttController);
@@ -98,6 +107,12 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub {
} else {
mIWifiRttController = null;
}
+
+ if (mIWifiRttController == null) {
+ mRttService.disable();
+ } else {
+ mRttService.enable();
+ }
}
}
@@ -112,7 +127,7 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub {
public boolean rangeRequest(int cmdId, RangingRequest request) {
if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
synchronized (mLock) {
- if (mIWifiRttController == null) {
+ if (!isReady()) {
Log.e(TAG, "rangeRequest: RttController is null");
return false;
}
@@ -149,7 +164,7 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub {
public boolean rangeCancel(int cmdId, ArrayList<byte[]> macAddresses) {
if (VDBG) Log.v(TAG, "rangeCancel: cmdId=" + cmdId);
synchronized (mLock) {
- if (mIWifiRttController == null) {
+ if (!isReady()) {
Log.e(TAG, "rangeCancel: RttController is null");
return false;
}
@@ -223,12 +238,12 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub {
config.mustRequestLci = false;
config.mustRequestLcr = false;
config.burstDuration = 15;
- if (config.channel.centerFreq > 5000) {
+ config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
+ if (config.bw == RttBw.BW_80MHZ || config.bw == RttBw.BW_160MHZ) {
config.preamble = RttPreamble.VHT;
} else {
config.preamble = RttPreamble.HT;
}
- config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid configuration: " + e.getMessage());
continue;
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
index 35f76155..0790deed 100644
--- a/com/android/server/wifi/rtt/RttService.java
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -66,7 +66,6 @@ public class RttService extends SystemService {
Context.WIFI_AWARE_SERVICE);
RttNative rttNative = new RttNative(mImpl, halDeviceManager);
- rttNative.start();
mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil);
}
}
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
index 19210479..36caab74 100644
--- a/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -16,7 +16,10 @@
package com.android.server.wifi.rtt;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.wifi.V1_0.RttResult;
import android.hardware.wifi.V1_0.RttStatus;
@@ -29,12 +32,16 @@ import android.net.wifi.rtt.IWifiRttManager;
import android.net.wifi.rtt.RangingRequest;
import android.net.wifi.rtt.RangingResult;
import android.net.wifi.rtt.RangingResultCallback;
+import android.net.wifi.rtt.WifiRttManager;
import android.os.Binder;
import android.os.Handler;
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;
@@ -63,7 +70,9 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
private final Context mContext;
private IWifiAwareManager mAwareBinder;
+ private RttNative mRttNative;
private WifiPermissionsUtil mWifiPermissionsUtil;
+ private PowerManager mPowerManager;
private RttServiceSynchronized mRttServiceSynchronized;
@@ -91,8 +100,32 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative,
WifiPermissionsUtil wifiPermissionsUtil) {
mAwareBinder = awareBinder;
+ mRttNative = rttNative;
mWifiPermissionsUtil = wifiPermissionsUtil;
mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (VDBG) Log.v(TAG, "BroadcastReceiver: action=" + action);
+
+ if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+ if (mPowerManager.isDeviceIdleMode()) {
+ disable();
+ } else {
+ enable();
+ }
+ }
+ }
+ }, intentFilter);
+
+ mRttServiceSynchronized.mHandler.post(() -> {
+ rttNative.start();
+ });
}
/*
@@ -108,15 +141,50 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
}
/**
+ * Enable the API: broadcast notification
+ */
+ public void enable() {
+ if (VDBG) Log.v(TAG, "enable");
+ sendRttStateChangedBroadcast(true);
+ mRttServiceSynchronized.mHandler.post(() -> {
+ // queue should be empty at this point (but this call allows validation)
+ mRttServiceSynchronized.executeNextRangingRequestIfPossible(false);
+ });
+ }
+
+ /**
+ * Disable the API:
+ * - Clean-up (fail) pending requests
+ * - Broadcast notification
+ */
+ public void disable() {
+ if (VDBG) Log.v(TAG, "disable");
+ sendRttStateChangedBroadcast(false);
+ mRttServiceSynchronized.mHandler.post(() -> {
+ mRttServiceSynchronized.cleanUpOnDisable();
+ });
+ }
+
+ /**
+ * Binder interface API to indicate whether the API is currently available. This requires an
+ * immediate asynchronous response.
+ */
+ @Override
+ public boolean isAvailable() {
+ return mRttNative.isReady() && !mPowerManager.isDeviceIdleMode();
+ }
+
+ /**
* Binder interface API to start a ranging operation. Called on binder thread, operations needs
* to be posted to handler thread.
*/
@Override
- public void startRanging(IBinder binder, String callingPackage, RangingRequest request,
- IRttCallback callback) throws RemoteException {
+ public void startRanging(IBinder binder, String callingPackage, WorkSource workSource,
+ RangingRequest request, IRttCallback callback) throws RemoteException {
if (VDBG) {
Log.v(TAG, "startRanging: binder=" + binder + ", callingPackage=" + callingPackage
- + ", request=" + request + ", callback=" + callback);
+ + ", workSource=" + workSource + ", request=" + request + ", callback="
+ + callback);
}
// verify arguments
if (binder == null) {
@@ -130,12 +198,24 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
}
request.enforceValidity(mAwareBinder != null);
+ if (!isAvailable()) {
+ try {
+ callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+ } catch (RemoteException e) {
+ Log.e(TAG, "startRanging: disabled, callback failed -- " + e);
+ }
+ return;
+ }
+
final int uid = getMockableCallingUid();
- // permission check
+ // permission checks
enforceAccessPermission();
enforceChangePermission();
enforceLocationPermission(callingPackage, uid);
+ if (workSource != null) {
+ enforceLocationHardware();
+ }
// register for binder death
IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
@@ -145,7 +225,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
binder.unlinkToDeath(this, 0);
mRttServiceSynchronized.mHandler.post(() -> {
- mRttServiceSynchronized.cleanUpOnClientDeath(uid);
+ mRttServiceSynchronized.cleanUpClientRequests(uid, null);
});
}
};
@@ -156,9 +236,29 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
Log.e(TAG, "Error on linkToDeath - " + e);
}
+
+ mRttServiceSynchronized.mHandler.post(() -> {
+ WorkSource sourceToUse = workSource;
+ if (workSource == null || workSource.size() == 0 || workSource.get(0) == 0) {
+ sourceToUse = new WorkSource(uid);
+ }
+ mRttServiceSynchronized.queueRangingRequest(uid, sourceToUse, binder, dr,
+ callingPackage, request, callback);
+ });
+ }
+
+ @Override
+ public void cancelRanging(WorkSource workSource) throws RemoteException {
+ if (VDBG) Log.v(TAG, "cancelRanging: workSource=" + workSource);
+ enforceLocationHardware();
+
+ if (workSource == null || workSource.size() == 0 || workSource.get(0) == 0) {
+ Log.e(TAG, "cancelRanging: invalid work-source -- " + workSource);
+ return;
+ }
+
mRttServiceSynchronized.mHandler.post(() -> {
- mRttServiceSynchronized.queueRangingRequest(uid, binder, dr, callingPackage, request,
- callback);
+ mRttServiceSynchronized.cleanUpClientRequests(0, workSource);
});
}
@@ -186,6 +286,18 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
TAG);
}
+ private void enforceLocationHardware() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE,
+ TAG);
+ }
+
+ private void sendRttStateChangedBroadcast(boolean enabled) {
+ if (VDBG) Log.v(TAG, "sendRttStateChangedBroadcast: enabled=" + enabled);
+ final Intent intent = new Intent(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(
@@ -249,18 +361,65 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
mRttNative.rangeCancel(rri.cmdId, macAddresses);
}
- private void cleanUpOnClientDeath(int uid) {
+ private void cleanUpOnDisable() {
+ if (VDBG) Log.v(TAG, "RttServiceSynchronized.cleanUpOnDisable");
+ for (RttRequestInfo rri : mRttRequestQueue) {
+ try {
+ if (rri.dispatchedToNative) {
+ // may not be necessary in some cases (e.g. Wi-Fi disable may already clear
+ // up active RTT), but in other cases will be needed (doze disabling RTT
+ // but Wi-Fi still up). Doesn't hurt - worst case will fail.
+ cancelRanging(rri);
+ }
+ rri.callback.onRangingFailure(
+ RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
+ + e);
+ }
+ rri.binder.unlinkToDeath(rri.dr, 0);
+ }
+ mRttRequestQueue.clear();
+ mRangingTimeoutMessage.cancel();
+ }
+
+ /**
+ * Remove entries related to the specified client and cancel any dispatched to HAL
+ * requests. Expected to provide either the UID or the WorkSource (the other will be 0 or
+ * null respectively).
+ *
+ * A workSource specification will be cleared from the requested workSource and the request
+ * cancelled only if there are no remaining uids in the work-source.
+ */
+ private void cleanUpClientRequests(int uid, WorkSource workSource) {
if (VDBG) {
Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
- + ", mRttRequestQueue=" + mRttRequestQueue);
+ + ", workSource=" + workSource + ", mRttRequestQueue=" + mRttRequestQueue);
}
+ boolean dispatchedRequestAborted = false;
ListIterator<RttRequestInfo> it = mRttRequestQueue.listIterator();
while (it.hasNext()) {
RttRequestInfo rri = it.next();
- if (rri.uid == uid) {
+
+ boolean match = rri.uid == uid; // original UID will never be 0
+ if (rri.workSource != null && workSource != null) {
+ try {
+ rri.workSource.remove(workSource);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Invalid WorkSource specified in the start or cancel requests: "
+ + e);
+ }
+ if (rri.workSource.size() == 0) {
+ match = true;
+ }
+ }
+
+ if (match) {
if (!rri.dispatchedToNative) {
it.remove();
+ rri.binder.unlinkToDeath(rri.dr, 0);
} else {
+ dispatchedRequestAborted = true;
Log.d(TAG, "Client death - cancelling RTT operation in progress: cmdId="
+ rri.cmdId);
mRangingTimeoutMessage.cancel();
@@ -271,8 +430,13 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
if (VDBG) {
Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+ + ", dispatchedRequestAborted=" + dispatchedRequestAborted
+ ", after cleanup - mRttRequestQueue=" + mRttRequestQueue);
}
+
+ if (dispatchedRequestAborted) {
+ executeNextRangingRequestIfPossible(true);
+ }
}
private void timeoutRangingRequest() {
@@ -299,10 +463,12 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
executeNextRangingRequestIfPossible(true);
}
- private void queueRangingRequest(int uid, IBinder binder, IBinder.DeathRecipient dr,
- String callingPackage, RangingRequest request, IRttCallback callback) {
+ private void queueRangingRequest(int uid, WorkSource workSource, IBinder binder,
+ IBinder.DeathRecipient dr, String callingPackage, RangingRequest request,
+ IRttCallback callback) {
RttRequestInfo newRequest = new RttRequestInfo();
newRequest.uid = uid;
+ newRequest.workSource = workSource;
newRequest.binder = binder;
newRequest.dr = dr;
newRequest.callingPackage = callingPackage;
@@ -325,7 +491,8 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
Log.w(TAG, "executeNextRangingRequestIfPossible: pop requested - but empty "
+ "queue!? Ignoring pop.");
} else {
- mRttRequestQueue.remove(0);
+ RttRequestInfo topOfQueueRequest = mRttRequestQueue.remove(0);
+ topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
}
}
@@ -334,6 +501,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
return;
}
+ // if top of list is in progress then do nothing
RttRequestInfo nextRequest = mRttRequestQueue.get(0);
if (nextRequest.peerHandlesTranslated || nextRequest.dispatchedToNative) {
if (VDBG) {
@@ -351,6 +519,19 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
}
+ if (!isAvailable()) {
+ Log.d(TAG, "RttServiceSynchronized.startRanging: disabled");
+ try {
+ nextRequest.callback.onRangingFailure(
+ RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
+ + e);
+ executeNextRangingRequestIfPossible(true);
+ return;
+ }
+ }
+
if (processAwarePeerHandles(nextRequest)) {
if (VDBG) {
Log.v(TAG, "RttServiceSynchronized.startRanging: deferring due to PeerHandle "
@@ -506,10 +687,6 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
"RttServiceSynchronized.onRangingResults: callback exception -- " + e);
}
- // clean-up binder death listener: the callback for results is a onetime event - now
- // done with the binder.
- topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
-
executeNextRangingRequestIfPossible(true);
}
@@ -578,12 +755,14 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" mNextCommandId: " + mNextCommandId);
pw.println(" mRttRequestQueue: " + mRttRequestQueue);
+ pw.println(" mRangingTimeoutMessage: " + mRangingTimeoutMessage);
mRttNative.dump(fd, pw, args);
}
}
private static class RttRequestInfo {
public int uid;
+ public WorkSource workSource;
public IBinder binder;
public IBinder.DeathRecipient dr;
public String callingPackage;
@@ -596,10 +775,11 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
@Override
public String toString() {
- return new StringBuilder("RttRequestInfo: uid=").append(uid).append(", binder=").append(
- binder).append(", dr=").append(dr).append(", callingPackage=").append(
- callingPackage).append(", request=").append(request.toString()).append(
- ", callback=").append(callback).append(", cmdId=").append(cmdId).append(
+ return new StringBuilder("RttRequestInfo: uid=").append(uid).append(
+ ", workSource=").append(workSource).append(", binder=").append(binder).append(
+ ", dr=").append(dr).append(", callingPackage=").append(callingPackage).append(
+ ", request=").append(request.toString()).append(", callback=").append(
+ callback).append(", cmdId=").append(cmdId).append(
", peerHandlesTranslated=").append(peerHandlesTranslated).toString();
}
}
diff --git a/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 890f72e6..1478a99c 100644
--- a/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -46,7 +46,7 @@ public class HalWifiScannerImpl extends WifiScannerImpl implements Handler.Callb
public HalWifiScannerImpl(Context context, WifiNative wifiNative, WifiMonitor wifiMonitor,
Looper looper, Clock clock) {
mWifiNative = wifiNative;
- mChannelHelper = new HalChannelHelper(wifiNative);
+ mChannelHelper = new WificondChannelHelper(wifiNative);
mWificondScannerDelegate =
new WificondScannerImpl(context, wifiNative, wifiMonitor, mChannelHelper,
looper, clock);
diff --git a/com/android/server/wifi/scanner/NoBandChannelHelper.java b/com/android/server/wifi/scanner/NoBandChannelHelper.java
deleted file mode 100644
index b2eeada7..00000000
--- a/com/android/server/wifi/scanner/NoBandChannelHelper.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi.scanner;
-
-import android.net.wifi.WifiScanner;
-import android.util.ArraySet;
-
-import com.android.server.wifi.WifiNative;
-
-import java.util.Set;
-
-/**
- * ChannelHelper that offers channel manipulation utilities when the channels in a band are not
- * known. Operations performed may simplify any band to include all channels.
- */
-public class NoBandChannelHelper extends ChannelHelper {
-
- /**
- * These parameters are used to estimate the scan duration.
- * This is a guess at the number of channels the device supports for use when a ScanSettings
- * specifies a band instead of a list of channels.
- */
- private static final int ALL_BAND_CHANNEL_COUNT_ESTIMATE = 36;
-
- @Override
- public boolean settingsContainChannel(WifiScanner.ScanSettings settings, int channel) {
- if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
- for (int i = 0; i < settings.channels.length; ++i) {
- if (settings.channels[i].frequency == channel) {
- return true;
- }
- }
- return false;
- } else {
- return true;
- }
- }
-
- @Override
- public WifiScanner.ChannelSpec[] getAvailableScanChannels(int band) {
- return NO_CHANNELS; // not supported
- }
-
- @Override
- public int estimateScanDuration(WifiScanner.ScanSettings settings) {
- if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
- return settings.channels.length * SCAN_PERIOD_PER_CHANNEL_MS;
- } else {
- return ALL_BAND_CHANNEL_COUNT_ESTIMATE * SCAN_PERIOD_PER_CHANNEL_MS;
- }
- }
-
- /**
- * ChannelCollection that merges channels without knowing which channels are in each band. In
- * order to do this if any band is added or the maxChannels is exceeded then all channels will
- * be included.
- */
- public class NoBandChannelCollection extends ChannelCollection {
- private final ArraySet<Integer> mChannels = new ArraySet<Integer>();
- private boolean mAllChannels = false;
-
- @Override
- public void addChannel(int frequency) {
- mChannels.add(frequency);
- }
-
- @Override
- public void addBand(int band) {
- if (band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
- mAllChannels = true;
- }
- }
-
- @Override
- public boolean containsChannel(int channel) {
- return mAllChannels || mChannels.contains(channel);
- }
-
- @Override
- public boolean containsBand(int band) {
- if (band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
- return mAllChannels;
- }
- return false;
- }
-
- @Override
- public boolean partiallyContainsBand(int band) {
- // We don't need to partially collapse settings in wificond scanner because we
- // don't have any limitation on the number of channels that can be scanned. We also
- // don't currently keep track of bands very well in NoBandChannelHelper.
- return false;
- }
-
- @Override
- public boolean isEmpty() {
- return !mAllChannels && mChannels.isEmpty();
- }
-
- @Override
- public boolean isAllChannels() {
- return mAllChannels;
- }
-
- @Override
- public void clear() {
- mAllChannels = false;
- mChannels.clear();
- }
-
- @Override
- public Set<Integer> getMissingChannelsFromBand(int band) {
- // We don't need to partially collapse settings in wificond scanner because we
- // don't have any limitation on the number of channels that can be scanned. We also
- // don't currently keep track of bands very well in NoBandChannelHelper.
- return new ArraySet<Integer>();
- }
-
- @Override
- public Set<Integer> getContainingChannelsFromBand(int band) {
- // We don't need to partially collapse settings in wificond scanner because we
- // don't have any limitation on the number of channels that can be scanned. We also
- // don't currently keep track of bands very well in NoBandChannelHelper.
- return new ArraySet<Integer>();
- }
-
- @Override
- public Set<Integer> getChannelSet() {
- if (!isEmpty() && !mAllChannels) {
- return mChannels;
- } else {
- return new ArraySet<>();
- }
- }
-
- @Override
- public void fillBucketSettings(WifiNative.BucketSettings bucketSettings, int maxChannels) {
- if (mAllChannels || mChannels.size() > maxChannels) {
- bucketSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
- bucketSettings.num_channels = 0;
- bucketSettings.channels = null;
- } else {
- bucketSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
- bucketSettings.num_channels = mChannels.size();
- bucketSettings.channels = new WifiNative.ChannelSettings[mChannels.size()];
- for (int i = 0; i < mChannels.size(); ++i) {
- WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings();
- channelSettings.frequency = mChannels.valueAt(i);
- bucketSettings.channels[i] = channelSettings;
- }
- }
- }
-
- @Override
- public Set<Integer> getScanFreqs() {
- if (mAllChannels) {
- return null;
- } else {
- return new ArraySet<Integer>(mChannels);
- }
- }
- }
-
- @Override
- public ChannelCollection createChannelCollection() {
- return new NoBandChannelCollection();
- }
-}
diff --git a/com/android/server/wifi/scanner/WifiScannerImpl.java b/com/android/server/wifi/scanner/WifiScannerImpl.java
index dacb0074..75b24d88 100644
--- a/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -53,8 +53,8 @@ public abstract class WifiScannerImpl {
if (wifiNative.getBgScanCapabilities(new WifiNative.ScanCapabilities())) {
return new HalWifiScannerImpl(context, wifiNative, wifiMonitor, looper, clock);
} else {
- return new WificondScannerImpl(context, wifiNative, wifiMonitor, looper,
- clock);
+ return new WificondScannerImpl(context, wifiNative, wifiMonitor,
+ new WificondChannelHelper(wifiNative), looper, clock);
}
}
};
diff --git a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 02462f67..ed1bedfa 100644
--- a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -1336,7 +1336,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
* -Started State
* -Hw Pno Scan state
* -Single Scan state
- * -Sw Pno Scan state
*
* These are the main state transitions:
* 1. Start at |Default State|
@@ -1348,11 +1347,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
* contains IE (information elements). If yes, send the results to the client, else
* switch to |Single Scan state| and send the result to the client when the scan result
* is obtained.
- * b.1. Switch to |Sw Pno Scan state| when the device does not supports HW PNO
- * (This is for older devices which do not support HW PNO and for connected PNO on
- * devices which support wificond based PNO)
- * b.2. In |Sw Pno Scan state| send the result to the client when the background scan result
- * is obtained
*
* Note: PNO scans only work for a single client today. We don't have support in HW to support
* multiple requests at the same time, so will need non-trivial changes to support (if at all
@@ -1363,7 +1357,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
private final DefaultState mDefaultState = new DefaultState();
private final StartedState mStartedState = new StartedState();
private final HwPnoScanState mHwPnoScanState = new HwPnoScanState();
- private final SwPnoScanState mSwPnoScanState = new SwPnoScanState();
private final SingleScanState mSingleScanState = new SingleScanState();
private InternalClientInfo mInternalClientInfo;
@@ -1381,7 +1374,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
addState(mStartedState, mDefaultState);
addState(mHwPnoScanState, mStartedState);
addState(mSingleScanState, mHwPnoScanState);
- addState(mSwPnoScanState, mStartedState);
// CHECKSTYLE:ON IndentationCheck
setInitialState(mDefaultState);
@@ -1461,12 +1453,11 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
pnoParams.setDefusable(true);
PnoSettings pnoSettings =
pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
- // This message is handled after the transition to SwPnoScan/HwPnoScan state
- deferMessage(msg);
if (mScannerImpl.isHwPnoSupported(pnoSettings.isConnected)) {
+ deferMessage(msg);
transitionTo(mHwPnoScanState);
} else {
- transitionTo(mSwPnoScanState);
+ replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "not supported");
}
break;
case WifiScanner.CMD_STOP_PNO_SCAN:
@@ -1577,68 +1568,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
}
}
- class SwPnoScanState extends State {
- private final ArrayList<ScanResult> mSwPnoFullScanResults = new ArrayList<>();
-
- @Override
- public void enter() {
- if (DBG) localLog("SwPnoScanState");
- mSwPnoFullScanResults.clear();
- }
-
- @Override
- public void exit() {
- removeInternalClient();
- }
-
- @Override
- public boolean processMessage(Message msg) {
- ClientInfo ci = mClients.get(msg.replyTo);
- switch (msg.what) {
- case WifiScanner.CMD_START_PNO_SCAN:
- Bundle pnoParams = (Bundle) msg.obj;
- if (pnoParams == null) {
- replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
- return HANDLED;
- }
- pnoParams.setDefusable(true);
- PnoSettings pnoSettings =
- pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
- ScanSettings scanSettings =
- pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY);
- if (addSwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) {
- replySucceeded(msg);
- } else {
- replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
- transitionTo(mStartedState);
- }
- break;
- case WifiScanner.CMD_STOP_PNO_SCAN:
- removeSwPnoScanRequest(ci, msg.arg2);
- transitionTo(mStartedState);
- break;
- case WifiScanner.CMD_FULL_SCAN_RESULT:
- // Aggregate full scan results until we get the |CMD_SCAN_RESULT| message
- mSwPnoFullScanResults.add((ScanResult) msg.obj);
- break;
- case WifiScanner.CMD_SCAN_RESULT:
- ScanResult[] scanResults = mSwPnoFullScanResults.toArray(
- new ScanResult[mSwPnoFullScanResults.size()]);
- reportPnoNetworkFound(scanResults);
- mSwPnoFullScanResults.clear();
- break;
- case WifiScanner.CMD_OP_FAILED:
- sendPnoScanFailedToAllAndClear(
- WifiScanner.REASON_UNSPECIFIED, "background scan failed");
- transitionTo(mStartedState);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
private WifiNative.PnoSettings convertSettingsToPnoNative(ScanSettings scanSettings,
PnoSettings pnoSettings) {
WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings();
@@ -1731,34 +1660,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
}
}
- private boolean addSwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings,
- PnoSettings pnoSettings) {
- if (ci == null) {
- Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
- return false;
- }
- if (!mActivePnoScans.isEmpty()) {
- loge("Failing scan request because there is already an active scan");
- return false;
- }
- logScanRequest("addSwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
- addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
- // HW PNO is not supported, we need to revert to normal background scans and
- // report events after each scan and we need full scan results to get the IE information
- scanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
- | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
- addBackgroundScanRequest(scanSettings);
- return true;
- }
-
- private void removeSwPnoScanRequest(ClientInfo ci, int handler) {
- if (ci != null) {
- Pair<PnoSettings, ScanSettings> settings = removePnoScanRequest(ci, handler);
- logScanRequest("removeSwPnoScanRequest", ci, handler, null,
- settings.second, settings.first);
- }
- }
-
private void reportPnoNetworkFound(ScanResult[] results) {
WifiScanner.ParcelableScanResults parcelableScanResults =
new WifiScanner.ParcelableScanResults(results);
@@ -1781,15 +1682,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
mActivePnoScans.clear();
}
- private void addBackgroundScanRequest(ScanSettings settings) {
- if (DBG) localLog("Starting background scan");
- if (mInternalClientInfo != null) {
- mInternalClientInfo.sendRequestToClientHandler(
- WifiScanner.CMD_START_BACKGROUND_SCAN, settings,
- WifiStateMachine.WIFI_WORK_SOURCE);
- }
- }
-
private void addSingleScanRequest(ScanSettings settings) {
if (DBG) localLog("Starting single scan");
if (mInternalClientInfo != null) {
diff --git a/com/android/server/wifi/scanner/HalChannelHelper.java b/com/android/server/wifi/scanner/WificondChannelHelper.java
index e8b646d9..7cfeb72d 100644
--- a/com/android/server/wifi/scanner/HalChannelHelper.java
+++ b/com/android/server/wifi/scanner/WificondChannelHelper.java
@@ -22,15 +22,15 @@ import android.util.Log;
import com.android.server.wifi.WifiNative;
/**
- * KnownBandsChannelHelper that uses band to channel mappings retrieved from the HAL.
- * Also supporting updating the channel list from the HAL on demand.
+ * KnownBandsChannelHelper that uses band to channel mappings retrieved from wificond.
+ * Also supporting updating the channel list from the wificond on demand.
*/
-public class HalChannelHelper extends KnownBandsChannelHelper {
- private static final String TAG = "HalChannelHelper";
+public class WificondChannelHelper extends KnownBandsChannelHelper {
+ private static final String TAG = "WificondChannelHelper";
private final WifiNative mWifiNative;
- public HalChannelHelper(WifiNative wifiNative) {
+ public WificondChannelHelper(WifiNative wifiNative) {
mWifiNative = wifiNative;
final int[] emptyFreqList = new int[0];
setBandChannels(emptyFreqList, emptyFreqList, emptyFreqList);
@@ -39,11 +39,13 @@ public class HalChannelHelper extends KnownBandsChannelHelper {
@Override
public void updateChannels() {
- int[] channels24G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+ int[] channels24G =
+ mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
if (channels24G == null) Log.e(TAG, "Failed to get channels for 2.4GHz band");
int[] channels5G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
if (channels5G == null) Log.e(TAG, "Failed to get channels for 5GHz band");
- int[] channelsDfs = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
+ int[] channelsDfs =
+ mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
if (channelsDfs == null) Log.e(TAG, "Failed to get channels for 5GHz DFS only band");
if (channels24G == null || channels5G == null || channelsDfs == null) {
Log.e(TAG, "Failed to get all channels for band, not updating band channel lists");
diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java
index 10fc8e3e..72a7a27e 100644
--- a/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -128,12 +128,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
}
- public WificondScannerImpl(Context context, WifiNative wifiNative,
- WifiMonitor wifiMonitor, Looper looper, Clock clock) {
- // TODO get channel information from wificond.
- this(context, wifiNative, wifiMonitor, new NoBandChannelHelper(), looper, clock);
- }
-
@Override
public void cleanup() {
synchronized (mSettingsLock) {
diff --git a/com/android/server/wifi/util/ApConfigUtil.java b/com/android/server/wifi/util/ApConfigUtil.java
index 7bbbc036..b1a49480 100644
--- a/com/android/server/wifi/util/ApConfigUtil.java
+++ b/com/android/server/wifi/util/ApConfigUtil.java
@@ -133,15 +133,9 @@ public class ApConfigUtil {
config.apBand, allowed2GChannels,
wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ));
if (config.apChannel == -1) {
- if (wifiNative.isGetChannelsForBandSupported()) {
- /* We're not able to get channel when it is supported by HAL. */
- Log.e(TAG, "Failed to get available channel.");
- return ERROR_NO_CHANNEL;
- }
-
- /* Use the default for HAL without get channel support. */
- config.apBand = DEFAULT_AP_BAND;
- config.apChannel = DEFAULT_AP_CHANNEL;
+ /* We're not able to get channel from wificond. */
+ Log.e(TAG, "Failed to get available channel.");
+ return ERROR_NO_CHANNEL;
}
}
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index e873d32e..98db80ef 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -21,7 +21,6 @@ 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;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.os.Build.VERSION_CODES.O_MR1;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -1101,52 +1100,54 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
+ " from " + fromToken + " to " + this);
final long origId = Binder.clearCallingIdentity();
+ try {
+ // Transfer the starting window over to the new token.
+ startingData = fromToken.startingData;
+ startingSurface = fromToken.startingSurface;
+ startingDisplayed = fromToken.startingDisplayed;
+ fromToken.startingDisplayed = false;
+ startingWindow = tStartingWindow;
+ reportedVisible = fromToken.reportedVisible;
+ fromToken.startingData = null;
+ fromToken.startingSurface = null;
+ fromToken.startingWindow = null;
+ fromToken.startingMoved = true;
+ tStartingWindow.mToken = this;
+ tStartingWindow.mAppToken = this;
+
+ if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+ "Removing starting " + tStartingWindow + " from " + fromToken);
+ fromToken.removeChild(tStartingWindow);
+ fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+ fromToken.mHiddenSetFromTransferredStartingWindow = false;
+ addWindow(tStartingWindow);
+
+ // Propagate other interesting state between the tokens. If the old token is displayed,
+ // we should immediately force the new one to be displayed. If it is animating, we need
+ // to move that animation to the new one.
+ if (fromToken.allDrawn) {
+ allDrawn = true;
+ deferClearAllDrawn = fromToken.deferClearAllDrawn;
+ }
+ if (fromToken.firstWindowDrawn) {
+ firstWindowDrawn = true;
+ }
+ if (!fromToken.hidden) {
+ hidden = false;
+ hiddenRequested = false;
+ mHiddenSetFromTransferredStartingWindow = true;
+ }
+ setClientHidden(fromToken.mClientHidden);
+ fromToken.mAppAnimator.transferCurrentAnimation(
+ mAppAnimator, tStartingWindow.mWinAnimator);
- // Transfer the starting window over to the new token.
- startingData = fromToken.startingData;
- startingSurface = fromToken.startingSurface;
- startingDisplayed = fromToken.startingDisplayed;
- fromToken.startingDisplayed = false;
- startingWindow = tStartingWindow;
- reportedVisible = fromToken.reportedVisible;
- fromToken.startingData = null;
- fromToken.startingSurface = null;
- fromToken.startingWindow = null;
- fromToken.startingMoved = true;
- tStartingWindow.mToken = this;
- tStartingWindow.mAppToken = this;
-
- if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
- "Removing starting " + tStartingWindow + " from " + fromToken);
- fromToken.removeChild(tStartingWindow);
- fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
- fromToken.mHiddenSetFromTransferredStartingWindow = false;
- addWindow(tStartingWindow);
-
- // Propagate other interesting state between the tokens. If the old token is displayed,
- // we should immediately force the new one to be displayed. If it is animating, we need
- // to move that animation to the new one.
- if (fromToken.allDrawn) {
- allDrawn = true;
- deferClearAllDrawn = fromToken.deferClearAllDrawn;
- }
- if (fromToken.firstWindowDrawn) {
- firstWindowDrawn = true;
- }
- if (!fromToken.hidden) {
- hidden = false;
- hiddenRequested = false;
- mHiddenSetFromTransferredStartingWindow = true;
+ mService.updateFocusedWindowLocked(
+ UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
+ getDisplayContent().setLayoutNeeded();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- setClientHidden(fromToken.mClientHidden);
- fromToken.mAppAnimator.transferCurrentAnimation(
- mAppAnimator, tStartingWindow.mWinAnimator);
-
- mService.updateFocusedWindowLocked(
- UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
- getDisplayContent().setLayoutNeeded();
- mService.mWindowPlacerLocked.performSurfacePlacement();
- Binder.restoreCallingIdentity(origId);
return true;
} else if (fromToken.startingData != null) {
// The previous app was getting ready to show a
@@ -1201,15 +1202,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
*/
@Override
int getOrientation(int candidate) {
- // We do not allow non-fullscreen apps to influence orientation starting in O-MR1. While we
- // do throw an exception in {@link Activity#onCreate} and
- // {@link Activity#setRequestedOrientation}, we also ignore the orientation here so that
- // other calculations aren't affected.
- if (!fillsParent() && mTargetSdk >= O_MR1) {
- // Can't specify orientation if app doesn't fill parent.
- return SCREEN_ORIENTATION_UNSET;
- }
-
if (candidate == SCREEN_ORIENTATION_BEHIND) {
// Allow app to specify orientation regardless of its visibility state if the current
// candidate want us to use orientation behind. I.e. the visible app on-top of this one
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
index 401547e6..8fb2be8c 100644
--- a/com/android/server/wm/DimLayer.java
+++ b/com/android/server/wm/DimLayer.java
@@ -119,7 +119,7 @@ public class DimLayer {
} catch (Exception e) {
Slog.e(TAG_WM, "Exception creating Dim surface", e);
} finally {
- service.closeSurfaceTransaction();
+ service.closeSurfaceTransaction("DimLayer.constructSurface");
}
}
@@ -235,7 +235,7 @@ public class DimLayer {
} catch (RuntimeException e) {
Slog.w(TAG, "Failure setting size", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("DimLayer.setBounds");
}
}
}
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 4c6ab3f7..4d839d00 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -104,6 +105,7 @@ import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
import static com.android.server.wm.proto.DisplayProto.ABOVE_APP_WINDOWS;
import static com.android.server.wm.proto.DisplayProto.BELOW_APP_WINDOWS;
+import static com.android.server.wm.proto.DisplayProto.DISPLAY_FRAMES;
import static com.android.server.wm.proto.DisplayProto.DISPLAY_INFO;
import static com.android.server.wm.proto.DisplayProto.DOCKED_STACK_DIVIDER_CONTROLLER;
import static com.android.server.wm.proto.DisplayProto.DPI;
@@ -147,6 +149,7 @@ import android.view.WindowManagerPolicy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.view.IInputMethodClient;
+import android.view.DisplayFrames;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -222,6 +225,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Display mDisplay;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ DisplayFrames mDisplayFrames;
+
/**
* For default display it contains real metrics, empty for others.
* @see WindowManagerService#createWatermarkInTransaction()
@@ -285,7 +290,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
private boolean mLastWallpaperVisible = false;
private Rect mBaseDisplayRect = new Rect();
- private Rect mContentRect = new Rect();
// Accessed directly by all users.
private boolean mLayoutNeeded;
@@ -545,7 +549,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
w.mLayoutNeeded = false;
w.prelayout();
final boolean firstLayout = !w.isLaidOut();
- mService.mPolicy.layoutWindowLw(w, null);
+ mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames);
w.mLayoutSeq = mService.mLayoutSeq;
// If this is the first layout, we need to initialize the last inset values as
@@ -586,7 +590,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
w.mLayoutNeeded = false;
w.prelayout();
- mService.mPolicy.layoutWindowLw(w, w.getParentWindow());
+ mService.mPolicy.layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
w.mLayoutSeq = mService.mLayoutSeq;
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ " mContainingFrame=" + w.mContainingFrame
@@ -758,6 +762,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
display.getMetrics(mDisplayMetrics);
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mService = service;
+ mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo);
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service, this);
mPinnedStackControllerLocked = new PinnedStackController(service, this);
@@ -1083,7 +1088,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
} finally {
if (!inTransaction) {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setRotationUnchecked");
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
}
@@ -1129,6 +1134,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return true;
}
+ void configureDisplayPolicy() {
+ mService.mPolicy.setInitialDisplaySize(getDisplay(),
+ mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
+
+ mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+ }
+
/**
* Update {@link #mDisplayInfo} and other internal variables when display is rotated or config
* changed.
@@ -1452,17 +1464,17 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
/**
* @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise.
*/
- TaskStack getSplitScreenPrimaryStackStack() {
- TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack();
+ TaskStack getSplitScreenPrimaryStack() {
+ TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStack();
return (stack != null && stack.isVisible()) ? stack : null;
}
/**
- * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently
+ * Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently
* not visible.
*/
- TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() {
- return mTaskStackContainers.getSplitScreenPrimaryStackStack();
+ TaskStack getSplitScreenPrimaryStackIgnoringVisibility() {
+ return mTaskStackContainers.getSplitScreenPrimaryStack();
}
TaskStack getPinnedStack() {
@@ -1477,7 +1489,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* Returns the topmost stack on the display that is compatible with the input windowing mode.
* Null is no compatible stack on the display.
*/
- TaskStack getStack(int windowingMode) {
+ TaskStack getTopStackInWindowingMode(int windowingMode) {
return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
}
@@ -1744,7 +1756,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
void getContentRect(Rect out) {
- out.set(mContentRect);
+ out.set(mDisplayFrames.mContent);
}
TaskStack createStack(int stackId, boolean onTop, StackWindowController controller) {
@@ -1753,10 +1765,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
final TaskStack stack = new TaskStack(mService, stackId, controller);
mTaskStackContainers.addStackToDisplay(stack, onTop);
-
- if (stack.inSplitScreenPrimaryWindowingMode()) {
- mDividerControllerLocked.notifyDockedStackExistsChanged(true);
- }
return stack;
}
@@ -1847,8 +1855,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mTmpRect2.setEmpty();
for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
- stack.setTouchExcludeRegion(
- focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
+ stack.setTouchExcludeRegion(focusedTask, delta, mTouchExcludeRegion,
+ mDisplayFrames.mContent, mTmpRect2);
}
// If we removed the focused task above, add it back and only leave its
// outside touch area in the exclusion. TapDectector is not interested in
@@ -1877,7 +1885,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
}
// TODO(multi-display): Support docked stacks on secondary displays.
- if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) {
+ if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStack() != null) {
mDividerControllerLocked.getTouchRegion(mTmpRect);
mTmpRegion.set(mTmpRect);
mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2025,7 +2033,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock();
- final int imeHeight = mService.mPolicy.getInputMethodWindowVisibleHeightLw();
+ final int imeHeight = mDisplayFrames.getInputMethodWindowVisibleHeight();
final boolean imeHeightChanged = imeVisible &&
imeHeight != mDividerControllerLocked.getImeHeightAdjustedFor();
@@ -2167,6 +2175,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (screenRotationAnimation != null) {
screenRotationAnimation.writeToProto(proto, SCREEN_ROTATION_ANIMATION);
}
+ mDisplayFrames.writeToProto(proto, DISPLAY_FRAMES);
proto.end(token);
}
@@ -2232,7 +2241,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (pinnedStack != null) {
pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
}
- final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack();
+ final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
if (splitScreenPrimaryStack != null) {
pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
}
@@ -2246,6 +2255,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
pw.println(subPrefix
+ "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
}
+
+ pw.println();
+ mDisplayFrames.dump(prefix, pw);
}
@Override
@@ -2259,7 +2271,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
/** Returns true if the stack in the windowing mode is visible. */
boolean isStackVisible(int windowingMode) {
- final TaskStack stack = getStack(windowingMode);
+ final TaskStack stack = getTopStackInWindowingMode(windowingMode);
return stack != null && stack.isVisible();
}
@@ -2871,22 +2883,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
final int dw = mDisplayInfo.logicalWidth;
final int dh = mDisplayInfo.logicalHeight;
-
if (DEBUG_LAYOUT) {
Slog.v(TAG, "-------------------------------------");
Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
}
- mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation,
- getConfiguration().uiMode);
+ mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+ // TODO: Not sure if we really need to set the rotation here since we are updating from the
+ // display info above...
+ mDisplayFrames.mRotation = mRotation;
+ mService.mPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
if (isDefaultDisplay) {
// Not needed on non-default displays.
mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
mService.mScreenRect.set(0, 0, dw, dh);
}
- mService.mPolicy.getContentRectLw(mContentRect);
-
int seq = mService.mLayoutSeq + 1;
if (seq < 0) seq = 0;
mService.mLayoutSeq = seq;
@@ -2916,7 +2928,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
}
- mService.mPolicy.finishLayoutLw();
mService.mH.sendEmptyMessage(UPDATE_DOCKED_STACK_DIVIDER);
}
@@ -3377,6 +3388,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
final TaskStack stack = mTaskStackContainers.getChildAt(i);
+ if (activityType == ACTIVITY_TYPE_UNDEFINED
+ && windowingMode == stack.getWindowingMode()) {
+ // Passing in undefined type means we want to match the topmost stack with the
+ // windowing mode.
+ return stack;
+ }
if (stack.isCompatible(windowingMode, activityType)) {
return stack;
}
@@ -3401,7 +3418,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return mPinnedStack;
}
- TaskStack getSplitScreenPrimaryStackStack() {
+ TaskStack getSplitScreenPrimaryStack() {
return mSplitScreenPrimaryStack;
}
@@ -3447,6 +3464,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
+ " already exist on display=" + this + " stack=" + stack);
}
mSplitScreenPrimaryStack = stack;
+ mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
}
@@ -3457,6 +3475,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mPinnedStack = null;
} else if (stack == mSplitScreenPrimaryStack) {
mSplitScreenPrimaryStack = null;
+ // Re-set the split-screen create mode whenever the split-screen stack is removed.
+ mService.setDockedStackCreateStateLocked(
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+ mDividerControllerLocked.notifyDockedStackExistsChanged(false);
}
}
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 5ce4d46c..d79ba897 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -23,6 +23,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
@@ -116,6 +117,7 @@ public class DockedStackDividerController implements DimLayerUser {
private final DimLayer mDimLayer;
private boolean mMinimizedDock;
+ private int mOriginalDockedSide = DOCKED_INVALID;
private boolean mAnimatingForMinimizedDockedStack;
private boolean mAnimationStarted;
private long mAnimationStartTime;
@@ -321,7 +323,7 @@ public class DockedStackDividerController implements DimLayerUser {
if (mWindow == null) {
return;
}
- TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
// If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
final boolean visible = stack != null;
@@ -361,7 +363,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
void positionDockedStackedDivider(Rect frame) {
- TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack();
+ TaskStack stack = mDisplayContent.getSplitScreenPrimaryStack();
if (stack == null) {
// Unfortunately we might end up with still having a divider, even though the underlying
// stack was already removed. This is because we are on AM thread and the removal of the
@@ -408,6 +410,31 @@ public class DockedStackDividerController implements DimLayerUser {
mDockedStackListeners.finishBroadcast();
}
+ /**
+ * Checks if the primary stack is allowed to dock to a specific side based on its original dock
+ * side.
+ *
+ * @param dockSide the side to see if it is valid
+ * @return true if the side provided is valid
+ */
+ boolean canPrimaryStackDockTo(int dockSide) {
+ if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+ // Side is the same as original side
+ if (dockSide == mOriginalDockedSide) {
+ return true;
+ }
+ // Special rule that the top in portrait is always valid
+ if (dockSide == DOCKED_TOP) {
+ return true;
+ }
+ // Only if original docked side was top in portrait will allow left side for landscape
+ if (dockSide == DOCKED_LEFT && mOriginalDockedSide == DOCKED_TOP) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void notifyDockedStackExistsChanged(boolean exists) {
// TODO(multi-display): Perform all actions only for current display.
final int size = mDockedStackListeners.beginBroadcast();
@@ -430,8 +457,11 @@ public class DockedStackDividerController implements DimLayerUser {
inputMethodManagerInternal.hideCurrentInputMethod();
mImeHideRequested = true;
}
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+ mOriginalDockedSide = stack.getDockSide();
return;
}
+ mOriginalDockedSide = DOCKED_INVALID;
setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
}
@@ -458,7 +488,7 @@ public class DockedStackDividerController implements DimLayerUser {
long animDuration = 0;
if (animate) {
final TaskStack stack =
- mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
final long transitionDuration = isAnimationMaximizing()
? mService.mAppTransition.getLastClipRevealTransitionDuration()
: DEFAULT_APP_TRANSITION_DURATION;
@@ -513,7 +543,7 @@ public class DockedStackDividerController implements DimLayerUser {
mDockedStackListeners.register(listener);
notifyDockedDividerVisibilityChanged(wasVisible());
notifyDockedStackExistsChanged(
- mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null);
+ mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null);
notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
isHomeStackResizable());
notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -529,9 +559,9 @@ public class DockedStackDividerController implements DimLayerUser {
void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
// TODO: Maybe only allow split-screen windowing modes?
final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
- ? mDisplayContent.getStack(targetWindowingMode)
+ ? mDisplayContent.getTopStackInWindowingMode(targetWindowingMode)
: null;
- final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack();
+ final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
if (visibleAndValid) {
stack.getDimBounds(mTmpRect);
@@ -543,7 +573,7 @@ public class DockedStackDividerController implements DimLayerUser {
mDimLayer.setBounds(mTmpRect);
mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setResizeDimLayer");
}
}
mLastDimLayerRect.set(mTmpRect);
@@ -558,7 +588,7 @@ public class DockedStackDividerController implements DimLayerUser {
mService.openSurfaceTransaction();
mDimLayer.hide();
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setResizeDimLayer");
}
}
mLastDimLayerAlpha = 0f;
@@ -616,7 +646,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
private void checkMinimizeChanged(boolean animate) {
- if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) {
+ if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) {
return;
}
final TaskStack homeStack = mDisplayContent.getHomeStack();
@@ -633,11 +663,15 @@ public class DockedStackDividerController implements DimLayerUser {
if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
return;
}
- final TaskStack fullscreenStack = mDisplayContent.getStack(
+ final TaskStack topSecondaryStack = mDisplayContent.getTopStackInWindowingMode(
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
- final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible();
- setMinimizedDockedStack(homeVisible && !homeBehind, animate);
+ boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
+ if (homeVisible && topSecondaryStack != null) {
+ // Home should only be considered visible if it is greater or equal to the top secondary
+ // stack in terms of z-order.
+ homeVisible = homeStack.compareTo(topSecondaryStack) >= 0;
+ }
+ setMinimizedDockedStack(homeVisible, animate);
}
private boolean isWithinDisplay(Task task) {
@@ -778,7 +812,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
private boolean setMinimizedDockedStack(boolean minimized) {
- final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
}
@@ -829,7 +863,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
private boolean animateForMinimizedDockedStack(long now) {
- final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
if (!mAnimationStarted) {
mAnimationStarted = true;
mAnimationStartTime = now;
diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java
index a3c61670..4567e101 100644
--- a/com/android/server/wm/DragDropController.java
+++ b/com/android/server/wm/DragDropController.java
@@ -21,10 +21,13 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.NonNull;
import android.content.ClipData;
import android.graphics.PixelFormat;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.util.Slog;
import android.view.Display;
@@ -33,7 +36,9 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
-import com.android.server.wm.WindowManagerService.H;
+import android.view.WindowManagerInternal.IDragDropCallback;
+import com.android.internal.util.Preconditions;
+import com.android.server.input.InputWindowHandle;
/**
* Managing drag and drop operations initiated by View#startDragAndDrop.
@@ -41,13 +46,88 @@ import com.android.server.wm.WindowManagerService.H;
class DragDropController {
private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
private static final long DRAG_TIMEOUT_MS = 5000;
- DragState mDragState;
+
+ // Messages for Handler.
+ private static final int MSG_DRAG_START_TIMEOUT = 0;
+ static final int MSG_DRAG_END_TIMEOUT = 1;
+ static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
+ static final int MSG_ANIMATION_END = 3;
+
+ /**
+ * Drag state per operation.
+ * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of
+ * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this.
+ * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
+ * itself, thus the variable can be null after calling DragState's methods.
+ */
+ private DragState mDragState;
+
+ private WindowManagerService mService;
+ private final Handler mHandler;
+
+ /**
+ * Lock to preserve the order of state updates.
+ * The lock is used to process drag and drop state updates in order without having the window
+ * manager lock.
+ *
+ * Suppose DragDropController invokes a callback method A, then processes the following update
+ * A'. Same for a callback method B and the following update B'. The callback wants
+ * DragDropController to processes the updates in the order of A' then B'.
+ *
+ * Without mWriteLock: the following race can happen.
+ *
+ * 1. Thread a calls A.
+ * 2. Thread b calls B.
+ * 3. Thread b acquires the window manager lock
+ * 4. thread b processes the update B'
+ * 5. Thread a acquires the window manager lock
+ * 6. thread a processes the update A'
+ *
+ * With mWriteLock we can ensure the order of A' and B'
+ *
+ * 1. Thread a acquire mWriteLock
+ * 2. Thread a calls A
+ * 3. Thread a acquire the window manager lock
+ * 4. Thread a processes A'
+ * 5. Thread b acquire mWriteLock
+ * 6. Thread b calls B
+ * 7. Thread b acquire the window manager lock
+ * 8. Thread b processes B'
+ *
+ * Don't acquire the lock while holding the window manager lock, otherwise it causes a deadlock.
+ */
+ private final Object mWriteLock = new Object();
+
+ /**
+ * Callback which is used to sync drag state with the vendor-specific code.
+ */
+ @NonNull private IDragDropCallback mCallback = new IDragDropCallback() {};
boolean dragDropActiveLocked() {
return mDragState != null;
}
- IBinder prepareDrag(WindowManagerService service, SurfaceSession session, int callerPid,
+ InputWindowHandle getInputWindowHandleLocked() {
+ return mDragState.getInputWindowHandle();
+ }
+
+ void registerCallback(IDragDropCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mWriteLock) {
+ mCallback = callback;
+ }
+ }
+
+ DragDropController(WindowManagerService service, Looper looper) {
+ mService = service;
+ mHandler = new DragHandler(service, looper);
+ }
+
+ void sendDragStartedIfNeededLocked(WindowState window) {
+ mDragState.sendDragStartedIfNeededLocked(window);
+ }
+
+ IBinder prepareDrag(SurfaceSession session, int callerPid,
int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
@@ -55,188 +135,223 @@ class DragDropController {
+ " asbinder=" + window.asBinder());
}
- IBinder token = null;
-
- synchronized (service.mWindowMap) {
- if (dragDropActiveLocked()) {
- Slog.w(TAG_WM, "Drag already in progress");
- return null;
- }
+ synchronized (mWriteLock) {
+ synchronized (mService.mWindowMap) {
+ if (dragDropActiveLocked()) {
+ Slog.w(TAG_WM, "Drag already in progress");
+ return null;
+ }
- // TODO(multi-display): support other displays
- final DisplayContent displayContent =
- service.getDefaultDisplayContentLocked();
- final Display display = displayContent.getDisplay();
-
- final SurfaceControl surface = new SurfaceControl.Builder(session)
- .setName("drag surface")
- .setSize(width, height)
- .setFormat(PixelFormat.TRANSLUCENT)
- .build();
- surface.setLayerStack(display.getLayerStack());
- float alpha = 1;
- if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
- alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
+ // TODO(multi-display): support other displays
+ final DisplayContent displayContent =
+ mService.getDefaultDisplayContentLocked();
+ final Display display = displayContent.getDisplay();
+
+ final SurfaceControl surface = new SurfaceControl.Builder(session)
+ .setName("drag surface")
+ .setSize(width, height)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
+ surface.setLayerStack(display.getLayerStack());
+ float alpha = 1;
+ if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
+ alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
+ }
+ surface.setAlpha(alpha);
+
+ if (SHOW_TRANSACTIONS)
+ Slog.i(TAG_WM, " DRAG " + surface + ": CREATE");
+ outSurface.copyFrom(surface);
+ final IBinder winBinder = window.asBinder();
+ IBinder token = new Binder();
+ mDragState = new DragState(mService, token, surface, flags, winBinder);
+ mDragState.mPid = callerPid;
+ mDragState.mUid = callerUid;
+ mDragState.mOriginalAlpha = alpha;
+ token = mDragState.mToken = new Binder();
+
+ // 5 second timeout for this window to actually begin the drag
+ sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
+ return token;
}
- surface.setAlpha(alpha);
-
- if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " + surface + ": CREATE");
- outSurface.copyFrom(surface);
- final IBinder winBinder = window.asBinder();
- token = new Binder();
- mDragState = new DragState(service, token, surface, flags, winBinder);
- mDragState.mPid = callerPid;
- mDragState.mUid = callerUid;
- mDragState.mOriginalAlpha = alpha;
- token = mDragState.mToken = new Binder();
-
- // 5 second timeout for this window to actually begin the drag
- service.mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
- Message msg = service.mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
- service.mH.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
}
-
- return token;
}
- boolean performDrag(WindowManagerService service, IWindow window, IBinder dragToken,
+ boolean performDrag(IWindow window, IBinder dragToken,
int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
ClipData data) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
}
- synchronized (service.mWindowMap) {
- if (mDragState == null) {
- Slog.w(TAG_WM, "No drag prepared");
- throw new IllegalStateException("performDrag() without prepareDrag()");
+ synchronized (mWriteLock) {
+ if (!mCallback.performDrag(window, dragToken, touchSource, touchX, touchY, thumbCenterX,
+ thumbCenterY, data)) {
+ return false;
}
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ Slog.w(TAG_WM, "No drag prepared");
+ throw new IllegalStateException("performDrag() without prepareDrag()");
+ }
- if (dragToken != mDragState.mToken) {
- Slog.w(TAG_WM, "Performing mismatched drag");
- throw new IllegalStateException("performDrag() does not match prepareDrag()");
- }
+ if (dragToken != mDragState.mToken) {
+ Slog.w(TAG_WM, "Performing mismatched drag");
+ throw new IllegalStateException("performDrag() does not match prepareDrag()");
+ }
- final WindowState callingWin = service.windowForClientLocked(null, window, false);
- if (callingWin == null) {
- Slog.w(TAG_WM, "Bad requesting window " + window);
- return false; // !!! TODO: throw here?
- }
+ final WindowState callingWin = mService.windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG_WM, "Bad requesting window " + window);
+ return false; // !!! TODO: throw here?
+ }
- // !!! TODO: if input is not still focused on the initiating window, fail
- // the drag initiation (e.g. an alarm window popped up just as the application
- // called performDrag()
+ // !!! TODO: if input is not still focused on the initiating window, fail
+ // the drag initiation (e.g. an alarm window popped up just as the application
+ // called performDrag()
- service.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+ mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
- // !!! TODO: extract the current touch (x, y) in screen coordinates. That
- // will let us eliminate the (touchX,touchY) parameters from the API.
+ // !!! TODO: extract the current touch (x, y) in screen coordinates. That
+ // will let us eliminate the (touchX,touchY) parameters from the API.
- // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
- // the actual drag event dispatch stuff in the dragstate
+ // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
+ // the actual drag event dispatch stuff in the dragstate
- final DisplayContent displayContent = callingWin.getDisplayContent();
- if (displayContent == null) {
- return false;
- }
- Display display = displayContent.getDisplay();
- mDragState.register(display);
- if (!service.mInputManager.transferTouchFocus(callingWin.mInputChannel,
- mDragState.getInputChannel())) {
- Slog.e(TAG_WM, "Unable to transfer touch focus");
- mDragState.unregister();
- mDragState.reset();
- mDragState = null;
- return false;
- }
+ final DisplayContent displayContent = callingWin.getDisplayContent();
+ if (displayContent == null) {
+ return false;
+ }
+ Display display = displayContent.getDisplay();
+ mDragState.register(display);
+ if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
+ mDragState.getInputChannel())) {
+ Slog.e(TAG_WM, "Unable to transfer touch focus");
+ mDragState.closeLocked();
+ return false;
+ }
- mDragState.mDisplayContent = displayContent;
- mDragState.mData = data;
- mDragState.broadcastDragStartedLw(touchX, touchY);
- mDragState.overridePointerIconLw(touchSource);
-
- // remember the thumb offsets for later
- mDragState.mThumbOffsetX = thumbCenterX;
- mDragState.mThumbOffsetY = thumbCenterY;
-
- // Make the surface visible at the proper location
- final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, ">>> OPEN TRANSACTION performDrag");
- service.openSurfaceTransaction();
- try {
- surfaceControl.setPosition(touchX - thumbCenterX,
- touchY - thumbCenterY);
- surfaceControl.setLayer(mDragState.getDragLayerLw());
- surfaceControl.setLayerStack(display.getLayerStack());
- surfaceControl.show();
- } finally {
- service.closeSurfaceTransaction();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, "<<< CLOSE TRANSACTION performDrag");
- }
+ mDragState.mDisplayContent = displayContent;
+ mDragState.mData = data;
+ mDragState.broadcastDragStartedLocked(touchX, touchY);
+ mDragState.overridePointerIconLocked(touchSource);
+
+ // remember the thumb offsets for later
+ mDragState.mThumbOffsetX = thumbCenterX;
+ mDragState.mThumbOffsetY = thumbCenterY;
+
+ // Make the surface visible at the proper location
+ final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
+ mService.openSurfaceTransaction();
+ try {
+ surfaceControl.setPosition(touchX - thumbCenterX,
+ touchY - thumbCenterY);
+ surfaceControl.setLayer(mDragState.getDragLayerLocked());
+ surfaceControl.setLayerStack(display.getLayerStack());
+ surfaceControl.show();
+ } finally {
+ mService.closeSurfaceTransaction("performDrag");
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+ }
+ }
- mDragState.notifyLocationLw(touchX, touchY);
+ mDragState.notifyLocationLocked(touchX, touchY);
+ }
}
return true; // success!
}
- void reportDropResult(WindowManagerService service, IWindow window, boolean consumed) {
+ void reportDropResult(IWindow window, boolean consumed) {
IBinder token = window.asBinder();
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
}
- synchronized (service.mWindowMap) {
- if (mDragState == null) {
- // Most likely the drop recipient ANRed and we ended the drag
- // out from under it. Log the issue and move on.
- Slog.w(TAG_WM, "Drop result given but no drag in progress");
- return;
- }
+ synchronized (mWriteLock) {
+ mCallback.reportDropResult(window, consumed);
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ // Most likely the drop recipient ANRed and we ended the drag
+ // out from under it. Log the issue and move on.
+ Slog.w(TAG_WM, "Drop result given but no drag in progress");
+ return;
+ }
- if (mDragState.mToken != token) {
- // We're in a drag, but the wrong window has responded.
- Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
- throw new IllegalStateException("reportDropResult() by non-recipient");
- }
+ if (mDragState.mToken != token) {
+ // We're in a drag, but the wrong window has responded.
+ Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
+ throw new IllegalStateException("reportDropResult() by non-recipient");
+ }
- // The right window has responded, even if it's no longer around,
- // so be sure to halt the timeout even if the later WindowState
- // lookup fails.
- service.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
- WindowState callingWin = service.windowForClientLocked(null, window, false);
- if (callingWin == null) {
- Slog.w(TAG_WM, "Bad result-reporting window " + window);
- return; // !!! TODO: throw here?
- }
+ // The right window has responded, even if it's no longer around,
+ // so be sure to halt the timeout even if the later WindowState
+ // lookup fails.
+ mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+ WindowState callingWin = mService.windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG_WM, "Bad result-reporting window " + window);
+ return; // !!! TODO: throw here?
+ }
- mDragState.mDragResult = consumed;
- mDragState.endDragLw();
+
+ mDragState.mDragResult = consumed;
+ mDragState.endDragLocked();
+ }
}
}
- void cancelDragAndDrop(WindowManagerService service, IBinder dragToken) {
+ void cancelDragAndDrop(IBinder dragToken) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "cancelDragAndDrop");
}
- synchronized (service.mWindowMap) {
- if (mDragState == null) {
- Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
- throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
- }
+ synchronized (mWriteLock) {
+ mCallback.cancelDragAndDrop(dragToken);
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
+ throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+ }
+
+ if (mDragState.mToken != dragToken) {
+ Slog.w(TAG_WM,
+ "cancelDragAndDrop() does not match prepareDrag()");
+ throw new IllegalStateException(
+ "cancelDragAndDrop() does not match prepareDrag()");
+ }
- if (mDragState.mToken != dragToken) {
- Slog.w(TAG_WM,
- "cancelDragAndDrop() does not match prepareDrag()");
- throw new IllegalStateException(
- "cancelDragAndDrop() does not match prepareDrag()");
+ mDragState.mDragResult = false;
+ mDragState.cancelDragLocked();
}
+ }
+ }
+
+ /**
+ * Handles motion events.
+ * @param keepHandling Whether if the drag operation is continuing or this is the last motion
+ * event.
+ * @param newX X coordinate value in dp in the screen coordinate
+ * @param newY Y coordinate value in dp in the screen coordinate
+ */
+ void handleMotionEvent(boolean keepHandling, float newX, float newY) {
+ synchronized (mWriteLock) {
+ synchronized (mService.mWindowMap) {
+ if (!dragDropActiveLocked()) {
+ // The drag has ended but the clean-up message has not been processed by
+ // window manager. Drop events that occur after this until window manager
+ // has a chance to clean-up the input handle.
+ return;
+ }
- mDragState.mDragResult = false;
- mDragState.cancelDragLw();
+ if (keepHandling) {
+ mDragState.notifyMoveLocked(newX, newY);
+ } else {
+ mDragState.notifyDropLocked(newX, newY);
+ }
+ }
}
}
@@ -252,49 +367,105 @@ class DragDropController {
}
}
- void handleMessage(WindowManagerService service, Message msg) {
- switch (msg.what) {
- case H.DRAG_START_TIMEOUT: {
- IBinder win = (IBinder) msg.obj;
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Timeout starting drag by win " + win);
- }
- synchronized (service.mWindowMap) {
- // !!! TODO: ANR the app that has failed to start the drag in time
- if (mDragState != null) {
- mDragState.unregister();
- mDragState.reset();
- mDragState = null;
+ /**
+ * Sends a message to the Handler managed by DragDropController.
+ */
+ void sendHandlerMessage(int what, Object arg) {
+ mHandler.obtainMessage(what, arg).sendToTarget();
+ }
+
+ /**
+ * Sends a timeout message to the Handler managed by DragDropController.
+ */
+ void sendTimeoutMessage(int what, Object arg) {
+ mHandler.removeMessages(what, arg);
+ final Message msg = mHandler.obtainMessage(what, arg);
+ mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
+ }
+
+ /**
+ * Notifies the current drag state is closed.
+ */
+ void onDragStateClosedLocked(DragState dragState) {
+ if (mDragState != dragState) {
+ Slog.wtf(TAG_WM, "Unknown drag state is closed");
+ return;
+ }
+ mDragState = null;
+ }
+
+ private class DragHandler extends Handler {
+ /**
+ * Lock for window manager.
+ */
+ private final WindowManagerService mService;
+
+ DragHandler(WindowManagerService service, Looper looper) {
+ super(looper);
+ mService = service;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DRAG_START_TIMEOUT: {
+ IBinder win = (IBinder) msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG_WM, "Timeout starting drag by win " + win);
}
+ synchronized (mWriteLock) {
+ synchronized (mService.mWindowMap) {
+ // !!! TODO: ANR the app that has failed to start the drag in time
+ if (mDragState != null) {
+ mDragState.closeLocked();
+ }
+ }
+ }
+ break;
}
- break;
- }
- case H.DRAG_END_TIMEOUT: {
- IBinder win = (IBinder) msg.obj;
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Timeout ending drag to win " + win);
- }
- synchronized (service.mWindowMap) {
- // !!! TODO: ANR the drag-receiving app
- if (mDragState != null) {
- mDragState.mDragResult = false;
- mDragState.endDragLw();
+ case MSG_DRAG_END_TIMEOUT: {
+ final IBinder win = (IBinder) msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG_WM, "Timeout ending drag to win " + win);
+ }
+ synchronized (mWriteLock) {
+ synchronized (mService.mWindowMap) {
+ // !!! TODO: ANR the drag-receiving app
+ if (mDragState != null) {
+ mDragState.mDragResult = false;
+ mDragState.endDragLocked();
+ }
+ }
}
+ break;
}
- break;
- }
- case H.TEAR_DOWN_DRAG_AND_DROP_INPUT: {
- if (DEBUG_DRAG)
- Slog.d(TAG_WM, "Drag ending; tearing down input channel");
- DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
- if (interceptor != null) {
- synchronized (service.mWindowMap) {
+ case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
+ if (DEBUG_DRAG)
+ Slog.d(TAG_WM, "Drag ending; tearing down input channel");
+ final DragState.InputInterceptor interceptor =
+ (DragState.InputInterceptor) msg.obj;
+ if (interceptor == null) return;
+ synchronized (mService.mWindowMap) {
interceptor.tearDown();
}
+ break;
+ }
+
+ case MSG_ANIMATION_END: {
+ synchronized (mWriteLock) {
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
+ "plyaing animation");
+ return;
+ }
+ mDragState.closeLocked();
+ }
+ }
+ break;
}
- break;
}
}
}
diff --git a/com/android/server/wm/DragInputEventReceiver.java b/com/android/server/wm/DragInputEventReceiver.java
new file mode 100644
index 00000000..bee2bacf
--- /dev/null
+++ b/com/android/server/wm/DragInputEventReceiver.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.os.Looper;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+
+/**
+ * Input receiver for drag and drop
+ */
+class DragInputEventReceiver extends InputEventReceiver {
+ private final DragDropController mDragDropController;
+
+ // Set, if stylus button was down at the start of the drag.
+ private boolean mStylusButtonDownAtStart;
+ // Indicates the first event to check for button state.
+ private boolean mIsStartEvent = true;
+ // Set to true to ignore input events after the drag gesture is complete but the drag events
+ // are still being dispatched.
+ private boolean mMuteInput = false;
+
+ DragInputEventReceiver(InputChannel inputChannel, Looper looper,
+ DragDropController controller) {
+ super(inputChannel, looper);
+ mDragDropController = controller;
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event, int displayId) {
+ boolean handled = false;
+ try {
+ if (!(event instanceof MotionEvent)
+ || (event.getSource() & SOURCE_CLASS_POINTER) == 0
+ || mMuteInput) {
+ return;
+ }
+ final MotionEvent motionEvent = (MotionEvent) event;
+ final float newX = motionEvent.getRawX();
+ final float newY = motionEvent.getRawY();
+ final boolean isStylusButtonDown =
+ (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
+
+ if (mIsStartEvent) {
+ // First event and the button was down, check for the button being
+ // lifted in the future, if that happens we'll drop the item.
+ mStylusButtonDownAtStart = isStylusButtonDown;
+ mIsStartEvent = false;
+ }
+
+ switch (motionEvent.getAction()) {
+ case ACTION_DOWN:
+ if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
+ return;
+ case ACTION_MOVE:
+ if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Button no longer pressed; dropping at " + newX + ","
+ + newY);
+ }
+ mMuteInput = true;
+ }
+ break;
+ case ACTION_UP:
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Got UP on move channel; dropping at " + newX + "," + newY);
+ }
+ mMuteInput = true;
+ break;
+ case ACTION_CANCEL:
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
+ mMuteInput = true;
+ break;
+ default:
+ return;
+ }
+
+ mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, newX, newY);
+ handled = true;
+ } catch (Exception e) {
+ Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+}
diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java
index 11f22411..e81d366b 100644
--- a/com/android/server/wm/DragState.java
+++ b/com/android/server/wm/DragState.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
+import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
+import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -29,14 +32,10 @@ import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.hardware.input.InputManager;
import android.os.Build;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -50,19 +49,14 @@ import android.view.InputChannel;
import android.view.InputDevice;
import android.view.PointerIcon;
import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
-import android.view.animation.Transformation;
+import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
-import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
-import com.android.server.wm.WindowManagerService.H;
-
-import com.android.internal.view.IDragAndDropPermissions;
import java.util.ArrayList;
@@ -86,10 +80,8 @@ class DragState {
private static final String ANIMATED_PROPERTY_ALPHA = "alpha";
private static final String ANIMATED_PROPERTY_SCALE = "scale";
- // Messages for Handler.
- private static final int MSG_ANIMATION_END = 0;
-
final WindowManagerService mService;
+ final DragDropController mDragDropController;
IBinder mToken;
/**
* Do not use the variable from the out of animation thread while mAnimator is not null.
@@ -113,39 +105,101 @@ class DragState {
WindowState mTargetWindow;
ArrayList<WindowState> mNotifiedWindows;
boolean mDragInProgress;
+ /**
+ * Whether if animation is completed. Needs to be volatile to update from the animation thread
+ * without having a WM lock.
+ */
+ volatile boolean mAnimationCompleted = false;
DisplayContent mDisplayContent;
@Nullable private ValueAnimator mAnimator;
private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
private Point mDisplaySize = new Point();
- private final Handler mHandler;
DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
int flags, IBinder localWin) {
mService = service;
+ mDragDropController = service.mDragDropController;
mToken = token;
mSurfaceControl = surface;
mFlags = flags;
mLocalWin = localWin;
mNotifiedWindows = new ArrayList<WindowState>();
- mHandler = new DragStateHandler(service.mH.getLooper());
}
- void reset() {
- if (mAnimator != null) {
- Slog.wtf(TAG_WM,
- "Unexpectedly destroying mSurfaceControl while animation is running");
+ /**
+ * After calling this, DragDropController#onDragStateClosedLocked is invoked, which causes
+ * DragDropController#mDragState becomes null.
+ */
+ void closeLocked() {
+ // Unregister the input interceptor.
+ if (mInputInterceptor != null) {
+ if (DEBUG_DRAG)
+ Slog.d(TAG_WM, "unregistering drag input channel");
+
+ // Input channel should be disposed on the thread where the input is being handled.
+ mDragDropController.sendHandlerMessage(
+ MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
+ mInputInterceptor = null;
+ mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+ }
+
+ // Send drag end broadcast if drag start has been sent.
+ if (mDragInProgress) {
+ final int myPid = Process.myPid();
+
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
+ }
+ for (WindowState ws : mNotifiedWindows) {
+ float x = 0;
+ float y = 0;
+ if (!mDragResult && (ws.mSession.mPid == mPid)) {
+ // Report unconsumed drop location back to the app that started the drag.
+ x = mCurrentX;
+ y = mCurrentY;
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+ x, y, null, null, null, null, mDragResult);
+ try {
+ ws.mClient.dispatchDragEvent(evt);
+ } catch (RemoteException e) {
+ Slog.w(TAG_WM, "Unable to drag-end window " + ws);
+ }
+ // if the current window is in the same process,
+ // the dispatch has already recycled the event
+ if (myPid != ws.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ mNotifiedWindows.clear();
+ mDragInProgress = false;
+ }
+
+ // Take the cursor back if it has been changed.
+ if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+ mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
+ mTouchSource = 0;
}
+
+ // Clear the internal variables.
if (mSurfaceControl != null) {
mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ if (mAnimator != null && !mAnimationCompleted) {
+ Slog.wtf(TAG_WM,
+ "Unexpectedly destroying mSurfaceControl while animation is running");
}
- mSurfaceControl = null;
mFlags = 0;
mLocalWin = null;
mToken = null;
mData = null;
mThumbOffsetX = mThumbOffsetY = 0;
mNotifiedWindows = null;
+
+ // Notifies the controller that the drag state is closed.
+ mDragDropController.onDragStateClosedLocked(this);
}
class InputInterceptor {
@@ -159,8 +213,8 @@ class DragState {
mServerChannel = channels[0];
mClientChannel = channels[1];
mService.mInputManager.registerInputChannel(mServerChannel, null);
- mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
- mService.mH.getLooper());
+ mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
+ mService.mH.getLooper(), mDragDropController);
mDragApplicationHandle = new InputApplicationHandle(null);
mDragApplicationHandle.name = "drag";
@@ -171,7 +225,7 @@ class DragState {
display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.inputChannel = mServerChannel;
- mDragWindowHandle.layer = getDragLayerLw();
+ mDragWindowHandle.layer = getDragLayerLocked();
mDragWindowHandle.layoutParamsFlags = 0;
mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
mDragWindowHandle.dispatchingTimeoutNanos =
@@ -244,20 +298,7 @@ class DragState {
}
}
- void unregister() {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
- if (mInputInterceptor == null) {
- Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
- } else {
- // Input channel should be disposed on the thread where the input is being handled.
- mService.mH.obtainMessage(
- H.TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor).sendToTarget();
- mInputInterceptor = null;
- mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
- }
- }
-
- int getDragLayerLw() {
+ int getDragLayerLocked() {
return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
* WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET;
@@ -265,7 +306,7 @@ class DragState {
/* call out to each visible window/session informing it about the drag
*/
- void broadcastDragStartedLw(final float touchX, final float touchY) {
+ void broadcastDragStartedLocked(final float touchX, final float touchY) {
mOriginalX = mCurrentX = touchX;
mOriginalY = mCurrentY = touchY;
@@ -292,7 +333,7 @@ class DragState {
}
mDisplayContent.forAllWindows(w -> {
- sendDragStartedLw(w, touchX, touchY, mDataDescription);
+ sendDragStartedLocked(w, touchX, touchY, mDataDescription);
}, false /* traverseTopToBottom */ );
}
@@ -304,7 +345,7 @@ class DragState {
* This method clones the 'event' parameter if it's being delivered to the same
* process, so it's safe for the caller to call recycle() on the event afterwards.
*/
- private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
+ private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY,
ClipDescription desc) {
if (mDragInProgress && isValidDropTarget(newWin)) {
DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
@@ -353,7 +394,7 @@ class DragState {
* previously been notified, i.e. it became visible after the drag operation
* was begun. This is a rare case.
*/
- void sendDragStartedIfNeededLw(WindowState newWin) {
+ void sendDragStartedIfNeededLocked(WindowState newWin) {
if (mDragInProgress) {
// If we have sent the drag-started, we needn't do so again
if (isWindowNotified(newWin)) {
@@ -362,7 +403,7 @@ class DragState {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
}
- sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
+ sendDragStartedLocked(newWin, mCurrentX, mCurrentY, mDataDescription);
}
}
@@ -375,82 +416,33 @@ class DragState {
return false;
}
- private void broadcastDragEndedLw() {
- final int myPid = Process.myPid();
-
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
- }
- for (WindowState ws : mNotifiedWindows) {
- float x = 0;
- float y = 0;
- if (!mDragResult && (ws.mSession.mPid == mPid)) {
- // Report unconsumed drop location back to the app that started the drag.
- x = mCurrentX;
- y = mCurrentY;
- }
- DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
- x, y, null, null, null, null, mDragResult);
- try {
- ws.mClient.dispatchDragEvent(evt);
- } catch (RemoteException e) {
- Slog.w(TAG_WM, "Unable to drag-end window " + ws);
- }
- // if the current window is in the same process,
- // the dispatch has already recycled the event
- if (myPid != ws.mSession.mPid) {
- evt.recycle();
- }
- }
- mNotifiedWindows.clear();
- mDragInProgress = false;
- }
-
- void endDragLw() {
+ void endDragLocked() {
if (mAnimator != null) {
return;
}
if (!mDragResult) {
mAnimator = createReturnAnimationLocked();
- return; // Will call cleanUpDragLw when the animation is done.
+ return; // Will call closeLocked() when the animation is done.
}
- cleanUpDragLw();
+ closeLocked();
}
- void cancelDragLw() {
+ void cancelDragLocked() {
if (mAnimator != null) {
return;
}
if (!mDragInProgress) {
// This can happen if an app invokes Session#cancelDragAndDrop before
- // Session#performDrag. Reset the drag state:
- // 1. without sending the end broadcast because the start broadcast has not been sent,
- // and
- // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to
- // WindowManagerService, which will cause DragState#reset() while playing the
- // cancel animation.
- reset();
- mService.mDragDropController.mDragState = null;
+ // Session#performDrag. Reset the drag state without playing the cancel animation
+ // because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause
+ // DragState#reset() while playing the cancel animation.
+ closeLocked();
return;
}
mAnimator = createCancelAnimationLocked();
}
- private void cleanUpDragLw() {
- broadcastDragEndedLw();
- if (isFromSource(InputDevice.SOURCE_MOUSE)) {
- mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
- }
-
- // stop intercepting input
- unregister();
-
- // free our resources and drop all the object references
- reset();
- mService.mDragDropController.mDragState = null;
- }
-
- void notifyMoveLw(float x, float y) {
+ void notifyMoveLocked(float x, float y) {
if (mAnimator != null) {
return;
}
@@ -459,7 +451,7 @@ class DragState {
// Move the surface to the given touch
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
+ TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
mService.openSurfaceTransaction();
try {
mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
@@ -467,14 +459,14 @@ class DragState {
+ mSurfaceControl + ": pos=(" +
(int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("notifyMoveLw");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
+ TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
}
- notifyLocationLw(x, y);
+ notifyLocationLocked(x, y);
}
- void notifyLocationLw(float x, float y) {
+ void notifyLocationLocked(float x, float y) {
// Tell the affected window
WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
if (touchedWin != null && !isWindowNotified(touchedWin)) {
@@ -516,34 +508,33 @@ class DragState {
mTargetWindow = touchedWin;
}
- // Find the drop target and tell it about the data. Returns 'true' if we can immediately
- // dispatch the global drag-ended message, 'false' if we need to wait for a
- // result from the recipient.
- boolean notifyDropLw(float x, float y) {
+ /**
+ * Finds the drop target and tells it about the data. If the drop event is not sent to the
+ * target, invokes {@code endDragLocked} immediately.
+ */
+ void notifyDropLocked(float x, float y) {
if (mAnimator != null) {
- return false;
+ return;
}
mCurrentX = x;
mCurrentY = y;
- WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+ final WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
if (!isWindowNotified(touchedWin)) {
// "drop" outside a valid window -- no recipient to apply a
// timeout to, and we can send the drag-ended message immediately.
mDragResult = false;
- return true;
+ endDragLocked();
+ return;
}
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "sending DROP to " + touchedWin);
- }
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
- DragAndDropPermissionsHandler dragAndDropPermissions = null;
- if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
- (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+ final DragAndDropPermissionsHandler dragAndDropPermissions;
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
dragAndDropPermissions = new DragAndDropPermissionsHandler(
mData,
mUid,
@@ -551,31 +542,30 @@ class DragState {
mFlags & DRAG_FLAGS_URI_PERMISSIONS,
mSourceUserId,
targetUserId);
+ } else {
+ dragAndDropPermissions = null;
}
if (mSourceUserId != targetUserId){
mData.fixUris(mSourceUserId);
}
final int myPid = Process.myPid();
final IBinder token = touchedWin.mClient.asBinder();
- DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
+ final DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
null, null, mData, dragAndDropPermissions, false);
try {
touchedWin.mClient.dispatchDragEvent(evt);
// 5 second timeout for this window to respond to the drop
- mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
- Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
- mService.mH.sendMessageDelayed(msg, 5000);
+ mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token);
} catch (RemoteException e) {
Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
- return true;
+ endDragLocked();
} finally {
if (myPid != touchedWin.mSession.mPid) {
evt.recycle();
}
}
mToken = token;
- return false;
}
private static DragEvent obtainDragEvent(WindowState win, int action,
@@ -641,40 +631,13 @@ class DragState {
return (mTouchSource & source) == source;
}
- void overridePointerIconLw(int touchSource) {
+ void overridePointerIconLocked(int touchSource) {
mTouchSource = touchSource;
if (isFromSource(InputDevice.SOURCE_MOUSE)) {
InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
}
}
- private class DragStateHandler extends Handler {
- DragStateHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ANIMATION_END:
- synchronized (mService.mWindowMap) {
- if (mService.mDragDropController.mDragState != DragState.this) {
- Slog.wtf(TAG_WM, "mDragState is updated unexpectedly while " +
- "playing animation");
- return;
- }
- if (mAnimator == null) {
- Slog.wtf(TAG_WM, "Unexpected null mAnimator");
- return;
- }
- mAnimator = null;
- cleanUpDragLw();
- }
- break;
- }
- }
- }
-
private class AnimationListener
implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
@Override
@@ -706,9 +669,10 @@ class DragState {
@Override
public void onAnimationEnd(Animator animator) {
+ mAnimationCompleted = true;
// Updating mDragState requires the WM lock so continues it on the out of
// AnimationThread.
- mHandler.sendEmptyMessage(MSG_ANIMATION_END);
+ mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
}
}
}
diff --git a/com/android/server/wm/InputConsumerImpl.java b/com/android/server/wm/InputConsumerImpl.java
index 36753b7d..d40db8cb 100644
--- a/com/android/server/wm/InputConsumerImpl.java
+++ b/com/android/server/wm/InputConsumerImpl.java
@@ -16,21 +16,36 @@
package com.android.server.wm;
+import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.view.Display;
import android.view.InputChannel;
import android.view.WindowManager;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
-class InputConsumerImpl {
+import java.io.PrintWriter;
+
+class InputConsumerImpl implements IBinder.DeathRecipient {
final WindowManagerService mService;
final InputChannel mServerChannel, mClientChannel;
final InputApplicationHandle mApplicationHandle;
final InputWindowHandle mWindowHandle;
- InputConsumerImpl(WindowManagerService service, String name, InputChannel inputChannel) {
+ final IBinder mToken;
+ final String mName;
+ final int mClientPid;
+ final UserHandle mClientUser;
+
+ InputConsumerImpl(WindowManagerService service, IBinder token, String name,
+ InputChannel inputChannel, int clientPid, UserHandle clientUser) {
mService = service;
+ mToken = token;
+ mName = name;
+ mClientPid = clientPid;
+ mClientUser = clientUser;
InputChannel[] channels = InputChannel.openInputChannelPair(name);
mServerChannel = channels[0];
@@ -68,6 +83,26 @@ class InputConsumerImpl {
mWindowHandle.scaleFactor = 1.0f;
}
+ void linkToDeathRecipient() {
+ if (mToken == null) {
+ return;
+ }
+
+ try {
+ mToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // Client died, do nothing
+ }
+ }
+
+ void unlinkFromDeathRecipient() {
+ if (mToken == null) {
+ return;
+ }
+
+ mToken.unlinkToDeath(this, 0);
+ }
+
void layout(int dw, int dh) {
mWindowHandle.touchableRegion.set(0, 0, dw, dh);
mWindowHandle.frameLeft = 0;
@@ -86,5 +121,19 @@ class InputConsumerImpl {
mService.mInputManager.unregisterInputChannel(mServerChannel);
mClientChannel.dispose();
mServerChannel.dispose();
+ unlinkFromDeathRecipient();
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mService.getWindowManagerLock()) {
+ // Clean up the input consumer
+ mService.mInputMonitor.destroyInputConsumer(mName);
+ unlinkFromDeathRecipient();
+ }
+ }
+
+ void dump(PrintWriter pw, String name, String prefix) {
+ pw.println(prefix + " name=" + name + " pid=" + mClientPid + " user=" + mClientUser);
}
}
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index cf1f1713..a766097c 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -25,6 +25,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLP
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -34,8 +35,11 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.Debug;
+import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -43,7 +47,6 @@ 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;
@@ -106,8 +109,9 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
Looper looper, String name,
- InputEventReceiver.Factory inputEventReceiverFactory) {
- super(service, name, null);
+ InputEventReceiver.Factory inputEventReceiverFactory,
+ int clientPid, UserHandle clientUser) {
+ super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser);
mInputMonitor = monitor;
mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
mClientChannel, looper);
@@ -129,6 +133,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
private void addInputConsumer(String name, InputConsumerImpl consumer) {
mInputConsumers.put(name, consumer);
+ consumer.linkToDeathRecipient();
updateInputWindowsLw(true /* force */);
}
@@ -166,17 +171,20 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
}
final EventReceiverInputConsumer consumer = new EventReceiverInputConsumer(mService,
- this, looper, name, inputEventReceiverFactory);
+ this, looper, name, inputEventReceiverFactory, Process.myPid(),
+ UserHandle.SYSTEM);
addInputConsumer(name, consumer);
return consumer;
}
- void createInputConsumer(String name, InputChannel inputChannel) {
+ void createInputConsumer(IBinder token, String name, InputChannel inputChannel, int clientPid,
+ UserHandle clientUser) {
if (mInputConsumers.containsKey(name)) {
throw new IllegalStateException("Existing input consumer found with name: " + name);
}
- final InputConsumerImpl consumer = new InputConsumerImpl(mService, name, inputChannel);
+ final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
+ inputChannel, clientPid, clientUser);
switch (name) {
case INPUT_CONSUMER_WALLPAPER:
consumer.mWindowHandle.hasWallpaper = true;
@@ -373,7 +381,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
Log.d(TAG_WM, "Inserting drag window");
}
final InputWindowHandle dragWindowHandle =
- mService.mDragDropController.mDragState.getInputWindowHandle();
+ mService.mDragDropController.getInputWindowHandleLocked();
if (dragWindowHandle != null) {
addInputWindowHandle(dragWindowHandle);
} else {
@@ -593,7 +601,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
if (!inputConsumerKeys.isEmpty()) {
pw.println(prefix + "InputConsumers:");
for (String key : inputConsumerKeys) {
- pw.println(prefix + " name=" + key);
+ mInputConsumers.get(key).dump(pw, key, prefix);
}
}
}
@@ -690,7 +698,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
// If there's a drag in progress and 'child' is a potential drop target,
// make sure it's been told about the drag
if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
- mService.mDragDropController.mDragState.sendDragStartedIfNeededLw(w);
+ mService.mDragDropController.sendDragStartedIfNeededLocked(w);
}
addInputWindowHandle(
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
index 365366ad..d8726bfc 100644
--- a/com/android/server/wm/PinnedStackController.java
+++ b/com/android/server/wm/PinnedStackController.java
@@ -149,6 +149,9 @@ class PinnedStackController {
@Override
public void binderDied() {
// Clean up the state if the listener dies
+ if (mPinnedStackListener != null) {
+ mPinnedStackListener.asBinder().unlinkToDeath(mPinnedStackListenerDeathHandler, 0);
+ }
mPinnedStackListener = null;
}
}
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index a7104410..f541926e 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -247,7 +247,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
if (mService.mDisplayManagerInternal != null) {
mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
displayId, displayInfo);
- mService.configureDisplayPolicyLocked(dc);
+ dc.configureDisplayPolicy();
// Tap Listeners are supported for:
// 1. All physical displays (multi-display).
@@ -457,7 +457,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
try {
forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("removeReplacedWindows");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
}
}
@@ -599,7 +599,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index c25b19cc..3350feae 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -296,7 +296,7 @@ class ScreenRotationAnimation {
setRotationInTransaction(originalRotation);
} finally {
if (!inTransaction) {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation");
}
@@ -567,7 +567,7 @@ class ScreenRotationAnimation {
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -607,7 +607,7 @@ class ScreenRotationAnimation {
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -629,7 +629,7 @@ class ScreenRotationAnimation {
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java
index 717c5774..ad7c300a 100644
--- a/com/android/server/wm/Session.java
+++ b/com/android/server/wm/Session.java
@@ -313,7 +313,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final long ident = Binder.clearCallingIdentity();
try {
return mDragDropController.prepareDrag(
- mService, mSurfaceSession, callerPid, callerUid, window, flags, width, height,
+ mSurfaceSession, callerPid, callerUid, window, flags, width, height,
outSurface);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -324,7 +324,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public boolean performDrag(IWindow window, IBinder dragToken,
int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
ClipData data) {
- return mDragDropController.performDrag(mService, window, dragToken, touchSource,
+ return mDragDropController.performDrag(window, dragToken, touchSource,
touchX, touchY, thumbCenterX, thumbCenterY, data);
}
@@ -332,7 +332,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public void reportDropResult(IWindow window, boolean consumed) {
final long ident = Binder.clearCallingIdentity();
try {
- mDragDropController.reportDropResult(mService, window, consumed);
+ mDragDropController.reportDropResult(window, consumed);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -342,7 +342,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public void cancelDragAndDrop(IBinder dragToken) {
final long ident = Binder.clearCallingIdentity();
try {
- mDragDropController.cancelDragAndDrop(mService, dragToken);
+ mDragDropController.cancelDragAndDrop(dragToken);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index aff1bc62..95c1d536 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -225,11 +225,13 @@ public class StackWindowController
}
}
- private void getRawBounds(Rect outBounds) {
- if (mContainer.getRawFullscreen()) {
- outBounds.setEmpty();
- } else {
- mContainer.getRawBounds(outBounds);
+ public void getRawBounds(Rect outBounds) {
+ synchronized (mWindowMap) {
+ if (mContainer.getRawFullscreen()) {
+ outBounds.setEmpty();
+ } else {
+ mContainer.getRawBounds(outBounds);
+ }
}
}
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 7620cb0d..13435d76 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -419,7 +419,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return mFillsParent
|| !inSplitScreenSecondaryWindowingMode()
|| displayContent == null
- || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null;
+ || displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null;
}
/** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -678,7 +678,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
mChildren.get(i).forceWindowsScaleableInTransaction(force);
}
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("forceWindowsScaleable");
}
}
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
index 87de1514..12f6b5a2 100644
--- a/com/android/server/wm/TaskPositioner.java
+++ b/com/android/server/wm/TaskPositioner.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.RESIZE_MODE_USER;
import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -210,9 +210,9 @@ class TaskPositioner implements DimLayer.DimLayerUser {
if (mCurrentDimSide != CTRL_NONE) {
final int createMode = mCurrentDimSide == CTRL_LEFT
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
- mService.mActivityManager.moveTaskToDockedStack(
+ ? 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 */);
}
@@ -637,7 +637,7 @@ class TaskPositioner implements DimLayer.DimLayerUser {
} else {
showDimLayer();
}
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("updateDimLayerVisibility");
}
/**
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 791accf8..053fb470 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -294,7 +294,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (mFillsParent
|| !inSplitScreenSecondaryWindowingMode()
|| mDisplayContent == null
- || mDisplayContent.getSplitScreenPrimaryStackStack() != null) {
+ || mDisplayContent.getSplitScreenPrimaryStack() != null) {
return true;
}
return false;
@@ -442,7 +442,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mTmpRect2.set(mBounds);
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
if (inSplitScreenPrimaryWindowingMode()) {
- repositionDockedStackAfterRotation(mTmpRect2);
+ repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
snapDockedStackAfterRotation(mTmpRect2);
final int newDockSide = getDockSide(mTmpRect2);
@@ -450,8 +450,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// might change after a rotation and the original values will be invalid.
mService.setDockedStackCreateStateLocked(
(newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
null);
mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
}
@@ -466,14 +466,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
/**
- * Some dock sides are not allowed by the policy. This method queries the policy and moves
- * the docked stack around if needed.
+ * Some primary split screen sides are not allowed by the policy. This method queries the policy
+ * and moves the primary stack around if needed.
*
- * @param inOutBounds the bounds of the docked stack to adjust
+ * @param inOutBounds the bounds of the primary stack to adjust
*/
- private void repositionDockedStackAfterRotation(Rect inOutBounds) {
+ private void repositionPrimarySplitScreenStackAfterRotation(Rect inOutBounds) {
int dockSide = getDockSide(inOutBounds);
- if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+ if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
return;
}
mDisplayContent.getLogicalDisplayRect(mTmpRect);
@@ -523,7 +523,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
mService.mContext.getResources(), displayWidth, displayHeight,
dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds,
- isMinimizedDockAndHomeStackResizable());
+ getDockSide(), isMinimizedDockAndHomeStackResizable());
final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
// Recalculate the bounds based on the position of the target.
@@ -603,6 +603,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
} else {
maxPosition = computeMaxPosition(maxPosition);
}
+
+ // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
+ if (targetPosition == POSITION_BOTTOM && minPosition == 0) {
+ return POSITION_BOTTOM;
+ } else if (targetPosition == POSITION_TOP
+ && maxPosition == (addingNew ? stackSize : stackSize - 1)) {
+ return POSITION_TOP;
+ }
// Reset position based on minimum/maximum possible positions.
return Math.min(Math.max(targetPosition, minPosition), maxPosition);
}
@@ -677,9 +685,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
public void onConfigurationChanged(Configuration newParentConfig) {
final int prevWindowingMode = getWindowingMode();
super.onConfigurationChanged(newParentConfig);
- if (mDisplayContent != null && prevWindowingMode != getWindowingMode()) {
- mDisplayContent.onStackWindowingModeChanged(this);
+ final int windowingMode = getWindowingMode();
+ if (mDisplayContent == null || prevWindowingMode == windowingMode) {
+ return;
}
+ mDisplayContent.onStackWindowingModeChanged(this);
+ updateBoundsForWindowModeChange();
}
@Override
@@ -691,39 +702,41 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mDisplayContent = dc;
mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
"animation background stackId=" + mStackId);
+ updateBoundsForWindowModeChange();
+ super.onDisplayChanged(dc);
+ }
+ private void updateBoundsForWindowModeChange() {
Rect bounds = null;
- final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
- if (inSplitScreenPrimaryWindowingMode()
- || (dockedStack != null && inSplitScreenSecondaryWindowingMode()
- && !dockedStack.fillsParent())) {
+ final boolean inSplitScreenPrimary = inSplitScreenPrimaryWindowingMode();
+ final TaskStack splitScreenStack =
+ mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+ if (inSplitScreenPrimary || (splitScreenStack != null
+ && inSplitScreenSecondaryWindowingMode() && !splitScreenStack.fillsParent())) {
// The existence of a docked stack affects the size of other static stack created since
// the docked stack occupies a dedicated region on screen, but only if the dock stack is
// 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();
- dc.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getLogicalDisplayRect(mTmpRect);
mTmpRect2.setEmpty();
- if (dockedStack != null) {
- dockedStack.getRawBounds(mTmpRect2);
+ if (splitScreenStack != null) {
+ splitScreenStack.getRawBounds(mTmpRect2);
}
final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
- == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
getStackDockedModeBounds(mTmpRect, bounds, mTmpRect2,
- mDisplayContent.mDividerControllerLocked.getContentWidth(),
- dockedOnTopOrLeft);
+ mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);
} else if (inPinnedWindowingMode()) {
// Update the bounds based on any changes to the display info
getAnimationOrCurrentBounds(mTmpRect2);
- boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
- mTmpRect2, mTmpRect3);
- if (updated) {
+ if (mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
+ mTmpRect2, mTmpRect3)) {
bounds = new Rect(mTmpRect3);
}
}
updateDisplayInfo(bounds);
- super.onDisplayChanged(dc);
}
/**
@@ -773,7 +786,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
final TaskStack dockedStack =
- mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
if (dockedStack == null) {
// Not sure why you are calling this method when there is no docked stack...
throw new IllegalStateException(
@@ -914,10 +927,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mAnimationBackgroundSurface = null;
}
- if (inSplitScreenPrimaryWindowingMode()) {
- mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
- }
-
mDisplayContent = null;
mService.mWindowPlacerLocked.requestTraversal();
}
diff --git a/com/android/server/wm/TaskWindowContainerController.java b/com/android/server/wm/TaskWindowContainerController.java
index 65f8cdf3..b3bb0b7e 100644
--- a/com/android/server/wm/TaskWindowContainerController.java
+++ b/com/android/server/wm/TaskWindowContainerController.java
@@ -103,6 +103,10 @@ public class TaskWindowContainerController
}
}
+ public void positionChildAtTop(AppWindowContainerController childController) {
+ positionChildAt(childController, POSITION_TOP);
+ }
+
public void positionChildAt(AppWindowContainerController childController, int position) {
synchronized(mService.mWindowMap) {
final AppWindowToken aToken = childController.mContainer;
diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java
index e409a68f..1912095c 100644
--- a/com/android/server/wm/WindowAnimator.java
+++ b/com/android/server/wm/WindowAnimator.java
@@ -233,7 +233,7 @@ public class WindowAnimator {
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("WindowAnimator");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index f31ea67c..ce343061 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -20,7 +20,7 @@ import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
@@ -80,7 +80,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIO
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
@@ -125,7 +124,9 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
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;
@@ -137,7 +138,6 @@ import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -160,10 +160,13 @@ import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -203,14 +206,12 @@ import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.InputDevice;
-import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
@@ -226,7 +227,6 @@ import android.view.animation.Animation;
import android.view.inputmethod.InputMethodManagerInternal;
import com.android.internal.R;
-import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -245,7 +245,7 @@ import com.android.server.LocalServices;
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerService;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
+import android.view.DisplayFrames;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
@@ -365,6 +365,8 @@ public class WindowManagerService extends IWindowManager.Stub
private static final int TRANSITION_ANIMATION_SCALE = 1;
private static final int ANIMATION_DURATION_SCALE = 2;
+ final WindowTracing mWindowTracing;
+
final private KeyguardDisableHandler mKeyguardDisableHandler;
boolean mKeyguardGoingAway;
// VR Vr2d Display Id.
@@ -560,7 +562,7 @@ public class WindowManagerService extends IWindowManager.Stub
// The root of the device window hierarchy.
RootWindowContainer mRoot;
- int mDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
private final SparseIntArray mTmpTaskIds = new SparseIntArray();
@@ -568,6 +570,8 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mForceResizableTasks = false;
boolean mSupportsPictureInPicture = false;
+ private boolean mDisableTransitionAnimation = false;
+
int getDragLayerLocked() {
return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
}
@@ -751,7 +755,7 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mAllowTheaterModeWakeFromLayout;
TaskPositioner mTaskPositioner;
- final DragDropController mDragDropController = new DragDropController();
+ final DragDropController mDragDropController;
// For frozen screen animations.
private int mExitAnimId, mEnterAnimId;
@@ -771,110 +775,6 @@ public class WindowManagerService extends IWindowManager.Stub
private WindowContentFrameStats mTempWindowRenderStats;
- final class DragInputEventReceiver extends InputEventReceiver {
- // Set, if stylus button was down at the start of the drag.
- private boolean mStylusButtonDownAtStart;
- // Indicates the first event to check for button state.
- private boolean mIsStartEvent = true;
- // Set to true to ignore input events after the drag gesture is complete but the drag events
- // are still being dispatched.
- private boolean mMuteInput = false;
-
- public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
- super(inputChannel, looper);
- }
-
- @Override
- public void onInputEvent(InputEvent event, int displayId) {
- boolean handled = false;
- try {
- if (mDragDropController.mDragState == null) {
- // The drag has ended but the clean-up message has not been processed by
- // window manager. Drop events that occur after this until window manager
- // has a chance to clean-up the input handle.
- handled = true;
- return;
- }
- if (event instanceof MotionEvent
- && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
- && !mMuteInput) {
- final MotionEvent motionEvent = (MotionEvent)event;
- boolean endDrag = false;
- final float newX = motionEvent.getRawX();
- final float newY = motionEvent.getRawY();
- final boolean isStylusButtonDown =
- (motionEvent.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
-
- if (mIsStartEvent) {
- if (isStylusButtonDown) {
- // First event and the button was down, check for the button being
- // lifted in the future, if that happens we'll drop the item.
- mStylusButtonDownAtStart = true;
- }
- mIsStartEvent = false;
- }
-
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
- }
- } break;
-
- case MotionEvent.ACTION_MOVE: {
- if (mStylusButtonDownAtStart && !isStylusButtonDown) {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
- + newX + "," + newY);
- mMuteInput = true;
- synchronized (mWindowMap) {
- endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY);
- }
- } else {
- synchronized (mWindowMap) {
- // move the surface and tell the involved window(s) where we are
- mDragDropController.mDragState.notifyMoveLw(newX, newY);
- }
- }
- } break;
-
- case MotionEvent.ACTION_UP: {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
- + newX + "," + newY);
- mMuteInput = true;
- synchronized (mWindowMap) {
- endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY);
- }
- } break;
-
- case MotionEvent.ACTION_CANCEL: {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
- mMuteInput = true;
- endDrag = true;
- } break;
- }
-
- if (endDrag) {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ended; tearing down state");
- // tell all the windows that the drag has ended
- synchronized (mWindowMap) {
- // endDragLw will post back to looper to dispose the receiver
- // since we still need the receiver for the last finishInputEvent.
- mDragDropController.mDragState.endDragLw();
- }
- mStylusButtonDownAtStart = false;
- mIsStartEvent = true;
- }
-
- handled = true;
- }
- } catch (Exception e) {
- Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
- } finally {
- finishInputEvent(event, handled);
- }
- }
- }
-
/**
* Whether the UI is currently running in touch mode (not showing
* navigational focus because the user is directly pressing the screen).
@@ -928,15 +828,20 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Closes a surface transaction.
+ * @param where debug string indicating where the transaction originated
*/
- void closeSurfaceTransaction() {
+ void closeSurfaceTransaction(String where) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
synchronized (mWindowMap) {
- if (mRoot.mSurfaceTraceEnabled) {
- mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ try {
+ traceStateLocked(where);
+ } finally {
+ if (mRoot.mSurfaceTraceEnabled) {
+ mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ }
+ SurfaceControl.closeTransaction();
}
- SurfaceControl.closeTransaction();
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -1037,6 +942,12 @@ public class WindowManagerService extends IWindowManager.Stub
}, 0);
}
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver result) {
+ new WindowManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
+ }
+
private WindowManagerService(Context context, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
WindowManagerPolicy policy) {
@@ -1058,6 +969,8 @@ public class WindowManagerService extends IWindowManager.Stub
com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
mMaxUiWidth = context.getResources().getInteger(
com.android.internal.R.integer.config_maxUiWidth);
+ mDisableTransitionAnimation = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableTransitionAnimation);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplaySettings = new DisplaySettings();
@@ -1067,6 +980,8 @@ public class WindowManagerService extends IWindowManager.Stub
mPolicy = policy;
mTaskSnapshotController = new TaskSnapshotController(this);
+ mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);
+
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
if(mInputManager != null) {
@@ -1164,6 +1079,7 @@ public class WindowManagerService extends IWindowManager.Stub
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
+ mDragDropController = new DragDropController(this, mH.getLooper());
LocalServices.addService(WindowManagerInternal.class, new LocalService());
initPolicy();
@@ -1175,7 +1091,7 @@ public class WindowManagerService extends IWindowManager.Stub
try {
createWatermarkInTransaction();
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("createWatermarkInTransaction");
}
showEmulatorDisplayOverlayIfNeeded();
@@ -1410,7 +1326,10 @@ public class WindowManagerService extends IWindowManager.Stub
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
- mPolicy.adjustWindowParamsLw(win.mAttrs);
+ final boolean hasStatusBarServicePermission =
+ mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
+ == PackageManager.PERMISSION_GRANTED;
+ mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
res = mPolicy.prepareAddWindowLw(win, attrs);
@@ -1534,23 +1453,19 @@ public class WindowManagerService extends IWindowManager.Stub
prepareNoneTransitionForRelaunching(atoken);
}
- if (displayContent.isDefaultDisplay) {
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final Rect taskBounds;
- if (atoken != null && atoken.getTask() != null) {
- taskBounds = mTmpRect;
- atoken.getTask().getBounds(mTmpRect);
- } else {
- taskBounds = null;
- }
- if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayInfo.rotation,
- displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
- outStableInsets, outOutsets)) {
- res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
- }
+ final DisplayFrames displayFrames = displayContent.mDisplayFrames;
+ // TODO: Not sure if onDisplayInfoUpdated() call is needed.
+ displayFrames.onDisplayInfoUpdated(displayContent.getDisplayInfo());
+ final Rect taskBounds;
+ if (atoken != null && atoken.getTask() != null) {
+ taskBounds = mTmpRect;
+ atoken.getTask().getBounds(mTmpRect);
} else {
- outContentInsets.setEmpty();
- outStableInsets.setEmpty();
+ taskBounds = null;
+ }
+ if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayFrames, outContentInsets,
+ outStableInsets, outOutsets)) {
+ res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
}
if (mInTouchMode) {
@@ -1936,8 +1851,11 @@ public class WindowManagerService extends IWindowManager.Stub
MergedConfiguration mergedConfiguration, Surface outSurface) {
int result = 0;
boolean configChanged;
- boolean hasStatusBarPermission =
- mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+ final boolean hasStatusBarPermission =
+ mContext.checkCallingOrSelfPermission(permission.STATUS_BAR)
+ == PackageManager.PERMISSION_GRANTED;
+ final boolean hasStatusBarServicePermission =
+ mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
== PackageManager.PERMISSION_GRANTED;
long origId = Binder.clearCallingIdentity();
@@ -1957,7 +1875,7 @@ public class WindowManagerService extends IWindowManager.Stub
int attrChanges = 0;
int flagChanges = 0;
if (attrs != null) {
- mPolicy.adjustWindowParamsLw(attrs);
+ mPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission);
// if they don't have the permission, mask out the status bar bits
if (seq == win.mSeq) {
int systemUiVisibility = attrs.systemUiVisibility
@@ -2362,6 +2280,14 @@ public class WindowManagerService extends IWindowManager.Stub
boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
int transit, boolean enter, boolean isVoiceInteraction) {
+ if (mDisableTransitionAnimation) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG_WM,
+ "applyAnimation: transition animation is disabled. atoken=" + atoken);
+ }
+ atoken.mAppAnimator.clearAnimation();
+ return false;
+ }
// Only apply an animation if the display isn't frozen. If it is
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
@@ -3000,10 +2926,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
public void setKeyguardGoingAway(boolean keyguardGoingAway) {
-// TODO: Use of this can be removed. Revert ag/I8369723d6a77f2c602f1ef080371fa7cd9ee094e
-// synchronized (mWindowMap) {
-// mKeyguardGoingAway = keyguardGoingAway;
-// }
+ synchronized (mWindowMap) {
+ mKeyguardGoingAway = keyguardGoingAway;
+ }
}
// -------------------------------------------------------------
@@ -3391,7 +3316,7 @@ public class WindowManagerService extends IWindowManager.Stub
// Notify whether the docked stack exists for the current user
final DisplayContent displayContent = getDefaultDisplayContentLocked();
final TaskStack stack =
- displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ displayContent.getSplitScreenPrimaryStackIgnoringVisibility();
displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
stack != null && stack.hasTaskForUser(newUserId));
@@ -3679,7 +3604,7 @@ public class WindowManagerService extends IWindowManager.Stub
mCircularDisplayMask = null;
}
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("showCircularMask");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
}
@@ -3704,7 +3629,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
mEmulatorDisplayOverlay.setVisibility(true);
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("showEmulatorDisplayOverlay");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
}
@@ -3783,7 +3708,7 @@ public class WindowManagerService extends IWindowManager.Stub
* of the target image.
*/
@Override
- public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
+ public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
if (!checkCallingPermission(READ_FRAME_BUFFER,
"requestAssistScreenshot()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
@@ -3795,7 +3720,7 @@ public class WindowManagerService extends IWindowManager.Stub
1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
false /* includeDecor */);
try {
- receiver.send(bm);
+ receiver.onHandleAssistScreenshot(bm);
} catch (RemoteException e) {
}
});
@@ -4740,7 +4665,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(mWindowMap) {
mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN);
- configureDisplayPolicyLocked(getDefaultDisplayContentLocked());
+ getDefaultDisplayContentLocked().configureDisplayPolicy();
}
try {
@@ -4796,8 +4721,6 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int APP_FREEZE_TIMEOUT = 17;
public static final int SEND_NEW_CONFIGURATION = 18;
public static final int REPORT_WINDOWS_CHANGE = 19;
- public static final int DRAG_START_TIMEOUT = 20;
- public static final int DRAG_END_TIMEOUT = 21;
public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
public static final int BOOT_TIMEOUT = 23;
@@ -4824,8 +4747,6 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
- public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
-
public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
@@ -5053,13 +4974,6 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
- case DRAG_START_TIMEOUT:
- case DRAG_END_TIMEOUT:
- case TEAR_DOWN_DRAG_AND_DROP_INPUT: {
- mDragDropController.handleMessage(WindowManagerService.this, msg);
- break;
- }
-
case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
notifyHardKeyboardStatusChange();
break;
@@ -5608,7 +5522,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (!mDisplayReady) {
return;
}
- configureDisplayPolicyLocked(displayContent);
+ displayContent.configureDisplayPolicy();
displayContent.setLayoutNeeded();
final int displayId = displayContent.getDisplayId();
@@ -5629,18 +5543,6 @@ public class WindowManagerService extends IWindowManager.Stub
mWindowPlacerLocked.performSurfacePlacement();
}
- void configureDisplayPolicyLocked(DisplayContent displayContent) {
- mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
- displayContent.mBaseDisplayWidth,
- displayContent.mBaseDisplayHeight,
- displayContent.mBaseDisplayDensity);
-
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- mPolicy.setDisplayOverscan(displayContent.getDisplay(),
- displayInfo.overscanLeft, displayInfo.overscanTop,
- displayInfo.overscanRight, displayInfo.overscanBottom);
- }
-
/**
* Get an array with display ids ordered by focus priority - last items should be given
* focus first. Sparse array just maps position to displayId.
@@ -6253,9 +6155,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void createInputConsumer(String name, InputChannel inputChannel) {
+ public void createInputConsumer(IBinder token, String name, InputChannel inputChannel) {
synchronized (mWindowMap) {
- mInputMonitor.createInputConsumer(name, inputChannel);
+ mInputMonitor.createInputConsumer(token, name, inputChannel, Binder.getCallingPid(),
+ Binder.getCallingUserHandle());
}
}
@@ -6437,7 +6340,7 @@ public class WindowManagerService extends IWindowManager.Stub
* @param proto Stream to write the WindowContainer object to.
* @param trim If true, reduce the amount of data written.
*/
- private void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
+ void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
mPolicy.writeToProto(proto, POLICY);
mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
if (mCurrentFocus != null) {
@@ -6456,6 +6359,17 @@ public class WindowManagerService extends IWindowManager.Stub
mAppTransition.writeToProto(proto, APP_TRANSITION);
}
+ void traceStateLocked(String where) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
+ try {
+ mWindowTracing.traceStateLocked(where, this);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
@@ -6946,11 +6860,16 @@ public class WindowManagerService extends IWindowManager.Stub
public void setWillReplaceWindow(IBinder token, boolean animate) {
synchronized (mWindowMap) {
final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
- if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
+ if (appWindowToken == null) {
Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+ token);
return;
}
+ if (!appWindowToken.hasContentToDisplay()) {
+ Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
+ + token);
+ return;
+ }
appWindowToken.setWillReplaceWindows(animate);
}
}
@@ -6970,11 +6889,16 @@ public class WindowManagerService extends IWindowManager.Stub
void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
synchronized (mWindowMap) {
final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
- if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
+ if (appWindowToken == null) {
Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+ token);
return;
}
+ if (!appWindowToken.hasContentToDisplay()) {
+ Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
+ + token);
+ return;
+ }
if (childrenOnly) {
appWindowToken.setWillReplaceChildWindows();
@@ -7024,7 +6948,7 @@ public class WindowManagerService extends IWindowManager.Stub
public int getDockedStackSide() {
synchronized (mWindowMap) {
final TaskStack dockedStack = getDefaultDisplayContentLocked()
- .getSplitScreenPrimaryStackStackIgnoringVisibility();
+ .getSplitScreenPrimaryStackIgnoringVisibility();
return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
}
}
@@ -7170,7 +7094,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
synchronized (mWindowMap) {
- if (mDragDropController.mDragState != null) {
+ if (mDragDropController.dragDropActiveLocked()) {
// Drag cursor overrides the app cursor.
return;
}
@@ -7361,11 +7285,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public boolean isKeyguardGoingAway() {
- return WindowManagerService.this.mKeyguardGoingAway;
- }
-
- @Override
public boolean isKeyguardShowingAndNotOccluded() {
return WindowManagerService.this.isKeyguardShowingAndNotOccluded();
}
@@ -7446,7 +7365,9 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public int getInputMethodWindowVisibleHeight() {
synchronized (mWindowMap) {
- return mPolicy.getInputMethodWindowVisibleHeightLw();
+ // TODO(multi-display): Have caller pass in the display they are interested in.
+ final DisplayContent dc = getDefaultDisplayContentLocked();
+ return dc.mDisplayFrames.getInputMethodWindowVisibleHeight();
}
}
@@ -7530,6 +7451,11 @@ public class WindowManagerService extends IWindowManager.Stub
mVr2dDisplayId = vr2dDisplayId;
}
}
+
+ @Override
+ public void registerDragDropControllerCallback(IDragDropCallback callback) {
+ mDragDropController.registerCallback(callback);
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/com/android/server/wm/WindowManagerShellCommand.java b/com/android/server/wm/WindowManagerShellCommand.java
new file mode 100644
index 00000000..4b98d9d9
--- /dev/null
+++ b/com/android/server/wm/WindowManagerShellCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for WindowManagerService.
+ *
+ * Use with {@code adb shell cmd window ...}.
+ */
+public class WindowManagerShellCommand extends ShellCommand {
+
+ private final WindowManagerService mService;
+
+ public WindowManagerShellCommand(WindowManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ switch (cmd) {
+ case "tracing":
+ return mService.mWindowTracing.onShellCommand(this, getNextArgRequired());
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Window Manager (window) commands:");
+ 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();
+ }
+}
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 4370a763..6b1932d7 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -1849,110 +1849,109 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final long origId = Binder.clearCallingIdentity();
- disposeInputChannel();
+ try {
+ disposeInputChannel();
+
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
+ + ": mSurfaceController=" + mWinAnimator.mSurfaceController
+ + " mAnimatingExit=" + mAnimatingExit
+ + " mRemoveOnExit=" + mRemoveOnExit
+ + " mHasSurface=" + mHasSurface
+ + " surfaceShowing=" + mWinAnimator.getShown()
+ + " isAnimationSet=" + mWinAnimator.isAnimationSet()
+ + " app-animation="
+ + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+ + " mWillReplaceWindow=" + mWillReplaceWindow
+ + " inPendingTransaction="
+ + (mAppToken != null ? mAppToken.inPendingTransaction : false)
+ + " mDisplayFrozen=" + mService.mDisplayFrozen
+ + " callers=" + Debug.getCallers(6));
+
+ // Visibility of the removed window. Will be used later to update orientation later on.
+ boolean wasVisible = false;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
- + ": mSurfaceController=" + mWinAnimator.mSurfaceController
- + " mAnimatingExit=" + mAnimatingExit
- + " mRemoveOnExit=" + mRemoveOnExit
- + " mHasSurface=" + mHasSurface
- + " surfaceShowing=" + mWinAnimator.getShown()
- + " isAnimationSet=" + mWinAnimator.isAnimationSet()
- + " app-animation="
- + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
- + " mWillReplaceWindow=" + mWillReplaceWindow
- + " inPendingTransaction="
- + (mAppToken != null ? mAppToken.inPendingTransaction : false)
- + " mDisplayFrozen=" + mService.mDisplayFrozen
- + " callers=" + Debug.getCallers(6));
-
- // Visibility of the removed window. Will be used later to update orientation later on.
- boolean wasVisible = false;
-
- final int displayId = getDisplayId();
-
- // First, see if we need to run an animation. If we do, we have to hold off on removing the
- // window until the animation is done. If the display is frozen, just remove immediately,
- // since the animation wouldn't be seen.
- if (mHasSurface && mToken.okToAnimate()) {
- if (mWillReplaceWindow) {
- // This window is going to be replaced. We need to keep it around until the new one
- // gets added, then we will get rid of this one.
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Preserving " + this + " until the new one is " + "added");
- // TODO: We are overloading mAnimatingExit flag to prevent the window state from
- // been removed. We probably need another flag to indicate that window removal
- // should be deffered vs. overloading the flag that says we are playing an exit
- // animation.
- mAnimatingExit = true;
- mReplacingRemoveRequested = true;
- Binder.restoreCallingIdentity(origId);
- return;
- }
+ final int displayId = getDisplayId();
- // If we are not currently running the exit animation, we need to see about starting one
- wasVisible = isWinVisibleLw();
+ // First, see if we need to run an animation. If we do, we have to hold off on removing the
+ // window until the animation is done. If the display is frozen, just remove immediately,
+ // since the animation wouldn't be seen.
+ if (mHasSurface && mToken.okToAnimate()) {
+ if (mWillReplaceWindow) {
+ // This window is going to be replaced. We need to keep it around until the new one
+ // gets added, then we will get rid of this one.
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Preserving " + this + " until the new one is " + "added");
+ // TODO: We are overloading mAnimatingExit flag to prevent the window state from
+ // been removed. We probably need another flag to indicate that window removal
+ // should be deffered vs. overloading the flag that says we are playing an exit
+ // animation.
+ mAnimatingExit = true;
+ mReplacingRemoveRequested = true;
+ return;
+ }
- if (keepVisibleDeadWindow) {
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Not removing " + this + " because app died while it's visible");
+ // If we are not currently running the exit animation, we need to see about starting one
+ wasVisible = isWinVisibleLw();
- mAppDied = true;
- setDisplayLayoutNeeded();
- mService.mWindowPlacerLocked.performSurfacePlacement();
+ if (keepVisibleDeadWindow) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Not removing " + this + " because app died while it's visible");
- // Set up a replacement input channel since the app is now dead.
- // We need to catch tapping on the dead window to restart the app.
- openInputChannel(null);
- mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+ mAppDied = true;
+ setDisplayLayoutNeeded();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
- Binder.restoreCallingIdentity(origId);
- return;
- }
+ // Set up a replacement input channel since the app is now dead.
+ // We need to catch tapping on the dead window to restart the app.
+ openInputChannel(null);
+ mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+ return;
+ }
- if (wasVisible) {
- final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
+ if (wasVisible) {
+ final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
- // Try starting an animation.
- if (mWinAnimator.applyAnimationLocked(transit, false)) {
- mAnimatingExit = true;
- }
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
- mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
+ // Try starting an animation.
+ if (mWinAnimator.applyAnimationLocked(transit, false)) {
+ mAnimatingExit = true;
+ }
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+ mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
+ }
}
- }
- final boolean isAnimating =
- mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
- final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
- && mAppToken.isLastWindow(this);
- // We delay the removal of a window if it has a showing surface that can be used to run
- // exit animation and it is marked as exiting.
- // Also, If isn't the an animating starting window that is the last window in the app.
- // We allow the removal of the non-animating starting window now as there is no
- // additional window or animation that will trigger its removal.
- if (mWinAnimator.getShown() && mAnimatingExit
- && (!lastWindowIsStartingWindow || isAnimating)) {
- // The exit animation is running or should run... wait for it!
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Not removing " + this + " due to exit animation ");
- setupWindowForRemoveOnExit();
- if (mAppToken != null) {
- mAppToken.updateReportedVisibilityLocked();
+ final boolean isAnimating =
+ mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+ final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
+ && mAppToken.isLastWindow(this);
+ // We delay the removal of a window if it has a showing surface that can be used to run
+ // exit animation and it is marked as exiting.
+ // Also, If isn't the an animating starting window that is the last window in the app.
+ // We allow the removal of the non-animating starting window now as there is no
+ // additional window or animation that will trigger its removal.
+ if (mWinAnimator.getShown() && mAnimatingExit
+ && (!lastWindowIsStartingWindow || isAnimating)) {
+ // The exit animation is running or should run... wait for it!
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Not removing " + this + " due to exit animation ");
+ setupWindowForRemoveOnExit();
+ if (mAppToken != null) {
+ mAppToken.updateReportedVisibilityLocked();
+ }
+ return;
}
- Binder.restoreCallingIdentity(origId);
- return;
}
- }
- removeImmediately();
- // Removing a visible window will effect the computed orientation
- // So just update orientation if needed.
- if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
- mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+ removeImmediately();
+ // Removing a visible window will effect the computed orientation
+ // So just update orientation if needed.
+ if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+ }
+ mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
- Binder.restoreCallingIdentity(origId);
}
private void setupWindowForRemoveOnExit() {
@@ -2361,7 +2360,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// also reset drag resizing state, because the owner can't do it
// anymore.
final TaskStack stack =
- dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ dc.getSplitScreenPrimaryStackIgnoringVisibility();
if (stack != null) {
stack.resetDockedStackToMiddle();
}
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
index 52669031..86397aea 100644
--- a/com/android/server/wm/WindowStateAnimator.java
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -732,7 +732,7 @@ class WindowStateAnimator {
mSurfaceController.setLayerStackInTransaction(getLayerStack());
mSurfaceController.setLayer(mAnimLayer);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("createSurfaceLocked");
}
mLastHidden = true;
@@ -1711,7 +1711,7 @@ class WindowStateAnimator {
Slog.w(TAG, "Error positioning surface of " + mWin
+ " pos=(" + left + "," + top + ")", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setWallpaperOffset");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setWallpaperOffset");
}
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index edd650a4..a2145230 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -259,7 +259,7 @@ class WindowSurfaceController {
mSurfaceControl.setLayer(layer);
}
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setLayer");
}
}
}
@@ -385,7 +385,7 @@ class WindowSurfaceController {
try {
mSurfaceControl.setTransparentRegionHint(region);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setTransparentRegion");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setTransparentRegion");
}
@@ -403,7 +403,7 @@ class WindowSurfaceController {
try {
mSurfaceControl.setOpaque(isOpaque);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setOpaqueLocked");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
}
}
@@ -420,7 +420,7 @@ class WindowSurfaceController {
try {
mSurfaceControl.setSecure(isSecure);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setSecure");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
}
}
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index d57fdd26..cd5e4750 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -429,7 +429,7 @@ class WindowSurfacePlacer {
try {
mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
}
diff --git a/com/android/server/wm/WindowTracing.java b/com/android/server/wm/WindowTracing.java
new file mode 100644
index 00000000..5657f6c4
--- /dev/null
+++ b/com/android/server/wm/WindowTracing.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.proto.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.proto.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WHERE;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
+
+import android.content.Context;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * time series of window manager state can be analyzed after the fact.
+ */
+class WindowTracing {
+
+ private static final String TAG = "WindowTracing";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Object mLock = new Object();
+ private final File mTraceFile;
+ private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
+
+ private boolean mEnabled;
+ private volatile boolean mEnabledLockFree;
+
+ WindowTracing(File file) {
+ mTraceFile = file;
+ }
+
+ void startTrace(PrintWriter pw) throws IOException {
+ synchronized (mLock) {
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mWriteQueue.clear();
+ mTraceFile.delete();
+ try (OutputStream os = new FileOutputStream(mTraceFile)) {
+ ProtoOutputStream proto = new ProtoOutputStream(os);
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ proto.flush();
+ }
+ mEnabled = mEnabledLockFree = true;
+ }
+ }
+
+ private void logAndPrintln(PrintWriter pw, String msg) {
+ Log.i(TAG, msg);
+ pw.println(msg);
+ pw.flush();
+ }
+
+ void stopTrace(PrintWriter pw) {
+ synchronized (mLock) {
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ mEnabled = mEnabledLockFree = false;
+ while (!mWriteQueue.isEmpty()) {
+ if (mEnabled) {
+ logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+ throw new IllegalStateException("tracing enabled while waiting for flush.");
+ }
+ try {
+ mLock.wait();
+ mLock.notify();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ }
+ }
+
+ void appendTraceEntry(ProtoOutputStream proto) {
+ if (!mEnabledLockFree) {
+ return;
+ }
+
+ if (!mWriteQueue.offer(proto)) {
+ Log.e(TAG, "Dropping window trace entry, queue full");
+ }
+ }
+
+ void loop() {
+ for (;;) {
+ loopOnce();
+ }
+ }
+
+ @VisibleForTesting
+ void loopOnce() {
+ ProtoOutputStream proto;
+ try {
+ proto = mWriteQueue.take();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+
+ synchronized (mLock) {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
+ try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
+ os.write(proto.getBytes());
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file " + mTraceFile, e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ mLock.notify();
+ }
+ }
+
+ boolean isEnabled() {
+ return mEnabledLockFree;
+ }
+
+ static WindowTracing createDefaultAndStartLooper(Context context) {
+ File file = new File("/data/system/window_trace.proto");
+ WindowTracing windowTracing = new WindowTracing(file);
+ new Thread(windowTracing::loop, "window_tracing").start();
+ return windowTracing;
+ }
+
+ int onShellCommand(ShellCommand shell, String cmd) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "start":
+ startTrace(pw);
+ return 0;
+ case "stop":
+ stopTrace(pw);
+ return 0;
+ default:
+ pw.println("Unknown command: " + cmd);
+ return -1;
+ }
+ } catch (IOException e) {
+ logAndPrintln(pw, e.toString());
+ throw new RuntimeException(e);
+ }
+ }
+
+ void traceStateLocked(String where, WindowManagerService service) {
+ if (!isEnabled()) {
+ return;
+ }
+ ProtoOutputStream os = new ProtoOutputStream();
+ long tokenOuter = os.start(ENTRY);
+ os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ os.write(WHERE, where);
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
+ try {
+ long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+ service.writeToProtoLocked(os, true /* trim */);
+ os.end(tokenInner);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ os.end(tokenOuter);
+ appendTraceEntry(os);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+}
diff --git a/com/android/settingslib/CustomEditTextPreference.java b/com/android/settingslib/CustomEditTextPreference.java
index 90124f1f..8e29e50f 100644
--- a/com/android/settingslib/CustomEditTextPreference.java
+++ b/com/android/settingslib/CustomEditTextPreference.java
@@ -49,8 +49,13 @@ public class CustomEditTextPreference extends EditTextPreference {
}
public EditText getEditText() {
- return mFragment != null ? (EditText) mFragment.getDialog().findViewById(android.R.id.edit)
- : null;
+ if (mFragment != null) {
+ final Dialog dialog = mFragment.getDialog();
+ if (dialog != null) {
+ return (EditText) dialog.findViewById(android.R.id.edit);
+ }
+ }
+ return null;
}
public boolean isDialogOpen() {
diff --git a/com/android/settingslib/DeviceInfoUtils.java b/com/android/settingslib/DeviceInfoUtils.java
index 78a9064f..f2cd1033 100644
--- a/com/android/settingslib/DeviceInfoUtils.java
+++ b/com/android/settingslib/DeviceInfoUtils.java
@@ -22,12 +22,15 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
+import android.system.Os;
+import android.system.StructUtsname;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
+import android.support.annotation.VisibleForTesting;
import java.io.BufferedReader;
import java.io.FileReader;
@@ -45,7 +48,6 @@ import static android.content.Context.TELEPHONY_SERVICE;
public class DeviceInfoUtils {
private static final String TAG = "DeviceInfoUtils";
- private static final String FILENAME_PROC_VERSION = "/proc/version";
private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
/**
@@ -63,43 +65,36 @@ public class DeviceInfoUtils {
}
}
- public static String getFormattedKernelVersion() {
- try {
- return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
- } catch (IOException e) {
- Log.e(TAG, "IO Exception when getting kernel version for Device Info screen",
- e);
-
- return "Unavailable";
- }
+ public static String getFormattedKernelVersion(Context context) {
+ return formatKernelVersion(context, Os.uname());
}
- public static String formatKernelVersion(String rawKernelVersion) {
- // Example (see tests for more):
- // Linux version 4.9.29-g958411d (android-build@xyz) (Android clang version 3.8.256229 \
- // (based on LLVM 3.8.256229)) #1 SMP PREEMPT Wed Jun 7 00:06:03 CST 2017
- // Linux version 4.9.29-geb63318482a7 (android-build@xyz) (gcc version 4.9.x 20150123 \
- // (prerelease) (GCC) ) #1 SMP PREEMPT Thu Jun 1 03:41:57 UTC 2017
- final String PROC_VERSION_REGEX =
- "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
- "\\((\\S+?)\\) " + /* group 2: "(x@y.com) " */
- "\\((.+?)\\) " + /* group 3: kernel toolchain version information */
- "(#\\d+) " + /* group 4: "#1" */
+ @VisibleForTesting
+ static String formatKernelVersion(Context context, StructUtsname uname) {
+ if (uname == null) {
+ return context.getString(R.string.status_unavailable);
+ }
+ // Example:
+ // 4.9.29-g958411d
+ // #1 SMP PREEMPT Wed Jun 7 00:06:03 CST 2017
+ final String VERSION_REGEX =
+ "(#\\d+) " + /* group 1: "#1" */
"(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
- "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 5: "Thu Jun 28 11:02:39 PDT 2012" */
-
- Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
+ "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 2: "Thu Jun 28 11:02:39 PDT 2012" */
+ Matcher m = Pattern.compile(VERSION_REGEX).matcher(uname.version);
if (!m.matches()) {
- Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
- return "Unavailable";
- } else if (m.groupCount() < 4) {
- Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
- + " groups");
- return "Unavailable";
+ Log.e(TAG, "Regex did not match on uname version " + uname.version);
+ return context.getString(R.string.status_unavailable);
}
- return m.group(1) + " ("+ m.group(3) + ")\n" + // 3.0.31-g6fb96c9 (toolchain version)
- m.group(2) + " " + m.group(4) + "\n" + // x@y.com #1
- m.group(5); // Thu Jun 28 11:02:39 PDT 2012
+
+ // Example output:
+ // 4.9.29-g958411d
+ // #1 Wed Jun 7 00:06:03 CST 2017
+ return new StringBuilder().append(uname.release)
+ .append("\n")
+ .append(m.group(1))
+ .append(" ")
+ .append(m.group(2)).toString();
}
/**
diff --git a/com/android/settingslib/RestrictedLockUtils.java b/com/android/settingslib/RestrictedLockUtils.java
index 32e6389d..c3a36e97 100644
--- a/com/android/settingslib/RestrictedLockUtils.java
+++ b/com/android/settingslib/RestrictedLockUtils.java
@@ -28,6 +28,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
@@ -343,7 +344,8 @@ public class RestrictedLockUtils {
}
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);
- if (dpm == null) {
+ PackageManager pm = context.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) || dpm == null) {
return null;
}
boolean isAccountTypeDisabled = false;
diff --git a/com/android/settingslib/TwoTargetPreference.java b/com/android/settingslib/TwoTargetPreference.java
index 1c161dff..8b39f60a 100644
--- a/com/android/settingslib/TwoTargetPreference.java
+++ b/com/android/settingslib/TwoTargetPreference.java
@@ -21,41 +21,56 @@ import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
public class TwoTargetPreference extends Preference {
+ private boolean mUseSmallIcon;
+ private int mSmallIconSize;
+
public TwoTargetPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- init();
+ init(context);
}
public TwoTargetPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- init();
+ init(context);
}
public TwoTargetPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- init();
+ init(context);
}
public TwoTargetPreference(Context context) {
super(context);
- init();
+ init(context);
}
- private void init() {
+ private void init(Context context) {
setLayoutResource(R.layout.preference_two_target);
+ mSmallIconSize = context.getResources().getDimensionPixelSize(
+ R.dimen.two_target_pref_small_icon_size);
final int secondTargetResId = getSecondTargetResId();
if (secondTargetResId != 0) {
setWidgetLayoutResource(secondTargetResId);
}
}
+ public void setUseSmallIcon(boolean useSmallIcon) {
+ mUseSmallIcon = useSmallIcon;
+ }
+
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ if (mUseSmallIcon) {
+ ImageView icon = holder.itemView.findViewById(android.R.id.icon);
+ icon.setLayoutParams(new LinearLayout.LayoutParams(mSmallIconSize, mSmallIconSize));
+ }
final View divider = holder.findViewById(R.id.two_target_divider);
final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
final boolean shouldHideSecondTarget = shouldHideSecondTarget();
diff --git a/com/android/settingslib/Utils.java b/com/android/settingslib/Utils.java
index d90386f9..21861692 100644
--- a/com/android/settingslib/Utils.java
+++ b/com/android/settingslib/Utils.java
@@ -94,7 +94,7 @@ public class Utils {
public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
final int iconSize = UserIconDrawable.getSizeForList(context);
if (user.isManagedProfile()) {
- Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_icon);
+ Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_badge);
drawable.setBounds(0, 0, iconSize, iconSize);
return drawable;
}
diff --git a/com/android/settingslib/bluetooth/PanProfile.java b/com/android/settingslib/bluetooth/PanProfile.java
index 7bda2314..3299cb2d 100644
--- a/com/android/settingslib/bluetooth/PanProfile.java
+++ b/com/android/settingslib/bluetooth/PanProfile.java
@@ -32,7 +32,7 @@ import java.util.List;
/**
* PanProfile handles Bluetooth PAN profile (NAP and PANU).
*/
-public final class PanProfile implements LocalBluetoothProfile {
+public class PanProfile implements LocalBluetoothProfile {
private static final String TAG = "PanProfile";
private static boolean V = true;
diff --git a/com/android/settingslib/bluetooth/Utils.java b/com/android/settingslib/bluetooth/Utils.java
index c9194268..0ee1dad9 100644
--- a/com/android/settingslib/bluetooth/Utils.java
+++ b/com/android/settingslib/bluetooth/Utils.java
@@ -1,9 +1,17 @@
package com.android.settingslib.bluetooth;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.util.Pair;
import com.android.settingslib.R;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
+
+import java.util.List;
public class Utils {
public static final boolean V = false; // verbose logging
@@ -40,4 +48,78 @@ public class Utils {
void onShowError(Context context, String name, int messageResId);
}
+ public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+ CachedBluetoothDevice cachedDevice) {
+ return getBtClassDrawableWithDescription(context, cachedDevice, 1 /* iconScale */);
+ }
+
+ public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+ CachedBluetoothDevice cachedDevice, float iconScale) {
+ BluetoothClass btClass = cachedDevice.getBtClass();
+ final int level = cachedDevice.getBatteryLevel();
+ if (btClass != null) {
+ switch (btClass.getMajorDeviceClass()) {
+ case BluetoothClass.Device.Major.COMPUTER:
+ return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_computer));
+
+ case BluetoothClass.Device.Major.PHONE:
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_phone));
+
+ case BluetoothClass.Device.Major.PERIPHERAL:
+ return new Pair<>(
+ getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass),
+ level, iconScale),
+ context.getString(R.string.bluetooth_talkback_input_peripheral));
+
+ case BluetoothClass.Device.Major.IMAGING:
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_settings_print, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_imaging));
+
+ default:
+ // unrecognized device class; continue
+ }
+ }
+
+ List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+ for (LocalBluetoothProfile profile : profiles) {
+ int resId = profile.getDrawableResource(btClass);
+ if (resId != 0) {
+ return new Pair<>(getBluetoothDrawable(context, resId, level, iconScale), null);
+ }
+ }
+ if (btClass != null) {
+ if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_headset));
+ }
+ if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_headphone));
+ }
+ }
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale),
+ context.getString(R.string.bluetooth_talkback_bluetooth));
+ }
+
+ public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId,
+ int batteryLevel, float iconScale) {
+ if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ return BluetoothDeviceLayerDrawable.createLayerDrawable(context, resId, batteryLevel,
+ iconScale);
+ } else {
+ return context.getDrawable(resId);
+ }
+ }
}
diff --git a/com/android/settingslib/core/AbstractPreferenceController.java b/com/android/settingslib/core/AbstractPreferenceController.java
index 38fe8790..88f0d2be 100644
--- a/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/com/android/settingslib/core/AbstractPreferenceController.java
@@ -10,79 +10,63 @@ import android.support.v7.preference.PreferenceScreen;
*/
public abstract class AbstractPreferenceController {
- protected final Context mContext;
+ protected final Context mContext;
- public AbstractPreferenceController(Context context) {
- mContext = context;
- }
+ public AbstractPreferenceController(Context context) {
+ mContext = context;
+ }
- /**
- * Displays preference in this controller.
- */
- public void displayPreference(PreferenceScreen screen) {
- if (isAvailable()) {
- if (this instanceof Preference.OnPreferenceChangeListener) {
- final Preference preference = screen.findPreference(getPreferenceKey());
- preference.setOnPreferenceChangeListener(
- (Preference.OnPreferenceChangeListener) this);
- }
- } else {
- removePreference(screen, getPreferenceKey());
- }
- }
+ /**
+ * Displays preference in this controller.
+ */
+ public void displayPreference(PreferenceScreen screen) {
+ final String prefKey = getPreferenceKey();
+ if (isAvailable()) {
+ setVisible(screen, prefKey, true /* visible */);
+ if (this instanceof Preference.OnPreferenceChangeListener) {
+ final Preference preference = screen.findPreference(prefKey);
+ preference.setOnPreferenceChangeListener(
+ (Preference.OnPreferenceChangeListener) this);
+ }
+ } else {
+ setVisible(screen, prefKey, false /* visible */);
+ }
+ }
- /**
- * Updates the current status of preference (summary, switch state, etc)
- */
- public void updateState(Preference preference) {
+ /**
+ * Updates the current status of preference (summary, switch state, etc)
+ */
+ public void updateState(Preference preference) {
- }
+ }
- /**
- * Returns true if preference is available (should be displayed)
- */
- public abstract boolean isAvailable();
+ /**
+ * Returns true if preference is available (should be displayed)
+ */
+ public abstract boolean isAvailable();
- /**
- * Handles preference tree click
- *
- * @param preference the preference being clicked
- * @return true if click is handled
- */
- public boolean handlePreferenceTreeClick(Preference preference) {
- return false;
- }
+ /**
+ * Handles preference tree click
+ *
+ * @param preference the preference being clicked
+ * @return true if click is handled
+ */
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ return false;
+ }
- /**
- * Returns the key for this preference.
- */
- public abstract String getPreferenceKey();
-
- /**
- * Removes preference from screen.
- */
- protected final void removePreference(PreferenceScreen screen, String key) {
- findAndRemovePreference(screen, key);
- }
-
- // finds the preference recursively and removes it from its parent
- private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
- final int preferenceCount = prefGroup.getPreferenceCount();
- for (int i = 0; i < preferenceCount; i++) {
- final Preference preference = prefGroup.getPreference(i);
- final String curKey = preference.getKey();
-
- if (curKey != null && curKey.equals(key)) {
- return prefGroup.removePreference(preference);
- }
-
- if (preference instanceof PreferenceGroup) {
- if (findAndRemovePreference((PreferenceGroup) preference, key)) {
- return true;
- }
- }
- }
- return false;
- }
+ /**
+ * Returns the key for this preference.
+ */
+ public abstract String getPreferenceKey();
+ /**
+ * Show/hide a preference.
+ */
+ protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
+ final Preference pref = group.findPreference(key);
+ if (pref != null) {
+ pref.setVisible(isVisible);
+ }
+ }
}
diff --git a/com/android/settingslib/core/lifecycle/Lifecycle.java b/com/android/settingslib/core/lifecycle/Lifecycle.java
index b2351a9e..451e5611 100644
--- a/com/android/settingslib/core/lifecycle/Lifecycle.java
+++ b/com/android/settingslib/core/lifecycle/Lifecycle.java
@@ -15,11 +15,18 @@
*/
package com.android.settingslib.core.lifecycle;
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+
import android.annotation.UiThread;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.LifecycleRegistry;
+import android.arch.lifecycle.OnLifecycleEvent;
import android.content.Context;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -44,18 +51,46 @@ import java.util.List;
/**
* Dispatcher for lifecycle events.
*/
-public class Lifecycle {
+public class Lifecycle extends LifecycleRegistry {
+ private static final String TAG = "LifecycleObserver";
+
+ private final List<LifecycleObserver> mObservers = new ArrayList<>();
+ private final LifecycleProxy mProxy = new LifecycleProxy();
- protected final List<LifecycleObserver> mObservers = new ArrayList<>();
+ /**
+ * Creates a new LifecycleRegistry for the given provider.
+ * <p>
+ * You should usually create this inside your LifecycleOwner class's constructor and hold
+ * onto the same instance.
+ *
+ * @param provider The owner LifecycleOwner
+ */
+ public Lifecycle(@NonNull LifecycleOwner provider) {
+ super(provider);
+ addObserver(mProxy);
+ }
/**
* Registers a new observer of lifecycle events.
*/
@UiThread
- public <T extends LifecycleObserver> T addObserver(T observer) {
+ @Override
+ public void addObserver(android.arch.lifecycle.LifecycleObserver observer) {
ThreadUtils.ensureMainThread();
- mObservers.add(observer);
- return observer;
+ super.addObserver(observer);
+ if (observer instanceof LifecycleObserver) {
+ mObservers.add((LifecycleObserver) observer);
+ }
+ }
+
+ @UiThread
+ @Override
+ public void removeObserver(android.arch.lifecycle.LifecycleObserver observer) {
+ ThreadUtils.ensureMainThread();
+ super.removeObserver(observer);
+ if (observer instanceof LifecycleObserver) {
+ mObservers.remove(observer);
+ }
}
public void onAttach(Context context) {
@@ -67,6 +102,8 @@ public class Lifecycle {
}
}
+ // This method is not called from the proxy because it does not have access to the
+ // savedInstanceState
public void onCreate(Bundle savedInstanceState) {
for (int i = 0, size = mObservers.size(); i < size; i++) {
final LifecycleObserver observer = mObservers.get(i);
@@ -76,7 +113,7 @@ public class Lifecycle {
}
}
- public void onStart() {
+ private void onStart() {
for (int i = 0, size = mObservers.size(); i < size; i++) {
final LifecycleObserver observer = mObservers.get(i);
if (observer instanceof OnStart) {
@@ -94,7 +131,7 @@ public class Lifecycle {
}
}
- public void onResume() {
+ private void onResume() {
for (int i = 0, size = mObservers.size(); i < size; i++) {
final LifecycleObserver observer = mObservers.get(i);
if (observer instanceof OnResume) {
@@ -103,7 +140,7 @@ public class Lifecycle {
}
}
- public void onPause() {
+ private void onPause() {
for (int i = 0, size = mObservers.size(); i < size; i++) {
final LifecycleObserver observer = mObservers.get(i);
if (observer instanceof OnPause) {
@@ -121,7 +158,7 @@ public class Lifecycle {
}
}
- public void onStop() {
+ private void onStop() {
for (int i = 0, size = mObservers.size(); i < size; i++) {
final LifecycleObserver observer = mObservers.get(i);
if (observer instanceof OnStop) {
@@ -130,7 +167,7 @@ public class Lifecycle {
}
}
- public void onDestroy() {
+ private void onDestroy() {
for (int i = 0, size = mObservers.size(); i < size; i++) {
final LifecycleObserver observer = mObservers.get(i);
if (observer instanceof OnDestroy) {
@@ -168,4 +205,34 @@ public class Lifecycle {
}
return false;
}
+
+ private class LifecycleProxy
+ implements android.arch.lifecycle.LifecycleObserver {
+ @OnLifecycleEvent(ON_ANY)
+ public void onLifecycleEvent(LifecycleOwner owner, Event event) {
+ switch (event) {
+ case ON_CREATE:
+ // onCreate is called directly since we don't have savedInstanceState here
+ break;
+ case ON_START:
+ onStart();
+ break;
+ case ON_RESUME:
+ onResume();
+ break;
+ case ON_PAUSE:
+ onPause();
+ break;
+ case ON_STOP:
+ onStop();
+ break;
+ case ON_DESTROY:
+ onDestroy();
+ break;
+ case ON_ANY:
+ Log.wtf(TAG, "Should not receive an 'ANY' event!");
+ break;
+ }
+ }
+ }
}
diff --git a/com/android/settingslib/core/lifecycle/LifecycleObserver.java b/com/android/settingslib/core/lifecycle/LifecycleObserver.java
index 6c410729..ec8a8b53 100644
--- a/com/android/settingslib/core/lifecycle/LifecycleObserver.java
+++ b/com/android/settingslib/core/lifecycle/LifecycleObserver.java
@@ -17,6 +17,9 @@ package com.android.settingslib.core.lifecycle;
/**
* Observer of lifecycle events.
+ * @deprecated use {@link android.arch.lifecycle.LifecycleObserver} instead
*/
-public interface LifecycleObserver {
+@Deprecated
+public interface LifecycleObserver extends
+ android.arch.lifecycle.LifecycleObserver {
}
diff --git a/com/android/settingslib/core/lifecycle/ObservableActivity.java b/com/android/settingslib/core/lifecycle/ObservableActivity.java
index 727bec75..8b062f84 100644
--- a/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -15,8 +15,16 @@
*/
package com.android.settingslib.core.lifecycle;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
import android.annotation.Nullable;
import android.app.Activity;
+import android.arch.lifecycle.LifecycleOwner;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Menu;
@@ -25,17 +33,19 @@ import android.view.MenuItem;
/**
* {@link Activity} that has hooks to observe activity lifecycle events.
*/
-public class ObservableActivity extends Activity {
+public class ObservableActivity extends Activity implements LifecycleOwner {
- private final Lifecycle mLifecycle = new Lifecycle();
+ private final Lifecycle mLifecycle = new Lifecycle(this);
- protected Lifecycle getLifecycle() {
+ public Lifecycle getLifecycle() {
return mLifecycle;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mLifecycle.onAttach(this);
+ mLifecycle.onCreate(savedInstanceState);
+ mLifecycle.handleLifecycleEvent(ON_CREATE);
super.onCreate(savedInstanceState);
}
@@ -43,36 +53,38 @@ public class ObservableActivity extends Activity {
public void onCreate(@Nullable Bundle savedInstanceState,
@Nullable PersistableBundle persistentState) {
mLifecycle.onAttach(this);
+ mLifecycle.onCreate(savedInstanceState);
+ mLifecycle.handleLifecycleEvent(ON_CREATE);
super.onCreate(savedInstanceState, persistentState);
}
@Override
protected void onStart() {
- mLifecycle.onStart();
+ mLifecycle.handleLifecycleEvent(ON_START);
super.onStart();
}
@Override
protected void onResume() {
- mLifecycle.onResume();
+ mLifecycle.handleLifecycleEvent(ON_RESUME);
super.onResume();
}
@Override
protected void onPause() {
- mLifecycle.onPause();
+ mLifecycle.handleLifecycleEvent(ON_PAUSE);
super.onPause();
}
@Override
protected void onStop() {
- mLifecycle.onStop();
+ mLifecycle.handleLifecycleEvent(ON_STOP);
super.onStop();
}
@Override
protected void onDestroy() {
- mLifecycle.onDestroy();
+ mLifecycle.handleLifecycleEvent(ON_DESTROY);
super.onDestroy();
}
diff --git a/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
index 315bedc1..dc95384b 100644
--- a/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
+++ b/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
@@ -15,9 +15,17 @@
*/
package com.android.settingslib.core.lifecycle;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
import android.app.DialogFragment;
+import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
-import android.support.annotation.VisibleForTesting;
+import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -25,9 +33,9 @@ import android.view.MenuItem;
/**
* {@link DialogFragment} that has hooks to observe fragment lifecycle events.
*/
-public class ObservableDialogFragment extends DialogFragment {
+public class ObservableDialogFragment extends DialogFragment implements LifecycleOwner {
- protected final Lifecycle mLifecycle = createLifecycle();
+ protected final Lifecycle mLifecycle = new Lifecycle(this);
@Override
public void onAttach(Context context) {
@@ -36,32 +44,39 @@ public class ObservableDialogFragment extends DialogFragment {
}
@Override
+ public void onCreate(Bundle savedInstanceState) {
+ mLifecycle.onCreate(savedInstanceState);
+ mLifecycle.handleLifecycleEvent(ON_CREATE);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
public void onStart() {
- mLifecycle.onStart();
+ mLifecycle.handleLifecycleEvent(ON_START);
super.onStart();
}
@Override
public void onResume() {
- mLifecycle.onResume();
+ mLifecycle.handleLifecycleEvent(ON_RESUME);
super.onResume();
}
@Override
public void onPause() {
- mLifecycle.onPause();
+ mLifecycle.handleLifecycleEvent(ON_PAUSE);
super.onPause();
}
@Override
public void onStop() {
- mLifecycle.onStop();
+ mLifecycle.handleLifecycleEvent(ON_STOP);
super.onStop();
}
@Override
public void onDestroy() {
- mLifecycle.onDestroy();
+ mLifecycle.handleLifecycleEvent(ON_DESTROY);
super.onDestroy();
}
@@ -86,9 +101,8 @@ public class ObservableDialogFragment extends DialogFragment {
return lifecycleHandled;
}
- @VisibleForTesting(otherwise = VisibleForTesting.NONE)
- /** @return a new lifecycle. */
- public static Lifecycle createLifecycle() {
- return new Lifecycle();
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
}
}
diff --git a/com/android/settingslib/core/lifecycle/ObservableFragment.java b/com/android/settingslib/core/lifecycle/ObservableFragment.java
index 3a00eba6..925eda6e 100644
--- a/com/android/settingslib/core/lifecycle/ObservableFragment.java
+++ b/com/android/settingslib/core/lifecycle/ObservableFragment.java
@@ -16,19 +16,27 @@
package com.android.settingslib.core.lifecycle;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
import android.annotation.CallSuper;
import android.app.Fragment;
+import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-public class ObservableFragment extends Fragment {
+public class ObservableFragment extends Fragment implements LifecycleOwner {
- private final Lifecycle mLifecycle = new Lifecycle();
+ private final Lifecycle mLifecycle = new Lifecycle(this);
- protected Lifecycle getLifecycle() {
+ public Lifecycle getLifecycle() {
return mLifecycle;
}
@@ -43,6 +51,7 @@ public class ObservableFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
mLifecycle.onCreate(savedInstanceState);
+ mLifecycle.handleLifecycleEvent(ON_CREATE);
super.onCreate(savedInstanceState);
}
@@ -56,35 +65,35 @@ public class ObservableFragment extends Fragment {
@CallSuper
@Override
public void onStart() {
- mLifecycle.onStart();
+ mLifecycle.handleLifecycleEvent(ON_START);
super.onStart();
}
@CallSuper
@Override
- public void onStop() {
- mLifecycle.onStop();
- super.onStop();
- }
-
- @CallSuper
- @Override
public void onResume() {
- mLifecycle.onResume();
+ mLifecycle.handleLifecycleEvent(ON_RESUME);
super.onResume();
}
@CallSuper
@Override
public void onPause() {
- mLifecycle.onPause();
+ mLifecycle.handleLifecycleEvent(ON_PAUSE);
super.onPause();
}
@CallSuper
@Override
+ public void onStop() {
+ mLifecycle.handleLifecycleEvent(ON_STOP);
+ super.onStop();
+ }
+
+ @CallSuper
+ @Override
public void onDestroy() {
- mLifecycle.onDestroy();
+ mLifecycle.handleLifecycleEvent(ON_DESTROY);
super.onDestroy();
}
diff --git a/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
index 76e5c857..abd77559 100644
--- a/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ b/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -16,7 +16,15 @@
package com.android.settingslib.core.lifecycle;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
import android.annotation.CallSuper;
+import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.support.v14.preference.PreferenceFragment;
@@ -28,11 +36,12 @@ import android.view.MenuItem;
/**
* {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
*/
-public abstract class ObservablePreferenceFragment extends PreferenceFragment {
+public abstract class ObservablePreferenceFragment extends PreferenceFragment
+ implements LifecycleOwner {
- private final Lifecycle mLifecycle = new Lifecycle();
+ private final Lifecycle mLifecycle = new Lifecycle(this);
- protected Lifecycle getLifecycle() {
+ public Lifecycle getLifecycle() {
return mLifecycle;
}
@@ -47,6 +56,7 @@ public abstract class ObservablePreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
mLifecycle.onCreate(savedInstanceState);
+ mLifecycle.handleLifecycleEvent(ON_CREATE);
super.onCreate(savedInstanceState);
}
@@ -66,35 +76,35 @@ public abstract class ObservablePreferenceFragment extends PreferenceFragment {
@CallSuper
@Override
public void onStart() {
- mLifecycle.onStart();
+ mLifecycle.handleLifecycleEvent(ON_START);
super.onStart();
}
@CallSuper
@Override
- public void onStop() {
- mLifecycle.onStop();
- super.onStop();
- }
-
- @CallSuper
- @Override
public void onResume() {
- mLifecycle.onResume();
+ mLifecycle.handleLifecycleEvent(ON_RESUME);
super.onResume();
}
@CallSuper
@Override
public void onPause() {
- mLifecycle.onPause();
+ mLifecycle.handleLifecycleEvent(ON_PAUSE);
super.onPause();
}
@CallSuper
@Override
+ public void onStop() {
+ mLifecycle.handleLifecycleEvent(ON_STOP);
+ super.onStop();
+ }
+
+ @CallSuper
+ @Override
public void onDestroy() {
- mLifecycle.onDestroy();
+ mLifecycle.handleLifecycleEvent(ON_DESTROY);
super.onDestroy();
}
diff --git a/com/android/settingslib/core/lifecycle/events/OnAttach.java b/com/android/settingslib/core/lifecycle/events/OnAttach.java
index 152cbac3..e28c3873 100644
--- a/com/android/settingslib/core/lifecycle/events/OnAttach.java
+++ b/com/android/settingslib/core/lifecycle/events/OnAttach.java
@@ -17,6 +17,10 @@ package com.android.settingslib.core.lifecycle.events;
import android.content.Context;
+/**
+ * @deprecated pass {@link Context} in constructor instead
+ */
+@Deprecated
public interface OnAttach {
void onAttach(Context context);
}
diff --git a/com/android/settingslib/core/lifecycle/events/OnCreate.java b/com/android/settingslib/core/lifecycle/events/OnCreate.java
index 44cbf8d2..ad7068e2 100644
--- a/com/android/settingslib/core/lifecycle/events/OnCreate.java
+++ b/com/android/settingslib/core/lifecycle/events/OnCreate.java
@@ -16,8 +16,14 @@
package com.android.settingslib.core.lifecycle.events;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
import android.os.Bundle;
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
public interface OnCreate {
void onCreate(Bundle savedInstanceState);
}
diff --git a/com/android/settingslib/core/lifecycle/events/OnDestroy.java b/com/android/settingslib/core/lifecycle/events/OnDestroy.java
index ffa3d168..c37286e1 100644
--- a/com/android/settingslib/core/lifecycle/events/OnDestroy.java
+++ b/com/android/settingslib/core/lifecycle/events/OnDestroy.java
@@ -15,6 +15,13 @@
*/
package com.android.settingslib.core.lifecycle.events;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
public interface OnDestroy {
void onDestroy();
}
diff --git a/com/android/settingslib/core/lifecycle/events/OnPause.java b/com/android/settingslib/core/lifecycle/events/OnPause.java
index 4a711058..a5ab39c4 100644
--- a/com/android/settingslib/core/lifecycle/events/OnPause.java
+++ b/com/android/settingslib/core/lifecycle/events/OnPause.java
@@ -15,6 +15,13 @@
*/
package com.android.settingslib.core.lifecycle.events;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
public interface OnPause {
void onPause();
}
diff --git a/com/android/settingslib/core/lifecycle/events/OnResume.java b/com/android/settingslib/core/lifecycle/events/OnResume.java
index 8dd24e98..1effba4a 100644
--- a/com/android/settingslib/core/lifecycle/events/OnResume.java
+++ b/com/android/settingslib/core/lifecycle/events/OnResume.java
@@ -15,6 +15,13 @@
*/
package com.android.settingslib.core.lifecycle.events;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event)}
+ */
+@Deprecated
public interface OnResume {
void onResume();
}
diff --git a/com/android/settingslib/core/lifecycle/events/OnStart.java b/com/android/settingslib/core/lifecycle/events/OnStart.java
index c88ddaa1..07b84603 100644
--- a/com/android/settingslib/core/lifecycle/events/OnStart.java
+++ b/com/android/settingslib/core/lifecycle/events/OnStart.java
@@ -15,7 +15,13 @@
*/
package com.android.settingslib.core.lifecycle.events;
-public interface OnStart {
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
+public interface OnStart {
void onStart();
}
diff --git a/com/android/settingslib/core/lifecycle/events/OnStop.java b/com/android/settingslib/core/lifecycle/events/OnStop.java
index 32f61d98..d6a59671 100644
--- a/com/android/settingslib/core/lifecycle/events/OnStop.java
+++ b/com/android/settingslib/core/lifecycle/events/OnStop.java
@@ -15,7 +15,13 @@
*/
package com.android.settingslib.core.lifecycle.events;
-public interface OnStop {
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
+public interface OnStop {
void onStop();
}
diff --git a/com/android/settingslib/datetime/ZoneGetter.java b/com/android/settingslib/datetime/ZoneGetter.java
index 17712083..a8262c8c 100644
--- a/com/android/settingslib/datetime/ZoneGetter.java
+++ b/com/android/settingslib/datetime/ZoneGetter.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.XmlResourceParser;
import android.icu.text.TimeZoneFormat;
import android.icu.text.TimeZoneNames;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.text.BidiFormatter;
import android.support.v4.text.TextDirectionHeuristicsCompat;
import android.text.SpannableString;
@@ -32,6 +33,8 @@ import android.view.View;
import com.android.settingslib.R;
+import libcore.util.TimeZoneFinder;
+
import org.xmlpull.v1.XmlPullParserException;
import java.util.ArrayList;
@@ -349,7 +352,8 @@ public class ZoneGetter {
return gmtText;
}
- private static final class ZoneGetterData {
+ @VisibleForTesting
+ public static final class ZoneGetterData {
public final String[] olsonIdsToDisplay;
public final CharSequence[] gmtOffsetTexts;
public final TimeZone[] timeZones;
@@ -376,10 +380,13 @@ public class ZoneGetter {
}
// Create a lookup of local zone IDs.
- localZoneIds = new HashSet<String>();
- for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
- localZoneIds.add(olsonId);
- }
+ final List<String> zoneIds = lookupTimeZoneIdsByCountry(locale.getCountry());
+ localZoneIds = new HashSet<>(zoneIds);
+ }
+
+ @VisibleForTesting
+ public List<String> lookupTimeZoneIdsByCountry(String country) {
+ return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country);
}
}
}
diff --git a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 6aae226b..3c02f6a2 100644
--- a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -16,11 +16,13 @@
package com.android.settingslib.development;
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.UserManager;
import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.SwitchPreference;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.preference.Preference;
@@ -95,6 +97,10 @@ public abstract class AbstractEnableAdbPreferenceController extends
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
+ if (isUserAMonkey()) {
+ return false;
+ }
+
if (TextUtils.equals(KEY_ENABLE_ADB, preference.getKey())) {
if (!isAdbEnabled()) {
showConfirmationDialog(preference);
@@ -117,4 +123,9 @@ public abstract class AbstractEnableAdbPreferenceController extends
LocalBroadcastManager.getInstance(mContext)
.sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED));
}
+
+ @VisibleForTesting
+ boolean isUserAMonkey() {
+ return ActivityManager.isUserAMonkey();
+ }
}
diff --git a/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
index ff7536ad..90f14ef4 100644
--- a/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
+++ b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
@@ -18,18 +18,20 @@ package com.android.settingslib.deviceinfo;
import android.content.Context;
import android.os.Build;
+import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.core.AbstractPreferenceController;
/**
* Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
*/
public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
- private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+ @VisibleForTesting
+ static final String KEY_SERIAL_NUMBER = "serial_number";
private final String mSerialNumber;
diff --git a/com/android/settingslib/drawer/UserAdapter.java b/com/android/settingslib/drawer/UserAdapter.java
index 750601d8..b27d8235 100644
--- a/com/android/settingslib/drawer/UserAdapter.java
+++ b/com/android/settingslib/drawer/UserAdapter.java
@@ -57,7 +57,7 @@ public class UserAdapter implements SpinnerAdapter, ListAdapter {
if (userInfo.isManagedProfile()) {
mName = context.getString(R.string.managed_user_title);
icon = context.getDrawable(
- com.android.internal.R.drawable.ic_corp_icon);
+ com.android.internal.R.drawable.ic_corp_badge);
} else {
mName = userInfo.name;
final int userId = userInfo.id;
diff --git a/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index f4c9bb31..4fe9d56a 100644
--- a/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -16,7 +16,6 @@
package com.android.settingslib.graph;
-import android.animation.ArgbEvaluator;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
@@ -100,7 +99,7 @@ public class BatteryMeterDrawableBase extends Drawable {
final int N = levels.length();
mColors = new int[2 * N];
- for (int i=0; i < N; i++) {
+ for (int i = 0; i < N; i++) {
mColors[2 * i] = levels.getInt(i, 0);
if (colors.getType(i) == TypedValue.TYPE_ATTRIBUTE) {
mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getThemeAttributeId(i, 0));
diff --git a/com/android/settingslib/wifi/AccessPointPreference.java b/com/android/settingslib/wifi/AccessPointPreference.java
index fdbbf14a..dd55188e 100644
--- a/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/com/android/settingslib/wifi/AccessPointPreference.java
@@ -31,15 +31,17 @@ import android.support.v7.preference.PreferenceViewHolder;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
+import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settingslib.R;
import com.android.settingslib.TronUtils;
+import com.android.settingslib.TwoTargetPreference;
import com.android.settingslib.Utils;
import com.android.settingslib.wifi.AccessPoint.Speed;
-public class AccessPointPreference extends Preference {
+public class AccessPointPreference extends TwoTargetPreference {
private static final int[] STATE_SECURED = {
R.attr.state_encrypted
@@ -126,7 +128,6 @@ public class AccessPointPreference extends Preference {
int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld,
int level, IconInjector iconInjector) {
super(context);
- setWidgetLayoutResource(R.layout.access_point_friction_widget);
mBadgeCache = cache;
mAccessPoint = accessPoint;
mForSavedNetworks = forSavedNetworks;
@@ -165,6 +166,20 @@ public class AccessPointPreference extends Preference {
ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
bindFrictionImage(frictionImageView);
+ setDividerVisibility(view, View.GONE);
+ }
+
+ protected void setDividerVisibility(final PreferenceViewHolder view,
+ @View.Visibility int visibility) {
+ final View divider = view.findViewById(R.id.two_target_divider);
+ if (divider != null) {
+ divider.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ protected int getSecondTargetResId() {
+ return R.layout.access_point_friction_widget;
}
protected void updateIcon(int level, Context context) {
diff --git a/com/android/setupwizardlib/GlifLayout.java b/com/android/setupwizardlib/GlifLayout.java
index f4d52a5e..dd0963b0 100644
--- a/com/android/setupwizardlib/GlifLayout.java
+++ b/com/android/setupwizardlib/GlifLayout.java
@@ -77,6 +77,8 @@ public class GlifLayout extends TemplateLayout {
@Nullable
private ColorStateList mBackgroundBaseColor;
+ private boolean mLayoutFullscreen = true;
+
public GlifLayout(Context context) {
this(context, 0, 0);
}
@@ -139,7 +141,13 @@ public class GlifLayout extends TemplateLayout {
inflateFooter(footer);
}
+ mLayoutFullscreen = a.getBoolean(R.styleable.SuwGlifLayout_suwLayoutFullscreen, true);
+
a.recycle();
+
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && mLayoutFullscreen) {
+ setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ }
}
@Override
@@ -280,9 +288,6 @@ public class GlifLayout extends TemplateLayout {
patternBg.setBackgroundDrawable(background);
}
}
- if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
- setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
- }
}
public boolean isProgressBarShown() {
diff --git a/com/android/setupwizardlib/GlifLayoutTest.java b/com/android/setupwizardlib/GlifLayoutTest.java
index d46409df..967a52eb 100644
--- a/com/android/setupwizardlib/GlifLayoutTest.java
+++ b/com/android/setupwizardlib/GlifLayoutTest.java
@@ -32,6 +32,7 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.support.annotation.IdRes;
import android.view.ContextThemeWrapper;
import android.view.View;
@@ -266,6 +267,32 @@ public class GlifLayoutTest {
assertNotNull(layout.findViewById(android.R.id.text1));
}
+ @Config(sdk = { VERSION_CODES.M, Config.NEWEST_SDK })
+ @Test
+ public void createFromXml_shouldSetLayoutFullscreen_whenLayoutFullscreenIsNotSet() {
+ GlifLayout layout = new GlifLayout(
+ mContext,
+ Robolectric.buildAttributeSet()
+ .build());
+
+ assertEquals(
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+ layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ }
+
+ @Test
+ public void createFromXml_shouldNotSetLayoutFullscreen_whenLayoutFullscreenIsFalse() {
+ GlifLayout layout = new GlifLayout(
+ mContext,
+ Robolectric.buildAttributeSet()
+ .addAttribute(R.attr.suwLayoutFullscreen, "false")
+ .build());
+
+ assertEquals(
+ 0,
+ layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ }
+
private Drawable getPhoneBackground(GlifLayout layout) {
final StatusBarBackgroundLayout patternBg =
(StatusBarBackgroundLayout) layout.findManagedViewById(R.id.suw_pattern_bg);
diff --git a/com/android/setupwizardlib/template/IconMixin.java b/com/android/setupwizardlib/template/IconMixin.java
index 46c23f08..c42299b8 100644
--- a/com/android/setupwizardlib/template/IconMixin.java
+++ b/com/android/setupwizardlib/template/IconMixin.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.ImageView;
import com.android.setupwizardlib.R;
@@ -61,6 +62,7 @@ public class IconMixin implements Mixin {
final ImageView iconView = getView();
if (iconView != null) {
iconView.setImageDrawable(icon);
+ iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE);
}
}
diff --git a/com/android/setupwizardlib/template/IconMixinTest.java b/com/android/setupwizardlib/template/IconMixinTest.java
index a1f2b54c..03913925 100644
--- a/com/android/setupwizardlib/template/IconMixinTest.java
+++ b/com/android/setupwizardlib/template/IconMixinTest.java
@@ -31,6 +31,7 @@ import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.Xml;
+import android.view.View;
import android.widget.ImageView;
import com.android.setupwizardlib.TemplateLayout;
@@ -74,6 +75,15 @@ public class IconMixinTest {
mixin.setIcon(drawable);
assertSame(drawable, mIconView.getDrawable());
+ assertEquals(View.VISIBLE, mIconView.getVisibility());
+ }
+
+ @Test
+ public void setIcon_shouldSetVisibilityToGone_whenIconIsNull() {
+ IconMixin mixin = new IconMixin(mTemplateLayout, null, 0);
+ mixin.setIcon(null);
+
+ assertEquals(View.GONE, mIconView.getVisibility());
}
@Test
@@ -101,5 +111,6 @@ public class IconMixinTest {
.getDrawable(android.R.drawable.ic_menu_add);
final BitmapDrawable actual = (BitmapDrawable) mIconView.getDrawable();
assertEquals(expected.getBitmap(), actual.getBitmap());
+ assertEquals(View.VISIBLE, mIconView.getVisibility());
}
}
diff --git a/com/android/setupwizardlib/util/WizardManagerHelper.java b/com/android/setupwizardlib/util/WizardManagerHelper.java
index 896c0137..32929aad 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelper.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelper.java
@@ -27,6 +27,8 @@ import android.support.annotation.VisibleForTesting;
import com.android.setupwizardlib.R;
+import java.util.Arrays;
+
public class WizardManagerHelper {
private static final String ACTION_NEXT = "com.android.wizard.NEXT";
@@ -142,13 +144,14 @@ public class WizardManagerHelper {
*/
public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) {
dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE));
- dstIntent.putExtra(EXTRA_THEME, srcIntent.getStringExtra(EXTRA_THEME));
- dstIntent.putExtra(EXTRA_IS_FIRST_RUN,
- srcIntent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false));
- dstIntent.putExtra(EXTRA_IS_DEFERRED_SETUP,
- srcIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false));
- dstIntent.putExtra(EXTRA_SCRIPT_URI, srcIntent.getStringExtra(EXTRA_SCRIPT_URI));
- dstIntent.putExtra(EXTRA_ACTION_ID, srcIntent.getStringExtra(EXTRA_ACTION_ID));
+ for (String key : Arrays.asList(
+ EXTRA_IS_FIRST_RUN, EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP)) {
+ dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false));
+ }
+
+ for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) {
+ dstIntent.putExtra(key, srcIntent.getStringExtra(key));
+ }
}
/**
diff --git a/com/android/setupwizardlib/util/WizardManagerHelperTest.java b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
index 6477b518..c236bb54 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelperTest.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
@@ -274,6 +274,7 @@ public class WizardManagerHelperTest {
.putExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE, wizardBundle)
.putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true)
.putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true)
+ .putExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, true)
// Script URI and Action ID are kept for backwards compatibility
.putExtra(WizardManagerHelper.EXTRA_SCRIPT_URI, "test_script_uri")
.putExtra(WizardManagerHelper.EXTRA_ACTION_ID, "test_action_id");
@@ -292,6 +293,8 @@ public class WizardManagerHelperTest {
intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, false));
assertTrue("EXTRA_IS_DEFERRED_SETUP should be copied",
intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, false));
+ assertTrue("EXTRA_IS_PRE_DEFERRED_SETUP should be copied",
+ intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, false));
// Script URI and Action ID are replaced by Wizard Bundle in M, but are kept for backwards
// compatibility
diff --git a/com/android/systemui/DemoMode.java b/com/android/systemui/DemoMode.java
index 11996d07..5c397157 100644
--- a/com/android/systemui/DemoMode.java
+++ b/com/android/systemui/DemoMode.java
@@ -37,4 +37,5 @@ public interface DemoMode {
public static final String COMMAND_STATUS = "status";
public static final String COMMAND_NOTIFICATIONS = "notifications";
public static final String COMMAND_VOLUME = "volume";
+ public static final String COMMAND_OPERATOR = "operator";
}
diff --git a/com/android/systemui/Dependency.java b/com/android/systemui/Dependency.java
index d8a47c5e..e7e70afa 100644
--- a/com/android/systemui/Dependency.java
+++ b/com/android/systemui/Dependency.java
@@ -26,7 +26,7 @@ import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -205,8 +205,8 @@ public class Dependency extends SystemUI {
mProviders.put(BatteryController.class, () ->
new BatteryControllerImpl(mContext));
- mProviders.put(NightDisplayController.class, () ->
- new NightDisplayController(mContext));
+ mProviders.put(ColorDisplayController.class, () ->
+ new ColorDisplayController(mContext));
mProviders.put(ManagedProfileController.class, () ->
new ManagedProfileControllerImpl(mContext));
@@ -308,6 +308,8 @@ public class Dependency extends SystemUI {
mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService());
+ mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext));
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/com/android/systemui/OverviewProxyService.java b/com/android/systemui/OverviewProxyService.java
new file mode 100644
index 00000000..2e4a5a41
--- /dev/null
+++ b/com/android/systemui/OverviewProxyService.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+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;
+import android.os.PatternMatcher;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.SurfaceControl;
+
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+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;
+import java.util.List;
+
+/**
+ * Class to send information from overview to launcher with a binder.
+ */
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>, Dumpable {
+
+ private static final String TAG = "OverviewProxyService";
+ private static final long BACKOFF_MILLIS = 5000;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
+ private final ComponentName mLauncherComponentName;
+ private final DeviceProvisionedController mDeviceProvisionedController
+ = Dependency.get(DeviceProvisionedController.class);
+ private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
+
+ private IOverviewProxy mOverviewProxy;
+ 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) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
+ useIdentityTransform, rotation);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Reconnect immediately, instead of waiting for resume to arrive.
+ startConnectionToCurrentUser();
+ }
+ };
+
+ private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (service != null) {
+ mConnectionBackoffAttempts = 0;
+ mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
+ // Listen for launcher's death
+ try {
+ service.linkToDeath(mOverviewServiceDeathRcpt, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Lost connection to launcher service", e);
+ }
+ try {
+ mOverviewProxy.onBind(mSysUiProxy);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call onBind()", e);
+ }
+ notifyConnectionChanged();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Do nothing
+ }
+ };
+
+ private final DeviceProvisionedListener mDeviceProvisionedCallback =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ if (mDeviceProvisionedController.isCurrentUserSetup()) {
+ internalConnectToCurrentUser();
+ }
+ }
+
+ @Override
+ public void onUserSwitched() {
+ mConnectionBackoffAttempts = 0;
+ internalConnectToCurrentUser();
+ }
+ };
+
+ // This is the death handler for the binder from the launcher service
+ private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
+ = this::startConnectionToCurrentUser;
+
+ public OverviewProxyService(Context context) {
+ mContext = context;
+ mHandler = new Handler();
+ mConnectionBackoffAttempts = 0;
+ mLauncherComponentName = ComponentName
+ .unflattenFromString(context.getString(R.string.config_overviewServiceComponent));
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
+
+ // Listen for the package update changes.
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addDataScheme("package");
+ filter.addDataSchemeSpecificPart(mLauncherComponentName.getPackageName(),
+ PatternMatcher.PATTERN_LITERAL);
+ mContext.registerReceiver(mLauncherAddedReceiver, filter);
+ }
+
+ public void startConnectionToCurrentUser() {
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ mHandler.post(mConnectionRunnable);
+ } else {
+ internalConnectToCurrentUser();
+ }
+ }
+
+ private void internalConnectToCurrentUser() {
+ disconnectFromLauncherService();
+
+ // If user has not setup yet or already connected, do not try to connect
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ return;
+ }
+ mHandler.removeCallbacks(mConnectionRunnable);
+ Intent launcherServiceIntent = new Intent();
+ launcherServiceIntent.setComponent(mLauncherComponentName);
+ boolean bound = mContext.bindServiceAsUser(launcherServiceIntent,
+ mOverviewServiceConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.getUserHandleForUid(mDeviceProvisionedController.getCurrentUser()));
+ if (!bound) {
+ // Retry after exponential backoff timeout
+ final long timeoutMs = (long) Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts);
+ mHandler.postDelayed(mConnectionRunnable, timeoutMs);
+ mConnectionBackoffAttempts++;
+ }
+ }
+
+ @Override
+ public void addCallback(OverviewProxyListener listener) {
+ mConnectionCallbacks.add(listener);
+ listener.onConnectionChanged(mOverviewProxy != null);
+ }
+
+ @Override
+ public void removeCallback(OverviewProxyListener listener) {
+ mConnectionCallbacks.remove(listener);
+ }
+
+ public IOverviewProxy getProxy() {
+ return mOverviewProxy;
+ }
+
+ private void disconnectFromLauncherService() {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
+ mContext.unbindService(mOverviewServiceConnection);
+ mOverviewProxy = null;
+ notifyConnectionChanged();
+ }
+ }
+
+ private void notifyConnectionChanged() {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(TAG + " state:");
+ pw.print(" mConnectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
+ pw.print(" isCurrentUserSetup="); pw.println(mDeviceProvisionedController
+ .isCurrentUserSetup());
+ pw.print(" isConnected="); pw.println(mOverviewProxy != null);
+ }
+
+ public interface OverviewProxyListener {
+ void onConnectionChanged(boolean isConnected);
+ }
+}
diff --git a/com/android/systemui/RecentsComponent.java b/com/android/systemui/RecentsComponent.java
index 44a044bc..880ae709 100644
--- a/com/android/systemui/RecentsComponent.java
+++ b/com/android/systemui/RecentsComponent.java
@@ -28,7 +28,7 @@ public interface RecentsComponent {
/**
* Docks the top-most task and opens recents.
*/
- boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+ boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction);
/**
diff --git a/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 5d0a9d70..03b0082b 100644
--- a/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -33,8 +33,10 @@ public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delega
@Override
public void setDozeScreenState(int state) {
- if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
+ if (state == Display.STATE_DOZE) {
state = Display.STATE_ON;
+ } else if (state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_ON_SUSPEND;
}
super.setDozeScreenState(state);
}
diff --git a/com/android/systemui/doze/DozeService.java b/com/android/systemui/doze/DozeService.java
index 98b11061..6650cc63 100644
--- a/com/android/systemui/doze/DozeService.java
+++ b/com/android/systemui/doze/DozeService.java
@@ -92,6 +92,7 @@ public class DozeService extends DreamService
@Override
protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpOnHandler(fd, pw, args);
if (mDozeMachine != null) {
mDozeMachine.dump(pw);
}
diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java
index d82f9cd4..00e8b1a4 100644
--- a/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -80,7 +80,7 @@ import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
+import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
diff --git a/com/android/systemui/keyguard/KeyguardSliceProvider.java b/com/android/systemui/keyguard/KeyguardSliceProvider.java
new file mode 100644
index 00000000..03018f7d
--- /dev/null
+++ b/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.keyguard;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.net.Uri;
+import android.os.Handler;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Simple Slice provider that shows the current date.
+ */
+public class KeyguardSliceProvider extends SliceProvider {
+
+ public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+
+ private final Date mCurrentTime = new Date();
+ protected final Uri mSliceUri;
+ private final Handler mHandler;
+ private String mDatePattern;
+ private DateFormat mDateFormat;
+ private String mLastText;
+ private boolean mRegistered;
+ private boolean mRegisteredEveryMinute;
+
+ /**
+ * Receiver responsible for time ticking and updating the date format.
+ */
+ @VisibleForTesting
+ final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_TIME_TICK.equals(action)
+ || Intent.ACTION_DATE_CHANGED.equals(action)
+ || Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+ || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+ // need to get a fresh date format
+ mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
+ }
+ mHandler.post(KeyguardSliceProvider.this::updateClock);
+ }
+ }
+ };
+
+ public KeyguardSliceProvider() {
+ this(new Handler());
+ }
+
+ @VisibleForTesting
+ KeyguardSliceProvider(Handler handler) {
+ mHandler = handler;
+ mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+ }
+
+ @Override
+ public Slice onBindSlice(Uri sliceUri) {
+ return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+ }
+
+ @Override
+ public boolean onCreate() {
+
+ mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
+
+ registerClockUpdate(false /* everyMinute */);
+ updateClock();
+ return true;
+ }
+
+ protected void registerClockUpdate(boolean everyMinute) {
+ if (mRegistered) {
+ if (mRegisteredEveryMinute == everyMinute) {
+ return;
+ } else {
+ unregisterClockUpdate();
+ }
+ }
+
+ IntentFilter filter = new IntentFilter();
+ if (everyMinute) {
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ }
+ filter.addAction(Intent.ACTION_DATE_CHANGED);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+ null /* scheduler */);
+ mRegistered = true;
+ mRegisteredEveryMinute = everyMinute;
+ }
+
+ protected void unregisterClockUpdate() {
+ if (!mRegistered) {
+ return;
+ }
+ getContext().unregisterReceiver(mIntentReceiver);
+ mRegistered = false;
+ }
+
+ @VisibleForTesting
+ boolean isRegistered() {
+ return mRegistered;
+ }
+
+ protected void updateClock() {
+ final String text = getFormattedDate();
+ if (!text.equals(mLastText)) {
+ mLastText = text;
+ getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ }
+ }
+
+ protected String getFormattedDate() {
+ if (mDateFormat == null) {
+ final Locale l = Locale.getDefault();
+ DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+ mDateFormat = format;
+ }
+ mCurrentTime.setTime(System.currentTimeMillis());
+ return mDateFormat.format(mCurrentTime);
+ }
+
+ @VisibleForTesting
+ void cleanDateFormat() {
+ mDateFormat = null;
+ }
+}
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index 28adca97..a35ba9fa 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -28,6 +28,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
@@ -1691,6 +1692,8 @@ public class KeyguardViewMediator extends SystemUI {
mUiOffloadThread.submit(() -> {
// If the stream is muted, don't play the sound
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+ // If DND blocks the sound, don't play the sound
+ if (areSystemSoundsZenModeBlocked(mContext)) return;
int id = mLockSounds.play(soundId,
mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1702,6 +1705,25 @@ public class KeyguardViewMediator extends SystemUI {
}
}
+ private boolean areSystemSoundsZenModeBlocked(Context context) {
+ int zenMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ZEN_MODE, 0);
+
+ switch (zenMode) {
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return true;
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ final NotificationManager noMan = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ return (noMan.getNotificationPolicy().priorityCategories
+ & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+ case Settings.Global.ZEN_MODE_OFF:
+ default:
+ return false;
+ }
+ }
+
private void playTrustedSound() {
playSound(mTrustedSoundId);
}
diff --git a/com/android/systemui/keyguard/WorkLockActivityController.java b/com/android/systemui/keyguard/WorkLockActivityController.java
index 4c3d5bad..b9e9e0a7 100644
--- a/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -26,34 +26,42 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
public class WorkLockActivityController {
+ private static final String TAG = WorkLockActivityController.class.getSimpleName();
+
private final Context mContext;
- private final SystemServicesProxy mSsp;
private final IActivityManager mIam;
public WorkLockActivityController(Context context) {
- this(context, SystemServicesProxy.getInstance(context), ActivityManager.getService());
+ this(context, ActivityManagerWrapper.getInstance(), ActivityManager.getService());
}
@VisibleForTesting
- WorkLockActivityController(Context context, SystemServicesProxy ssp, IActivityManager am) {
+ WorkLockActivityController(Context context, ActivityManagerWrapper am, IActivityManager iAm) {
mContext = context;
- mSsp = ssp;
- mIam = am;
+ mIam = iAm;
- mSsp.registerTaskStackListener(mLockListener);
+ am.registerTaskStackListener(mLockListener);
}
private void startWorkChallengeInTask(int taskId, int userId) {
+ ActivityManager.TaskDescription taskDescription = null;
+ try {
+ taskDescription = mIam.getTaskDescription(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get description for task=" + taskId);
+ }
Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
.setComponent(new ComponentName(mContext, WorkLockActivity.class))
.putExtra(Intent.EXTRA_USER_ID, userId)
- .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId))
+ .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription)
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -67,7 +75,11 @@ public class WorkLockActivityController {
} else {
// Starting the activity inside the task failed. We can't be sure why, so to be
// safe just remove the whole task if it still exists.
- mSsp.removeTask(taskId);
+ try {
+ mIam.removeTask(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get description for task=" + taskId);
+ }
}
}
@@ -96,7 +108,7 @@ public class WorkLockActivityController {
}
}
- private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
+ private final SysUiTaskStackChangeListener mLockListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskProfileLocked(int taskId, int userId) {
startWorkChallengeInTask(taskId, userId);
diff --git a/com/android/systemui/pip/phone/InputConsumerController.java b/com/android/systemui/pip/phone/InputConsumerController.java
index e6d6c558..db4f988a 100644
--- a/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/com/android/systemui/pip/phone/InputConsumerController.java
@@ -18,6 +18,8 @@ package com.android.systemui.pip.phone;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@@ -77,7 +79,8 @@ public class InputConsumerController {
}
}
- private IWindowManager mWindowManager;
+ private final IWindowManager mWindowManager;
+ private final IBinder mToken;
private PipInputEventReceiver mInputEventReceiver;
private TouchListener mListener;
@@ -85,6 +88,7 @@ public class InputConsumerController {
public InputConsumerController(IWindowManager windowManager) {
mWindowManager = windowManager;
+ mToken = new Binder();
registerInputConsumer();
}
@@ -122,7 +126,7 @@ public class InputConsumerController {
final InputChannel inputChannel = new InputChannel();
try {
mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
- mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
+ mWindowManager.createInputConsumer(mToken, INPUT_CONSUMER_PIP, inputChannel);
} catch (RemoteException e) {
Log.e(TAG, "Failed to create PIP input consumer", e);
}
diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java
index 7e87666a..29635068 100644
--- a/com/android/systemui/pip/phone/PipManager.java
+++ b/com/android/systemui/pip/phone/PipManager.java
@@ -40,8 +40,9 @@ import android.view.WindowManagerGlobal;
import com.android.systemui.pip.BasePipManager;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.PrintWriter;
@@ -69,7 +70,7 @@ public class PipManager implements BasePipManager {
/**
* Handler for system task stack changes.
*/
- TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
mTouchHandler.onActivityPinned();
@@ -173,7 +174,7 @@ public class PipManager implements BasePipManager {
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mInputConsumerController = new InputConsumerController(mWindowManager);
mMediaController = new PipMediaController(context, mActivityManager);
@@ -202,9 +203,9 @@ public class PipManager implements BasePipManager {
StackInfo stackInfo = mActivityManager.getStackInfo(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stackInfo != null && stackInfo.taskIds != null) {
- SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
+ ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
for (int taskId : stackInfo.taskIds) {
- ssp.cancelThumbnailTransition(taskId);
+ am.cancelThumbnailTransition(taskId);
}
}
} catch (RemoteException e) {
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index c92562b4..eef43d29 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -45,8 +45,9 @@ import android.view.WindowManagerGlobal;
import com.android.systemui.R;
import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -234,7 +235,7 @@ public class PipManager implements BasePipManager {
mActivityManager = ActivityManager.getService();
mWindowManager = WindowManagerGlobal.getWindowManagerService();
- SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
@@ -620,7 +621,7 @@ public class PipManager implements BasePipManager {
return false;
}
- private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ private SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
diff --git a/com/android/systemui/qs/QSFooterImpl.java b/com/android/systemui/qs/QSFooterImpl.java
index 3199decb..5ffd7859 100644
--- a/com/android/systemui/qs/QSFooterImpl.java
+++ b/com/android/systemui/qs/QSFooterImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
import android.app.ActivityManager;
@@ -37,7 +39,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -51,10 +52,11 @@ import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.R.dimen;
import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ExpandableIndicator;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
@@ -70,7 +72,7 @@ import com.android.systemui.tuner.TunerService;
public class QSFooterImpl extends FrameLayout implements QSFooter,
NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
- SignalCallback {
+ SignalCallback, CommandQueue.Callbacks {
private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
private ActivityStarter mActivityStarter;
@@ -83,6 +85,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private View mAlarmStatusCollapsed;
private View mDate;
+ private boolean mQsDisabled;
private QSPanel mQsPanel;
private boolean mExpanded;
@@ -278,9 +281,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
@Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+ }
+
+ @Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
super.onDetachedFromWindow();
}
@@ -302,6 +312,14 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
return findViewById(R.id.expand_indicator);
}
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+ if (disabled == mQsDisabled) return;
+ mQsDisabled = disabled;
+ updateEverything();
+ }
+
public void updateEverything() {
post(() -> {
updateVisibilities();
@@ -311,11 +329,18 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private void updateVisibilities() {
updateAlarmVisibilities();
+
+ mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+
+ mExpandIndicator.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
+
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
- mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
+
+ mMultiUserSwitch.setVisibility(mExpanded
+ && UserManager.get(mContext).isUserSwitcherEnabled()
? View.VISIBLE : View.INVISIBLE);
mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
diff --git a/com/android/systemui/qs/QuickQSPanel.java b/com/android/systemui/qs/QuickQSPanel.java
index 00b883a5..947b23f8 100644
--- a/com/android/systemui/qs/QuickQSPanel.java
+++ b/com/android/systemui/qs/QuickQSPanel.java
@@ -25,7 +25,7 @@ import android.widget.Space;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.plugins.qs.QSTileView;
@@ -43,6 +43,7 @@ public class QuickQSPanel extends QSPanel {
public static final String NUM_QUICK_TILES = "sysui_qqs_count";
+ private boolean mDisabledByPolicy;
private int mMaxTiles;
protected QSPanel mFullPanel;
@@ -151,6 +152,30 @@ public class QuickQSPanel extends QSPanel {
return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6);
}
+ void setDisabledByPolicy(boolean disabled) {
+ if (disabled != mDisabledByPolicy) {
+ mDisabledByPolicy = disabled;
+ setVisibility(disabled ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ /**
+ * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel
+ * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the
+ * visibility will always be {@link View#GONE}. This method is called externally by
+ * {@link QSAnimator} only.
+ */
+ @Override
+ public void setVisibility(int visibility) {
+ if (mDisabledByPolicy) {
+ if (getVisibility() == View.GONE) {
+ return;
+ }
+ visibility = View.GONE;
+ }
+ super.setVisibility(visibility);
+ }
+
private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
diff --git a/com/android/systemui/qs/QuickStatusBarHeader.java b/com/android/systemui/qs/QuickStatusBarHeader.java
index 0709e229..398592ad 100644
--- a/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -14,6 +14,9 @@
package com.android.systemui.qs;
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -30,13 +33,14 @@ import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSDetail.Callback;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
-
-public class QuickStatusBarHeader extends RelativeLayout {
+public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks {
private ActivityStarter mActivityStarter;
@@ -44,6 +48,7 @@ public class QuickStatusBarHeader extends RelativeLayout {
private boolean mExpanded;
private boolean mListening;
+ private boolean mQsDisabled;
protected QuickQSPanel mHeaderQsPanel;
protected QSTileHost mHost;
@@ -119,9 +124,25 @@ public class QuickStatusBarHeader extends RelativeLayout {
}
@Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+ if (disabled == mQsDisabled) return;
+ mQsDisabled = disabled;
+ mHeaderQsPanel.setDisabledByPolicy(disabled);
+ final int rawHeight = (int) getResources().getDimension(R.dimen.status_bar_header_height);
+ getLayoutParams().height = disabled ? (rawHeight - mHeaderQsPanel.getHeight()) : rawHeight;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+ }
+
+ @Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
super.onDetachedFromWindow();
}
diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java
index 1aecdceb..0e4a9fe3 100644
--- a/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,7 +134,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
if (lastDevice != null) {
int batteryLevel = lastDevice.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- state.icon = new BluetoothBatteryTileIcon(batteryLevel,
+ state.icon = new BluetoothBatteryTileIcon(lastDevice,
mContext.getResources().getFraction(
R.fraction.bt_battery_scale_fraction, 1, 1));
}
@@ -213,18 +213,19 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
}
private class BluetoothBatteryTileIcon extends Icon {
- private int mLevel;
private float mIconScale;
+ private CachedBluetoothDevice mDevice;
- BluetoothBatteryTileIcon(int level, float iconScale) {
- mLevel = level;
+ BluetoothBatteryTileIcon(CachedBluetoothDevice device, float iconScale) {
mIconScale = iconScale;
+ mDevice = device;
}
@Override
public Drawable getDrawable(Context context) {
- return createLayerDrawable(context,
- R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale);
+ // This method returns Pair<Drawable, String> while first value is the drawable
+ return com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription(
+ context, mDevice, mIconScale).first;
}
}
@@ -306,7 +307,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
item.iconResId = R.drawable.ic_qs_bluetooth_connected;
int batteryLevel = device.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- item.icon = new BluetoothBatteryTileIcon(batteryLevel,
+ item.icon = new BluetoothBatteryTileIcon(device,
1 /* iconScale */);
item.line2 = mContext.getString(
R.string.quick_settings_connected_battery_level,
diff --git a/com/android/systemui/qs/tiles/NightDisplayTile.java b/com/android/systemui/qs/tiles/NightDisplayTile.java
index 4c203614..763ffc67 100644
--- a/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -22,8 +22,7 @@ import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.widget.Switch;
-import com.android.internal.app.NightDisplayController;
-import com.android.internal.logging.MetricsLogger;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.qs.QSHost;
@@ -31,19 +30,19 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
public class NightDisplayTile extends QSTileImpl<BooleanState>
- implements NightDisplayController.Callback {
+ implements ColorDisplayController.Callback {
- private NightDisplayController mController;
+ private ColorDisplayController mController;
private boolean mIsListening;
public NightDisplayTile(QSHost host) {
super(host);
- mController = new NightDisplayController(mContext, ActivityManager.getCurrentUser());
+ mController = new ColorDisplayController(mContext, ActivityManager.getCurrentUser());
}
@Override
public boolean isAvailable() {
- return NightDisplayController.isAvailable(mContext);
+ return ColorDisplayController.isAvailable(mContext);
}
@Override
@@ -65,7 +64,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
}
// Make a new controller for the new user.
- mController = new NightDisplayController(mContext, newUserId);
+ mController = new ColorDisplayController(mContext, newUserId);
if (mIsListening) {
mController.setListener(this);
}
diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java
index ce1438a1..5b62c7d3 100644
--- a/com/android/systemui/recents/Recents.java
+++ b/com/android/systemui/recents/Recents.java
@@ -63,6 +63,7 @@ import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
@@ -246,7 +247,7 @@ public class Recents extends SystemUI
return;
}
- sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
@@ -394,7 +395,7 @@ public class Recents extends SystemUI
}
@Override
- public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+ public boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
@@ -410,12 +411,12 @@ public class Recents extends SystemUI
}
int currentUser = sSystemServicesProxy.getCurrentUser();
- SystemServicesProxy ssp = Recents.getSystemServices();
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
final int activityType = runningTask != null
? runningTask.configuration.windowConfiguration.getActivityType()
: ACTIVITY_TYPE_UNDEFINED;
- boolean screenPinningActive = ssp.isScreenPinningActive();
+ boolean screenPinningActive = sSystemServicesProxy.isScreenPinningActive();
boolean isRunningTaskInHomeOrRecentsStack =
activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
@@ -426,15 +427,16 @@ public class Recents extends SystemUI
runningTask.topActivity.flattenToShortString());
}
if (sSystemServicesProxy.isSystemUser(currentUser)) {
- mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
+ mImpl.splitPrimaryTask(runningTask.id, dragMode, stackCreateMode,
+ initialBounds);
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
- callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
- initialBounds);
+ callbacks.splitPrimaryTask(runningTask.id, dragMode,
+ stackCreateMode, initialBounds);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java
index b75a1428..9aecc686 100644
--- a/com/android/systemui/recents/RecentsActivity.java
+++ b/com/android/systemui/recents/RecentsActivity.java
@@ -16,6 +16,8 @@
package com.android.systemui.recents;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
+
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
@@ -92,7 +94,7 @@ import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
-import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -280,8 +282,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
new DismissRecentsToHomeAnimationStarted(animateTaskViews);
dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
overrideAnimation));
- Recents.getSystemServices().sendCloseSystemWindows(
- StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
EventBus.getDefault().send(dismissEvent);
}
@@ -468,7 +469,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
if (launchState.launchedFromApp) {
Task launchTarget = stack.getLaunchTarget();
int launchTaskIndexInStack = launchTarget != null
- ? stack.indexOfStackTask(launchTarget)
+ ? stack.indexOfTask(launchTarget)
: 0;
MetricsLogger.count(this, "overview_source_app", 1);
// If from an app, track the stack index of the app in the stack (for affiliated tasks)
@@ -514,7 +515,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// Notify of the config change
Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
- int numStackTasks = mRecentsView.getStack().getStackTaskCount();
+ int numStackTasks = mRecentsView.getStack().getTaskCount();
EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
mLastConfig.orientation != newDeviceConfiguration.orientation,
mLastConfig.densityDpi != newDeviceConfiguration.densityDpi, numStackTasks > 0));
@@ -706,9 +707,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
int launchToTaskId = launchState.launchedToTaskId;
if (launchToTaskId != -1 &&
(event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.cancelWindowTransition(launchState.launchedToTaskId);
- ssp.cancelThumbnailTransition(getTaskId());
+ ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
+ am.cancelWindowTransition(launchState.launchedToTaskId);
+ am.cancelThumbnailTransition(getTaskId());
}
}
@@ -755,8 +756,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
loader.deleteTaskData(event.task, false);
// Remove the task from activity manager
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.removeTask(event.task.key.id);
+ ActivityManagerWrapper.getInstance().removeTask(event.task.key.id);
}
public final void onBusEvent(TaskViewDismissedEvent event) {
@@ -824,7 +824,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
loader.loadTasks(loadPlan, loadOpts);
TaskStack stack = loadPlan.getTaskStack();
- int numStackTasks = stack.getStackTaskCount();
+ int numStackTasks = stack.getTaskCount();
boolean showDeferredAnimation = numStackTasks > 0;
if (sendConfigChangedEvent) {
diff --git a/com/android/systemui/recents/RecentsActivityLaunchState.java b/com/android/systemui/recents/RecentsActivityLaunchState.java
index d2326ce2..14fda952 100644
--- a/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -50,24 +50,4 @@ public class RecentsActivityLaunchState {
launchedViaDragGesture = false;
launchedViaDockGesture = false;
}
-
- /**
- * Returns the task to focus given the current launch state.
- */
- public int getInitialFocusTaskIndex(int numTasks, boolean useGridLayout) {
- RecentsDebugFlags debugFlags = Recents.getDebugFlags();
- RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
- if (launchedFromApp) {
- if (useGridLayout) {
- // If coming from another app to the grid layout, focus the front most task
- return numTasks - 1;
- }
-
- // If coming from another app, focus the next task
- return Math.max(0, numTasks - 2);
- } else {
- // If coming from home, focus the front most task
- return numTasks - 1;
- }
- }
}
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 868ed64b..3b1b2f90 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -20,14 +20,16 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
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.app.ActivityOptions.OnAnimationStartedListener;
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;
@@ -38,7 +40,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.MutableBoolean;
import android.util.Pair;
-import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -70,26 +71,29 @@ import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.RecentsTransitionHelper;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
+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.stackdivider.DividerView;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
+import java.util.List;
/**
* An implementation of the Recents component for the current user. For secondary users, this can
@@ -113,10 +117,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
/**
- * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system
+ * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system
* task stacks and update recents accordingly.
*/
- class TaskStackListenerImpl extends TaskStackChangeListener {
+ class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
@Override
public void onTaskStackChangedBackground() {
@@ -134,8 +138,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
// Load the next task only if we aren't svelte
- SystemServicesProxy ssp = Recents.getSystemServices();
- ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTaskInfo =
+ ActivityManagerWrapper.getInstance().getRunningTask();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
loader.preloadTasks(plan, -1);
@@ -155,7 +159,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
VisibilityReport visibilityReport =
mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
- stack.getStackTasks());
+ stack.getTasks());
launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
@@ -196,14 +200,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
@Override
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+ public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
// Check this is for the right user
if (!checkCurrentUserId(mContext, false /* debug */)) {
return;
}
- EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId,
- new ThumbnailData(snapshot)));
+ EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot));
}
}
@@ -217,13 +220,12 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// recents. In this case, we defer the toggle state until then and apply it immediately after.
private static boolean mToggleFollowingTransitionStart = true;
- private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener =
- new OnAnimationStartedListener() {
- @Override
- public void onAnimationStarted() {
- setWaitingForTransitionStart(false);
- }
- };
+ private Runnable mResetToggleFlagListener = new Runnable() {
+ @Override
+ public void run() {
+ setWaitingForTransitionStart(false);
+ }
+ };
protected Context mContext;
protected Handler mHandler;
@@ -266,8 +268,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Register the task stack listener
mTaskStackListener = new TaskStackListenerImpl();
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
// Initialize the static configuration resources
mDummyStackView = new TaskStackView(mContext);
@@ -349,7 +350,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
growTarget);
}
@@ -440,12 +442,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
// Otherwise, start the recents activity
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
growTarget);
// Only close the other system windows if we are actually showing recents
- ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(
+ SYSTEM_DIALOG_REASON_RECENT_APPS);
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
@@ -465,7 +469,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// don't block the touch feedback on the nav bar button which triggers this.
mHandler.post(() -> {
if (!ssp.isRecentsActivityVisible(null)) {
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTask == null) {
return;
}
@@ -519,14 +524,15 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
// Return early if there is no running task
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTask == null) return;
// Find the task in the recents list
boolean isRunningTaskInHomeStack =
runningTask.configuration.windowConfiguration.getActivityType()
== ACTIVITY_TYPE_HOME;
- ArrayList<Task> tasks = focusedStack.getStackTasks();
+ ArrayList<Task> tasks = focusedStack.getTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
@@ -556,8 +562,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
// Launch the task
- ssp.startActivityFromRecents(
- mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
+ null /* resultCallback */, null /* resultCallbackHandler */);
}
/**
@@ -574,14 +580,15 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
// Return early if there is no running task (can't determine affiliated tasks in this case)
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
if (runningTask == null) return;
// Return early if the running task is in the home/recents stack (optimization)
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
// Find the task in the recents list
- ArrayList<Task> tasks = focusedStack.getStackTasks();
+ ArrayList<Task> tasks = focusedStack.getTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
@@ -625,8 +632,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
// Launch the task
- ssp.startActivityFromRecents(
- mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
+ null /* resultListener */, null /* resultCallbackHandler */);
}
public void showNextAffiliatedTask() {
@@ -641,13 +648,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
showRelativeAffiliatedTask(false);
}
- public void dockTopTask(int topTaskId, int dragMode,
- int stackCreateMode, Rect initialBounds) {
+ public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode,
+ Rect initialBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Make sure we inform DividerView before we actually start the activity so we can change
// the resize mode already.
- if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
+ if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
showRecents(
false /* triggeredFromAltTab */,
@@ -726,7 +733,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// However, the window bounds include the insets, so we need to subtract them here to make
// them identical.
if (ssp.hasDockedTask()) {
- windowRect.bottom -= systemInsets.bottom;
+ if (systemInsets.bottom < windowRect.height()) {
+ // Only apply inset if it isn't going to cause the rect height to go negative.
+ windowRect.bottom -= systemInsets.bottom;
+ }
systemInsets.bottom = 0;
}
calculateWindowStableInsets(systemInsets, windowRect, displayRect);
@@ -864,22 +874,22 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
windowOverrideRect);
RectF toTaskRect = toTransform.rect;
- AppTransitionAnimationSpecsFuture future =
- new RecentsTransitionHelper(mContext).getAppTransitionFuture(
- () -> {
- Rect rect = new Rect();
- toTaskRect.round(rect);
- GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
- toTransform);
- return Lists.newArrayList(new AppTransitionAnimationSpec(
- toTask.key.id, thumbnail, rect));
- });
+ AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ Rect rect = new Rect();
+ toTaskRect.round(rect);
+ Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+ return Lists.newArrayList(new AppTransitionAnimationSpecCompat(toTask.key.id,
+ thumbnail, rect));
+ }
+ };
// For low end ram devices, wait for transition flag is reset when Recents entrance
// animation is complete instead of when the transition animation starts
- return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
- false /* scaleUp */), future);
+ return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler,
+ false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener),
+ future);
}
/**
@@ -894,7 +904,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
runningTaskOut.copyFrom(launchTask);
} else {
// If no task is specified or we can not find the task just use the front most one
- launchTask = stack.getStackFrontMostTask();
+ launchTask = stack.getFrontMostTask();
runningTaskOut.copyFrom(launchTask);
}
@@ -909,7 +919,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
/**
* Draws the header of a task used for the window animation into a bitmap.
*/
- private GraphicBuffer drawThumbnailTransitionBitmap(Task toTask,
+ private Bitmap drawThumbnailTransitionBitmap(Task toTask,
TaskViewTransform toTransform) {
SystemServicesProxy ssp = Recents.getSystemServices();
int width = (int) toTransform.rect.width();
@@ -919,7 +929,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
mHeaderBar.onTaskViewSizeChanged(width, height);
if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+ return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
null, 1f, 0xFFff0000);
} else {
// Workaround for b/27815919, reset the callback so that we do not trigger an
@@ -932,7 +942,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
disabledInSafeMode);
mHeaderBar.onTaskDataLoaded();
mHeaderBar.setDimAlpha(toTransform.dimAlpha);
- return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+ return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
mHeaderBar, 1f, 0);
}
}
@@ -1047,7 +1057,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
EventBus.getDefault().send(new RecentsActivityStartingEvent());
if (future != null) {
- future.precacheSpecs();
+ future.composeSpecsSynchronous();
}
});
EventBus.getDefault().send(hideMenuEvent);
diff --git a/com/android/systemui/recents/RecentsImplProxy.java b/com/android/systemui/recents/RecentsImplProxy.java
index ff9e89e9..9493c78f 100644
--- a/com/android/systemui/recents/RecentsImplProxy.java
+++ b/com/android/systemui/recents/RecentsImplProxy.java
@@ -90,7 +90,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub {
}
@Override
- public void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+ public void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
Rect initialBounds) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
args.argi1 = topTaskId;
@@ -144,7 +144,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub {
break;
case MSG_DOCK_TOP_TASK:
args = (SomeArgs) msg.obj;
- mImpl.dockTopTask(args.argi1, args.argi2, args.argi3 = 0,
+ mImpl.splitPrimaryTask(args.argi1, args.argi2, args.argi3 = 0,
(Rect) args.arg1);
break;
case MSG_ON_DRAGGING_IN_RECENTS:
diff --git a/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java b/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
new file mode 100644
index 00000000..5d7f1ba5
--- /dev/null
+++ b/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.content.Context;
+
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+/**
+ * An implementation of {@link TaskStackChangeListener}.
+ */
+public abstract class SysUiTaskStackChangeListener extends TaskStackChangeListener {
+
+ /**
+ * Checks that the current user matches the user's SystemUI process.
+ */
+ protected final boolean checkCurrentUserId(Context context, boolean debug) {
+ int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+ return checkCurrentUserId(currentUserId, debug);
+ }
+}
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index 55ec5e7e..d89bab75 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -79,9 +79,11 @@ 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
@@ -112,7 +114,6 @@ public class SystemServicesProxy {
UserManager mUm;
Display mDisplay;
String mRecentsPackage;
- private TaskStackChangeListeners mTaskStackChangeListeners;
private int mCurrentUserId;
boolean mIsSafeMode;
@@ -122,7 +123,6 @@ public class SystemServicesProxy {
Paint mBgProtectionPaint;
Canvas mBgProtectionCanvas;
- private final Handler mHandler = new Handler();
private final Runnable mGcRunnable = new Runnable() {
@Override
public void run() {
@@ -155,7 +155,6 @@ public class SystemServicesProxy {
mRecentsPackage = context.getPackageName();
mIsSafeMode = mPm.isSafeMode();
mCurrentUserId = mAm.getCurrentUser();
- mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
// Get the dummy thumbnail width/heights
Resources res = context.getResources();
@@ -196,24 +195,6 @@ public class SystemServicesProxy {
}
/**
- * Returns the top running task.
- */
- public ActivityManager.RunningTaskInfo getRunningTask() {
- // Note: The set of running tasks from the system is ordered by recency
- try {
- List<ActivityManager.RunningTaskInfo> tasks = mIam.getFilteredTasks(1,
- ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
- WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
- if (tasks.isEmpty()) {
- return null;
- }
- return tasks.get(0);
- } catch (RemoteException e) {
- return null;
- }
- }
-
- /**
* Returns whether the recents activity is currently visible.
*/
public boolean isRecentsActivityVisible() {
@@ -225,6 +206,8 @@ public class SystemServicesProxy {
*
* @param isHomeStackVisible if provided, will return whether the home stack is visible
* regardless of the recents visibility
+ *
+ * TODO(winsonc): Refactor this check to just use the recents activity lifecycle
*/
public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
if (mIam == null) return false;
@@ -239,13 +222,13 @@ public class SystemServicesProxy {
final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
final int activityType = winConfig.getActivityType();
final int windowingMode = winConfig.getWindowingMode();
- if (activityType == ACTIVITY_TYPE_HOME) {
+ if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) {
homeStackInfo = stackInfo;
- } else if (activityType == ACTIVITY_TYPE_STANDARD
+ } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD
&& (windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
fullscreenStackInfo = stackInfo;
- } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+ } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) {
recentsStackInfo = stackInfo;
}
}
@@ -291,7 +274,7 @@ public class SystemServicesProxy {
try {
final ActivityOptions options = ActivityOptions.makeBasic();
- options.setDockCreateMode(createMode);
+ options.setSplitScreenCreateMode(createMode);
options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
mIam.startActivityFromRecents(taskId, options.toBundle());
return true;
@@ -301,14 +284,15 @@ public class SystemServicesProxy {
return false;
}
- /** Docks an already resumed task to the side of the screen. */
- public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
+ /** Moves an already resumed task to the side of the screen to initiate split screen. */
+ public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
+ Rect initialBounds) {
if (mIam == null) {
return false;
}
try {
- return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
+ return mIam.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
false /* animate */, initialBounds);
} catch (RemoteException e) {
e.printStackTrace();
@@ -363,32 +347,6 @@ public class SystemServicesProxy {
return insets.right > 0;
}
- /**
- * Cancels the current window transtion to/from Recents for the given task id.
- */
- public void cancelWindowTransition(int taskId) {
- if (mIam == null) return;
-
- try {
- mIam.cancelTaskWindowTransition(taskId);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Cancels the current thumbnail transtion to/from Recents for the given task id.
- */
- public void cancelThumbnailTransition(int taskId) {
- if (mIam == null) return;
-
- try {
- mIam.cancelTaskThumbnailTransition(taskId);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
/** Set the task's windowing mode. */
public void setTaskWindowingMode(int taskId, int windowingMode) {
if (mIam == null) return;
@@ -400,40 +358,6 @@ public class SystemServicesProxy {
}
}
- /** Removes the task */
- public void removeTask(final int taskId) {
- if (mAm == null) return;
-
- // Remove the task.
- mUiOffloadThread.submit(() -> {
- try {
- mIam.removeTask(taskId);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- });
- }
-
- /**
- * Sends a message to close other system windows.
- */
- public void sendCloseSystemWindows(String reason) {
- mUiOffloadThread.submit(() -> {
- try {
- mIam.closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- });
- }
-
- public ActivityManager.TaskDescription getTaskDescription(int taskId) {
- try {
- return mIam.getTaskDescription(taskId);
- } catch (RemoteException e) {
- return null;
- }
- }
-
/**
* Returns whether the provided {@param userId} represents the system user.
*/
@@ -556,56 +480,6 @@ public class SystemServicesProxy {
opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
}
- public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
- ActivityOptions options,
- @Nullable final StartActivityFromRecentsResultListener resultListener) {
- startActivityFromRecents(context, taskKey, taskName, options,
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, resultListener);
- }
-
- /** Starts an activity from recents. */
- public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
- ActivityOptions options, int windowingMode, int activityType,
- @Nullable final StartActivityFromRecentsResultListener resultListener) {
- if (mIam == null) {
- return;
- }
- if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- // We show non-visible docked tasks in Recents, but we always want to launch
- // them in the fullscreen stack.
- if (options == null) {
- options = ActivityOptions.makeBasic();
- }
- options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- } else if (windowingMode != WINDOWING_MODE_UNDEFINED
- || activityType != ACTIVITY_TYPE_UNDEFINED) {
- if (options == null) {
- options = ActivityOptions.makeBasic();
- }
- options.setLaunchWindowingMode(windowingMode);
- options.setLaunchActivityType(activityType);
- }
- final ActivityOptions finalOptions = options;
-
- // Execute this from another thread such that we can do other things (like caching the
- // bitmap for the thumbnail) while AM is busy starting our activity.
- mUiOffloadThread.submit(() -> {
- try {
- mIam.startActivityFromRecents(
- taskKey.id, finalOptions == null ? null : finalOptions.toBundle());
- if (resultListener != null) {
- mHandler.post(() -> resultListener.onStartActivityResult(true));
- }
- } catch (Exception e) {
- Log.e(TAG, context.getString(
- R.string.recents_launch_error_message, taskName), e);
- if (resultListener != null) {
- mHandler.post(() -> resultListener.onStartActivityResult(false));
- }
- }
- });
- }
-
/** Starts an in-place animation on the front most application windows. */
public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
if (mIam == null) return;
@@ -618,18 +492,6 @@ public class SystemServicesProxy {
}
}
- /**
- * Registers a task stack listener with the system.
- * This should be called on the main thread.
- */
- public void registerTaskStackListener(TaskStackChangeListener listener) {
- if (mIam == null) return;
-
- synchronized (mTaskStackChangeListeners) {
- mTaskStackChangeListeners.addListener(mIam, listener);
- }
- }
-
public void endProlongedAnimations() {
if (mWm == null) {
return;
diff --git a/com/android/systemui/recents/views/DockState.java b/com/android/systemui/recents/views/DockState.java
index 59f28680..65b96fbb 100644
--- a/com/android/systemui/recents/views/DockState.java
+++ b/com/android/systemui/recents/views/DockState.java
@@ -16,8 +16,8 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
@@ -71,19 +71,19 @@ public class DockState implements DropTarget {
public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
null, null, null);
public static final DockState LEFT = new DockState(DOCKED_LEFT,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
new RectF(0, 0, 0.5f, 1));
public static final DockState TOP = new DockState(DOCKED_TOP,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
new RectF(0, 0, 1, 0.5f));
public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
new RectF(0.5f, 0, 1, 1));
public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
new RectF(0, 0.5f, 1, 1));
diff --git a/com/android/systemui/recents/views/RecentsTransitionComposer.java b/com/android/systemui/recents/views/RecentsTransitionComposer.java
new file mode 100644
index 00000000..1c474301
--- /dev/null
+++ b/com/android/systemui/recents/views/RecentsTransitionComposer.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class to create the transition app animation specs to/from Recents
+ */
+public class RecentsTransitionComposer {
+
+ private static final String TAG = "RecentsTransitionComposer";
+
+ private Context mContext;
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+ public RecentsTransitionComposer(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link TaskView}
+ */
+ private static AppTransitionAnimationSpecCompat composeAnimationSpec(TaskStackView stackView,
+ TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
+ Bitmap b = null;
+ if (addHeaderBitmap) {
+ b = composeHeaderBitmap(taskView, transform);
+ if (b == null) {
+ return null;
+ }
+ }
+
+ Rect taskRect = new Rect();
+ transform.rect.round(taskRect);
+ // Disable in for low ram devices because each task does in Recents does not have fullscreen
+ // height (stackView height) and when transitioning to fullscreen app, the code below would
+ // force the task thumbnail to full stackView height immediately causing the transition
+ // jarring.
+ if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
+ stackView.getStack().getFrontMostTask()) {
+ taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
+ }
+ return new AppTransitionAnimationSpecCompat(taskView.getTask().key.id, b, taskRect);
+ }
+
+ /**
+ * Composes the transition spec when docking a task, which includes a full task bitmap.
+ */
+ public List<AppTransitionAnimationSpecCompat> composeDockAnimationSpec(TaskView taskView,
+ Rect bounds) {
+ mTmpTransform.fillIn(taskView);
+ Task task = taskView.getTask();
+ Bitmap buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform);
+ return Collections.singletonList(new AppTransitionAnimationSpecCompat(task.key.id, buffer,
+ bounds));
+ }
+
+ /**
+ * Composes the animation specs for all the tasks in the target stack.
+ */
+ public List<AppTransitionAnimationSpecCompat> composeAnimationSpecs(final Task task,
+ final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+ // Calculate the offscreen task rect (for tasks that are not backed by views)
+ TaskView taskView = stackView.getChildViewForTask(task);
+ TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
+ Rect offscreenTaskRect = new Rect();
+ stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
+
+ // If this is a full screen stack, the transition will be towards the single, full screen
+ // task. We only need the transition spec for this task.
+
+ // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
+ // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ || activityType == ACTIVITY_TYPE_ASSISTANT
+ || windowingMode == WINDOWING_MODE_UNDEFINED) {
+ List<AppTransitionAnimationSpecCompat> specs = new ArrayList<>();
+ if (taskView == null) {
+ specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
+ } else {
+ mTmpTransform.fillIn(taskView);
+ stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
+ AppTransitionAnimationSpecCompat spec = composeAnimationSpec(stackView, taskView,
+ mTmpTransform, true /* addHeaderBitmap */);
+ if (spec != null) {
+ specs.add(spec);
+ }
+ }
+ return specs;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link Task}
+ */
+ private static AppTransitionAnimationSpecCompat composeOffscreenAnimationSpec(Task task,
+ Rect taskRect) {
+ return new AppTransitionAnimationSpecCompat(task.key.id, null, taskRect);
+ }
+
+ public static Bitmap composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
+ float scale = transform.scale;
+ int fromWidth = (int) (transform.rect.width() * scale);
+ int fromHeight = (int) (transform.rect.height() * scale);
+ if (fromWidth == 0 || fromHeight == 0) {
+ Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
+ " at transform: " + transform);
+
+ return RecentsTransition.drawViewIntoHardwareBitmap(1, 1, null, 1f, 0x00ffffff);
+ } else {
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, null, 1f,
+ 0xFFff0000);
+ } else {
+ return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, taskView,
+ scale, 0);
+ }
+ }
+ }
+
+ private static Bitmap composeHeaderBitmap(TaskView taskView,
+ TaskViewTransform transform) {
+ float scale = transform.scale;
+ int headerWidth = (int) (transform.rect.width());
+ int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+ if (headerWidth == 0 || headerHeight == 0) {
+ return null;
+ }
+
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight, null, 1f,
+ 0xFFff0000);
+ } else {
+ return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight,
+ taskView.mHeaderView, scale, 0);
+ }
+ }
+}
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
deleted file mode 100644
index 7442904e..00000000
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ /dev/null
@@ -1,458 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.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.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.AppTransitionAnimationSpec;
-import android.view.DisplayListCanvas;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.RenderNode;
-import android.view.ThreadedRenderer;
-import android.view.View;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A helper class to create transitions to/from Recents
- */
-public class RecentsTransitionHelper {
-
- private static final String TAG = "RecentsTransitionHelper";
- private static final boolean DEBUG = false;
-
- /**
- * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
- * waiting for the specs to be retrieved.
- */
- private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
-
- @GuardedBy("this")
- private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
-
- private Context mContext;
- private Handler mHandler;
- private TaskViewTransform mTmpTransform = new TaskViewTransform();
-
- private class StartScreenPinningRunnableRunnable implements Runnable {
-
- private int taskId = -1;
-
- @Override
- public void run() {
- EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext, taskId));
- }
- }
- private StartScreenPinningRunnableRunnable mStartScreenPinningRunnable
- = new StartScreenPinningRunnableRunnable();
-
- public RecentsTransitionHelper(Context context) {
- mContext = context;
- mHandler = new Handler();
- }
-
- /**
- * Launches the specified {@link Task}.
- */
- public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
- final TaskStackView stackView, final TaskView taskView,
- final boolean screenPinningRequested, final int windowingMode, final int activityType) {
-
- final ActivityOptions.OnAnimationStartedListener animStartedListener;
- final AppTransitionAnimationSpecsFuture transitionFuture;
- if (taskView != null) {
-
- // Fetch window rect here already in order not to be blocked on lock contention in WM
- // when the future calls it.
- final Rect windowRect = Recents.getSystemServices().getWindowRect();
- transitionFuture = getAppTransitionFuture(() -> composeAnimationSpecs(
- task, stackView, windowingMode, activityType, windowRect));
- animStartedListener = new OnAnimationStartedListener() {
- private boolean mHandled;
-
- @Override
- public void onAnimationStarted() {
- if (mHandled) {
- return;
- }
- mHandled = true;
-
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
-
- if (screenPinningRequested) {
- // Request screen pinning after the animation runs
- mStartScreenPinningRunnable.taskId = task.key.id;
- mHandler.postDelayed(mStartScreenPinningRunnable, 350);
- }
-
- if (!Recents.getConfiguration().isLowRamDevice) {
- // Reset the state where we are waiting for the transition to start
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
- }
- }
- };
- } else {
- // This is only the case if the task is not on screen (scrolled offscreen for example)
- transitionFuture = null;
- animStartedListener = new OnAnimationStartedListener() {
- private boolean mHandled;
-
- @Override
- public void onAnimationStarted() {
- if (mHandled) {
- return;
- }
- mHandled = true;
-
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
-
- if (!Recents.getConfiguration().isLowRamDevice) {
- // Reset the state where we are waiting for the transition to start
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
- }
- }
- };
- }
-
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
- final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, transitionFuture != null ? transitionFuture.future : null,
- animStartedListener, true /* scaleUp */);
- if (taskView == null) {
- // If there is no task view, then we do not need to worry about animating out occluding
- // task views, and we can launch immediately
- startTaskActivity(stack, task, taskView, opts, transitionFuture,
- windowingMode, activityType);
- } else {
- LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
- screenPinningRequested);
- EventBus.getDefault().send(launchStartedEvent);
- startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
- activityType);
- }
- Recents.getSystemServices().sendCloseSystemWindows(
- StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
- }
-
- public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
- if (listener == null) {
- return null;
- }
- return new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- listener.onAnimationStarted();
- }
- });
- }
- };
- }
-
- /**
- * Starts the activity for the launch task.
- *
- * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
- * we are toggling recents and the launch-to task is now offscreen.
- */
- private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
- ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
- int windowingMode, int activityType) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
- activityType,
- succeeded -> {
- if (succeeded) {
- // Keep track of the index of the task launch
- int taskIndexFromFront = 0;
- int taskIndex = stack.indexOfStackTask(task);
- if (taskIndex > -1) {
- taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
- }
- EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
- } else {
- // Dismiss the task if we fail to launch it
- if (taskView != null) {
- taskView.dismissTask();
- }
-
- // Keep track of failed launches
- EventBus.getDefault().send(new LaunchTaskFailedEvent());
- }
- });
- if (transitionFuture != null) {
- mHandler.post(transitionFuture::precacheSpecs);
- }
- }
-
- /**
- * Creates a future which will later be queried for animation specs for this current transition.
- *
- * @param composer The implementation that composes the specs on the UI thread.
- */
- public AppTransitionAnimationSpecsFuture getAppTransitionFuture(
- final AnimationSpecComposer composer) {
- synchronized (this) {
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- }
- IAppTransitionAnimationSpecsFuture future = new IAppTransitionAnimationSpecsFuture.Stub() {
- @Override
- public AppTransitionAnimationSpec[] get() throws RemoteException {
- mHandler.post(() -> {
- synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composer.composeSpecs();
- RecentsTransitionHelper.this.notifyAll();
- }
- });
- synchronized (RecentsTransitionHelper.this) {
- while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
- try {
- RecentsTransitionHelper.this.wait();
- } catch (InterruptedException e) {}
- }
- if (mAppTransitionAnimationSpecs == null) {
- return null;
- }
- AppTransitionAnimationSpec[] specs
- = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
- mAppTransitionAnimationSpecs.toArray(specs);
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- return specs;
- }
- }
- };
- return new AppTransitionAnimationSpecsFuture(composer, future);
- }
-
- /**
- * Composes the transition spec when docking a task, which includes a full task bitmap.
- */
- public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
- Rect bounds) {
- mTmpTransform.fillIn(taskView);
- Task task = taskView.getTask();
- GraphicBuffer buffer = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform);
- return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer,
- bounds));
- }
-
- /**
- * Composes the animation specs for all the tasks in the target stack.
- */
- private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
- final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
- // Calculate the offscreen task rect (for tasks that are not backed by views)
- TaskView taskView = stackView.getChildViewForTask(task);
- TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
- Rect offscreenTaskRect = new Rect();
- stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
-
- // If this is a full screen stack, the transition will be towards the single, full screen
- // task. We only need the transition spec for this task.
-
- // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
- // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
- if (windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- || activityType == ACTIVITY_TYPE_ASSISTANT
- || windowingMode == WINDOWING_MODE_UNDEFINED) {
- List<AppTransitionAnimationSpec> specs = new ArrayList<>();
- if (taskView == null) {
- specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
- } else {
- mTmpTransform.fillIn(taskView);
- stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
- AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
- mTmpTransform, true /* addHeaderBitmap */);
- if (spec != null) {
- specs.add(spec);
- }
- }
- return specs;
- }
- return Collections.emptyList();
- }
-
- /**
- * Composes a single animation spec for the given {@link Task}
- */
- private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
- Rect taskRect) {
- return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
- }
-
- public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
- float scale = transform.scale;
- int fromWidth = (int) (transform.rect.width() * scale);
- int fromHeight = (int) (transform.rect.height() * scale);
- if (fromWidth == 0 || fromHeight == 0) {
- Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
- " at transform: " + transform);
-
- return drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff);
- } else {
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f, 0xFFff0000);
- } else {
- return drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView, scale, 0);
- }
- }
- }
-
- private static GraphicBuffer composeHeaderBitmap(TaskView taskView,
- TaskViewTransform transform) {
- float scale = transform.scale;
- int headerWidth = (int) (transform.rect.width());
- int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
- if (headerWidth == 0 || headerHeight == 0) {
- return null;
- }
-
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f, 0xFFff0000);
- } else {
- return drawViewIntoGraphicBuffer(headerWidth, headerHeight, taskView.mHeaderView,
- scale, 0);
- }
- }
-
- public static GraphicBuffer drawViewIntoGraphicBuffer(int bufferWidth, int bufferHeight,
- View view, float scale, int eraseColor) {
- RenderNode node = RenderNode.create("RecentsTransition", null);
- node.setLeftTopRightBottom(0, 0, bufferWidth, bufferHeight);
- node.setClipToBounds(false);
- DisplayListCanvas c = node.start(bufferWidth, bufferHeight);
- c.scale(scale, scale);
- if (eraseColor != 0) {
- c.drawColor(eraseColor);
- }
- if (view != null) {
- view.draw(c);
- }
- node.end(c);
- Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight);
- return hwBitmap.createGraphicBufferHandle();
- }
-
- /**
- * Composes a single animation spec for the given {@link TaskView}
- */
- private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView,
- TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
- GraphicBuffer b = null;
- if (addHeaderBitmap) {
- b = composeHeaderBitmap(taskView, transform);
- if (b == null) {
- return null;
- }
- }
-
- Rect taskRect = new Rect();
- transform.rect.round(taskRect);
- // Disable in for low ram devices because each task does in Recents does not have fullscreen
- // height (stackView height) and when transitioning to fullscreen app, the code below would
- // force the task thumbnail to full stackView height immediately causing the transition
- // jarring.
- if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
- stackView.getStack().getStackFrontMostTask()) {
- taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
- }
- return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
- }
-
- public interface AnimationSpecComposer {
- List<AppTransitionAnimationSpec> composeSpecs();
- }
-
- /**
- * Class to be returned from {@link #composeAnimationSpec} that gives access to both the future
- * and the anonymous class used for composing.
- */
- public class AppTransitionAnimationSpecsFuture {
-
- private final AnimationSpecComposer composer;
- private final IAppTransitionAnimationSpecsFuture future;
-
- private AppTransitionAnimationSpecsFuture(AnimationSpecComposer composer,
- IAppTransitionAnimationSpecsFuture future) {
- this.composer = composer;
- this.future = future;
- }
-
- public IAppTransitionAnimationSpecsFuture getFuture() {
- return future;
- }
-
- /**
- * Manually generates and caches the spec such that they are already available when the
- * future needs.
- */
- public void precacheSpecs() {
- synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composer.composeSpecs();
- }
- }
- }
-}
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index 5f12a04b..1440fc16 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,8 +16,12 @@
package com.android.systemui.recents.views;
+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;
@@ -28,8 +32,10 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
@@ -54,15 +60,22 @@ import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -76,8 +89,10 @@ 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.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
+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.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -101,6 +116,7 @@ public class RecentsView extends FrameLayout {
private static final int BUSY_RECENTS_TASK_COUNT = 3;
+ private Handler mHandler;
private TaskStackView mTaskStackView;
private TextView mStackActionButton;
private TextView mEmptyView;
@@ -126,7 +142,7 @@ public class RecentsView extends FrameLayout {
mMultiWindowBackgroundScrim.setAlpha(alpha);
};
- private RecentsTransitionHelper mTransitionHelper;
+ private RecentsTransitionComposer mTransitionHelper;
@ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
private RecentsViewTouchHandler mTouchHandler;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -148,7 +164,8 @@ public class RecentsView extends FrameLayout {
setWillNotDraw(false);
SystemServicesProxy ssp = Recents.getSystemServices();
- mTransitionHelper = new RecentsTransitionHelper(getContext());
+ mHandler = new Handler();
+ mTransitionHelper = new RecentsTransitionComposer(getContext());
mDividerSize = ssp.getDockedDividerSize(context);
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
@@ -279,7 +296,7 @@ public class RecentsView extends FrameLayout {
* @return True if it changed.
*/
private boolean updateBusyness() {
- final int taskCount = mTaskStackView.getStack().getStackTaskCount();
+ final int taskCount = mTaskStackView.getStack().getTaskCount();
final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT)
/ (float) BUSY_RECENTS_TASK_COUNT;
if (mBusynessFactor == busyness) {
@@ -518,9 +535,8 @@ public class RecentsView extends FrameLayout {
/**** EventBus Events ****/
public final void onBusEvent(LaunchTaskEvent event) {
- mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
- event.taskView, event.screenPinningRequested, event.targetWindowingMode,
- event.targetActivityType);
+ launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
+ event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
if (Recents.getConfiguration().isLowRamDevice) {
EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
}
@@ -595,29 +611,23 @@ public class RecentsView extends FrameLayout {
// Dock the task and launch it
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
- final OnAnimationStartedListener startedListener =
- new OnAnimationStartedListener() {
- @Override
- public void onAnimationStarted() {
- 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
- getStack().removeTask(event.task, null, true /* fromDockGesture */);
- }
+ 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
+ getStack().removeTask(event.task, null, true /* fromDockGesture */);
};
final Rect taskRect = getTaskRect(event.taskView);
- AppTransitionAnimationSpecsFuture future =
- mTransitionHelper.getAppTransitionFuture(
- new AnimationSpecComposer() {
- @Override
- public List<AppTransitionAnimationSpec> composeSpecs() {
- return mTransitionHelper.composeDockAnimationSpec(
- event.taskView, taskRect);
- }
- });
+ AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
+ getHandler()) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
+ }
+ };
ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
- mTransitionHelper.wrapStartedListener(startedListener),
+ RecentsTransition.wrapStartedListener(getHandler(), animStartedListener),
true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
@@ -881,7 +891,8 @@ public class RecentsView extends FrameLayout {
* @return the bounds of the stack action button.
*/
Rect getStackActionButtonBoundsFromStackLayout() {
- Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
+ Rect actionButtonRect = new Rect(
+ mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
int left, top;
if (Recents.getConfiguration().isLowRamDevice) {
Rect windowRect = Recents.getSystemServices().getWindowRect();
@@ -906,6 +917,141 @@ public class RecentsView extends FrameLayout {
return mStackActionButton;
}
+ /**
+ * Launches the specified {@link Task}.
+ */
+ public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
+ final TaskStackView stackView, final TaskView taskView,
+ final boolean screenPinningRequested, final int windowingMode, final int activityType) {
+
+ final Runnable animStartedListener;
+ final AppTransitionAnimationSpecsFuture transitionFuture;
+ if (taskView != null) {
+
+ // Fetch window rect here already in order not to be blocked on lock contention in WM
+ // when the future calls it.
+ final Rect windowRect = Recents.getSystemServices().getWindowRect();
+ transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
+ activityType, windowRect);
+ }
+ };
+ animStartedListener = new Runnable() {
+ private boolean mHandled;
+
+ @Override
+ public void run() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ if (screenPinningRequested) {
+ // Request screen pinning after the animation runs
+ mHandler.postDelayed(() -> {
+ EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
+ task.key.id));
+ }, 350);
+ }
+
+ if (!Recents.getConfiguration().isLowRamDevice) {
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+ }
+ }
+ };
+ } else {
+ // This is only the case if the task is not on screen (scrolled offscreen for example)
+ transitionFuture = null;
+ animStartedListener = new Runnable() {
+ private boolean mHandled;
+
+ @Override
+ public void run() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ if (!Recents.getConfiguration().isLowRamDevice) {
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+ }
+ }
+ };
+ }
+
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
+ final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
+ mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
+ animStartedListener);
+ if (taskView == null) {
+ // If there is no task view, then we do not need to worry about animating out occluding
+ // task views, and we can launch immediately
+ startTaskActivity(stack, task, taskView, opts, transitionFuture,
+ windowingMode, activityType);
+ } else {
+ LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
+ screenPinningRequested);
+ EventBus.getDefault().send(launchStartedEvent);
+ startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+ activityType);
+ }
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+ }
+
+ /**
+ * Starts the activity for the launch task.
+ *
+ * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
+ * we are toggling recents and the launch-to task is now offscreen.
+ */
+ private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
+ ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
+ int windowingMode, int activityType) {
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
+ windowingMode, activityType, succeeded -> {
+ if (succeeded) {
+ // Keep track of the index of the task launch
+ int taskIndexFromFront = 0;
+ int taskIndex = stack.indexOfTask(task);
+ if (taskIndex > -1) {
+ taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+ }
+ EventBus.getDefault().send(new LaunchTaskSucceededEvent(
+ taskIndexFromFront));
+ } else {
+ 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) {
+ taskView.dismissTask();
+ }
+
+ // Keep track of failed launches
+ EventBus.getDefault().send(new LaunchTaskFailedEvent());
+ }
+ }, getHandler());
+ if (transitionFuture != null) {
+ mHandler.post(transitionFuture::composeSpecsSynchronous);
+ }
+ }
+
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
diff --git a/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 0cfdbdec..5c69ae39 100644
--- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -40,7 +40,6 @@ import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
import java.util.ArrayList;
@@ -106,7 +105,7 @@ public class RecentsViewTouchHandler {
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
- if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getStackTaskCount() == 0) {
+ if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getTaskCount() == 0) {
EventBus.getDefault().send(new HideRecentsEvent(false, true));
}
return true;
diff --git a/com/android/systemui/recents/views/SystemBarScrimViews.java b/com/android/systemui/recents/views/SystemBarScrimViews.java
index 7827c590..170e39df 100644
--- a/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -154,7 +154,7 @@ public class SystemBarScrimViews {
public final void onBusEvent(MultiWindowStateChangedEvent event) {
mHasDockedTasks = event.inMultiWindow;
- animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
+ animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
}
public final void onBusEvent(final DragEndEvent event) {
@@ -166,7 +166,7 @@ public class SystemBarScrimViews {
public final void onBusEvent(final DragEndCancelledEvent event) {
// Restore the scrims to the normal state
- animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
+ animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
}
/**
diff --git a/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 26db26fa..67d09787 100644
--- a/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -449,7 +449,7 @@ public class TaskStackAnimationHelper {
// Get the current set of task transforms
int taskViewCount = mStackView.getTaskViews().size();
- ArrayList<Task> stackTasks = stack.getStackTasks();
+ ArrayList<Task> stackTasks = stack.getTasks();
mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
// Pick up the newly visible views after the scroll
@@ -541,7 +541,7 @@ public class TaskStackAnimationHelper {
TaskStackViewScroller stackScroller = mStackView.getScroller();
// Get the current set of task transforms
- ArrayList<Task> stackTasks = newStack.getStackTasks();
+ ArrayList<Task> stackTasks = newStack.getTasks();
mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
// Update the stack
@@ -563,7 +563,7 @@ public class TaskStackAnimationHelper {
false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
// Hide the front most task view until the scroll is complete
- Task frontMostTask = newStack.getStackFrontMostTask();
+ Task frontMostTask = newStack.getFrontMostTask();
final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
stackTasks.indexOf(frontMostTask));
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index acb058ce..600da041 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -438,7 +438,7 @@ public class TaskStackLayoutAlgorithm {
mTaskIndexMap.clear();
// Return early if we have no tasks
- ArrayList<Task> tasks = stack.getStackTasks();
+ ArrayList<Task> tasks = stack.getTasks();
if (tasks.isEmpty()) {
mFrontMostTaskP = 0;
mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
@@ -468,7 +468,7 @@ public class TaskStackLayoutAlgorithm {
// Calculate the min/max/initial scroll
Task launchTask = stack.getLaunchTarget();
int launchTaskIndex = launchTask != null
- ? stack.indexOfStackTask(launchTask)
+ ? stack.indexOfTask(launchTask)
: mNumStackTasks - 1;
if (getInitialFocusState() == STATE_FOCUSED) {
int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
@@ -557,7 +557,7 @@ public class TaskStackLayoutAlgorithm {
}
mUnfocusedRange.offset(0f);
- List<Task> tasks = stack.getStackTasks();
+ List<Task> tasks = stack.getTasks();
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
int indexFromFront = taskCount - i - 1;
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 428113a2..11975012 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -535,7 +535,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) {
// Get all the task transforms
- ArrayList<Task> tasks = mStack.getStackTasks();
+ ArrayList<Task> tasks = mStack.getTasks();
int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks,
mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks,
ignoreTaskOverrides);
@@ -557,7 +557,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// It is possible for the set of lingering TaskViews to differ from the stack if the
// stack was updated before the relayout. If the task view is no longer in the stack,
// then just return it back to the view pool.
- int taskIndex = mStack.indexOfStackTask(task);
+ int taskIndex = mStack.indexOfTask(task);
TaskViewTransform transform = null;
if (taskIndex != -1) {
transform = mCurrentTaskTransforms.get(taskIndex);
@@ -601,7 +601,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
} else {
// Reattach it in the right z order
- final int taskIndex = mStack.indexOfStackTask(task);
+ final int taskIndex = mStack.indexOfTask(task);
final int insertIndex = findTaskViewInsertIndex(task, taskIndex);
if (insertIndex != getTaskViews().indexOf(tv)){
detachViewFromParent(tv);
@@ -663,7 +663,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
continue;
}
- int taskIndex = mStack.indexOfStackTask(task);
+ int taskIndex = mStack.indexOfTask(task);
TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
if (animationOverrides != null && animationOverrides.containsKey(task)) {
animation = animationOverrides.get(task);
@@ -866,7 +866,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1;
final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
- mStack.getStackTasks().get(newFocusedTaskIndex) : null;
+ mStack.getTasks().get(newFocusedTaskIndex) : null;
// Reset the last focused task state if changed
if (mFocusedTask != null) {
@@ -953,10 +953,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
boolean cancelWindowAnimations, int timerIndicatorDuration) {
Task focusedTask = getFocusedTask();
- int newIndex = mStack.indexOfStackTask(focusedTask);
+ int newIndex = mStack.indexOfTask(focusedTask);
if (focusedTask != null) {
if (stackTasksOnly) {
- List<Task> tasks = mStack.getStackTasks();
+ List<Task> tasks = mStack.getTasks();
// Try the next task if it is a stack task
int tmpNewIndex = newIndex + (forward ? -1 : 1);
if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
@@ -971,7 +971,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
} else {
// We don't have a focused task
float stackScroll = mStackScroller.getStackScroll();
- ArrayList<Task> tasks = mStack.getStackTasks();
+ ArrayList<Task> tasks = mStack.getTasks();
int taskCount = tasks.size();
if (useGridLayout()) {
// For the grid layout, we directly set focus to the most recently used task
@@ -1060,8 +1060,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (taskViewCount > 0) {
TaskView backMostTask = taskViews.get(0);
TaskView frontMostTask = taskViews.get(taskViewCount - 1);
- event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask()));
- event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask()));
+ event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
+ event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
event.setContentDescription(frontMostTask.getTask().title);
}
event.setItemCount(mStack.getTaskCount());
@@ -1080,7 +1080,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Find the accessibility focused task
Task focusedTask = getAccessibilityFocusedTask();
info.setScrollable(true);
- int focusedTaskIndex = mStack.indexOfStackTask(focusedTask);
+ int focusedTaskIndex = mStack.indexOfTask(focusedTask);
if (focusedTaskIndex > 0 || !mStackActionButtonVisible) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
@@ -1101,7 +1101,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return true;
}
Task focusedTask = getAccessibilityFocusedTask();
- int taskIndex = mStack.indexOfStackTask(focusedTask);
+ int taskIndex = mStack.indexOfTask(focusedTask);
if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
@@ -1157,7 +1157,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* updateLayoutForStack() is called first.
*/
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
- return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
+ return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
}
/**
@@ -1328,10 +1328,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// We set the initial focused task view iff the following conditions are satisfied:
// 1. Recents is showing task views in stack layout.
// 2. Recents is launched with ALT + TAB.
- boolean setFocusOnFirstLayout = !useGridLayout() ||
- Recents.getConfiguration().getLaunchState().launchedWithAltTab;
+ boolean setFocusOnFirstLayout = !useGridLayout() || launchState.launchedWithAltTab;
if (setFocusOnFirstLayout) {
- int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(),
+ int focusedTaskIndex = getInitialFocusTaskIndex(launchState, mStack.getTaskCount(),
useGridLayout());
if (focusedTaskIndex != -1) {
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
@@ -1502,7 +1501,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
// Find the index where this task should be placed in the stack
- int taskIndex = mStack.indexOfStackTask(task);
+ int taskIndex = mStack.indexOfTask(task);
int insertIndex = findTaskViewInsertIndex(task, taskIndex);
// Add/attach the view to the hierarchy
@@ -1545,7 +1544,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Restore the action button visibility if it is the front most task view
- if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
+ if (mScreenPinningEnabled && tv.getTask() == mStack.getFrontMostTask()) {
tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
}
}
@@ -1669,7 +1668,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
event.packageName, event.userId);
// For other tasks, just remove them directly if they no longer exist
- ArrayList<Task> tasks = mStack.getStackTasks();
+ ArrayList<Task> tasks = mStack.getTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task t = tasks.get(i);
if (removedComponents.contains(t.key.getComponent())) {
@@ -1692,7 +1691,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) {
if (mStack.getTaskCount() > 0) {
- Task mostRecentTask = mStack.getStackFrontMostTask();
+ Task mostRecentTask = mStack.getFrontMostTask();
launchTask(mostRecentTask);
}
}
@@ -1786,7 +1785,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(final DismissAllTaskViewsEvent event) {
// Keep track of the tasks which will have their data removed
- ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks());
+ ArrayList<Task> tasks = new ArrayList<>(mStack.getTasks());
mAnimationHelper.startDeleteAllTasksAnimation(
getTaskViews(), useGridLayout(), event.getAnimationTrigger());
event.addPostAnimationCallback(new Runnable() {
@@ -1854,7 +1853,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(NavigateTaskViewEvent event) {
if (useGridLayout()) {
final int taskCount = mStack.getTaskCount();
- final int currentIndex = mStack.indexOfStackTask(getFocusedTask());
+ final int currentIndex = mStack.indexOfTask(getFocusedTask());
final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount,
currentIndex, event.direction);
setFocusedTask(nextIndex, false, true);
@@ -2000,7 +1999,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mFocusedTask != null) {
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- setFocusedTask(mStack.indexOfStackTask(mFocusedTask),
+ setFocusedTask(mStack.indexOfTask(mFocusedTask),
false /* scrollToTask */, launchState.launchedWithAltTab);
TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
if (mTouchExplorationEnabled && focusedTaskView != null) {
@@ -2131,7 +2130,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Task tvTask = taskViews.get(i).getTask();
if (tvTask == task) {
foundTaskView = true;
- } else if (taskIndex < mStack.indexOfStackTask(tvTask)) {
+ } else if (taskIndex < mStack.indexOfTask(tvTask)) {
if (foundTaskView) {
return i - 1;
} else {
@@ -2201,6 +2200,25 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
+ * Returns the task to focus given the current launch state.
+ */
+ private int getInitialFocusTaskIndex(RecentsActivityLaunchState launchState, int numTasks,
+ boolean useGridLayout) {
+ if (launchState.launchedFromApp) {
+ if (useGridLayout) {
+ // If coming from another app to the grid layout, focus the front most task
+ return numTasks - 1;
+ }
+
+ // If coming from another app, focus the next task
+ return Math.max(0, numTasks - 2);
+ } else {
+ // If coming from home, focus the front most task
+ return numTasks - 1;
+ }
+ }
+
+ /**
* Updates {@param transforms} to be the same size as {@param tasks}.
*/
private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
diff --git a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index b9ca2483..bfaa8cdb 100644
--- a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -446,7 +446,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
TaskView tv = (TaskView) v;
Task task = tv.getTask();
return !mSwipeHelperAnimations.containsKey(v) &&
- (mSv.getStack().indexOfStackTask(task) != -1);
+ (mSv.getStack().indexOfTask(task) != -1);
}
/**
@@ -476,7 +476,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mSv.addIgnoreTask(tv.getTask());
// Determine if we are animating the other tasks while dismissing this task
- mCurrentTasks = new ArrayList<Task>(mSv.getStack().getStackTasks());
+ mCurrentTasks = new ArrayList<Task>(mSv.getStack().getTasks());
MutableBoolean isFrontMostTask = new MutableBoolean(false);
Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
TaskStackLayoutAlgorithm layoutAlgorithm = mSv.getStackAlgorithm();
@@ -673,7 +673,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Returns the view at the specified coordinates */
private TaskView findViewAtPoint(int x, int y) {
- List<Task> tasks = mSv.getStack().getStackTasks();
+ List<Task> tasks = mSv.getStack().getTasks();
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
TaskView tv = mSv.getChildViewForTask(tasks.get(i));
diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java
index b4408474..f0278a69 100644
--- a/com/android/systemui/recents/views/TaskView.java
+++ b/com/android/systemui/recents/views/TaskView.java
@@ -55,6 +55,7 @@ import com.android.systemui.shared.recents.utilities.AnimationProps;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -683,7 +684,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
}
SystemServicesProxy ssp = Recents.getSystemServices();
boolean inBounds = false;
- Rect clipBounds = new Rect(mViewBounds.mClipBounds);
+ Rect clipBounds = new Rect(mViewBounds.getClipBounds());
if (!clipBounds.isEmpty()) {
// If we are clipping the view to the bounds, manually do the hit test.
clipBounds.scale(getScaleX());
diff --git a/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
index a029478c..3bdad314 100644
--- a/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
+++ b/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.views.grid;
import android.view.View;
-import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
/* An outline provider for grid-based task views. */
class AnimateableGridViewBounds extends AnimateableViewBounds {
diff --git a/com/android/systemui/recents/views/grid/GridTaskView.java b/com/android/systemui/recents/views/grid/GridTaskView.java
index 8b4700c5..0d511544 100644
--- a/com/android/systemui/recents/views/grid/GridTaskView.java
+++ b/com/android/systemui/recents/views/grid/GridTaskView.java
@@ -19,7 +19,7 @@ package com.android.systemui.recents.views.grid;
import android.content.Context;
import android.util.AttributeSet;
import com.android.systemui.R;
-import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
import com.android.systemui.recents.views.TaskView;
public class GridTaskView extends TaskView {
diff --git a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
index 95f1d583..fe6bafb2 100644
--- a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
+++ b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -125,7 +125,7 @@ public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListe
// We're returning from touch mode, set the focus to the previously focused task.
final TaskStack stack = mSv.getStack();
final int taskCount = stack.getTaskCount();
- final int k = stack.indexOfStackTask(mSv.getFocusedTask());
+ final int k = stack.indexOfTask(mSv.getFocusedTask());
final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount);
mSv.setFocusedTask(taskIndexToFocus, false, true);
}
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index 806a0738..8e2a25c1 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -70,21 +70,6 @@ public class RecentsTaskLoadPlan {
}
/**
- * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
- * to most-recent order.
- *
- * Note: Do not lock, callers should synchronize on the loader before making this call.
- */
- void preloadRawTasks() {
- int currentUserId = ActivityManagerWrapper.getInstance().getCurrentUserId();
- mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
- ActivityManager.getMaxRecentTasksStatic(), currentUserId);
-
- // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
- Collections.reverse(mRawTasks);
- }
-
- /**
* Preloads the list of recent tasks from the system. After this call, the TaskStack will
* have a list of all the recent tasks with their metadata, not including icons or
* thumbnails which were not cached and have to be loaded.
@@ -95,11 +80,15 @@ 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).
*/
- void preloadPlan(RecentsTaskLoader loader, int runningTaskId) {
+ public void preloadPlan(RecentsTaskLoader loader, int runningTaskId, int currentUserId) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
- preloadRawTasks();
+ mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
+ ActivityManager.getMaxRecentTasksStatic(), currentUserId);
+
+ // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
+ Collections.reverse(mRawTasks);
}
int taskCount = mRawTasks.size();
@@ -160,11 +149,11 @@ 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).
*/
- void executePlan(Options opts, RecentsTaskLoader loader) {
+ public void executePlan(Options opts, RecentsTaskLoader loader) {
Resources res = mContext.getResources();
// Iterate through each of the tasks and load them according to the load conditions.
- ArrayList<Task> tasks = mStack.getStackTasks();
+ ArrayList<Task> tasks = mStack.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
index de4c72c2..9a991cfa 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -147,9 +147,15 @@ public class RecentsTaskLoader {
/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
+ preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId());
+ }
+
+ /** Preloads recents tasks using the specified plan to store the output. */
+ public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
+ int currentUserId) {
try {
Trace.beginSection("preloadPlan");
- plan.preloadPlan(this, runningTaskId);
+ plan.preloadPlan(this, runningTaskId, currentUserId);
} finally {
Trace.endSection();
}
diff --git a/com/android/systemui/shared/recents/model/TaskFilter.java b/com/android/systemui/shared/recents/model/TaskFilter.java
index 9a1ff544..5f3dcd16 100644
--- a/com/android/systemui/shared/recents/model/TaskFilter.java
+++ b/com/android/systemui/shared/recents/model/TaskFilter.java
@@ -21,7 +21,7 @@ import android.util.SparseArray;
/**
* An interface for a task filter to query whether a particular task should show in a stack.
*/
-interface TaskFilter {
+public interface TaskFilter {
/** Returns whether the filter accepts the specified task */
boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
}
diff --git a/com/android/systemui/shared/recents/model/TaskStack.java b/com/android/systemui/shared/recents/model/TaskStack.java
index 693379d3..a3693976 100644
--- a/com/android/systemui/shared/recents/model/TaskStack.java
+++ b/com/android/systemui/shared/recents/model/TaskStack.java
@@ -19,7 +19,6 @@ package com.android.systemui.shared.recents.model;
import android.content.ComponentName;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.SparseArray;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.utilities.AnimationProps;
@@ -92,7 +91,7 @@ public class TaskStack {
boolean dismissRecentsIfAllRemoved) {
if (mStackTaskList.contains(t)) {
mStackTaskList.remove(t);
- Task newFrontMostTask = getStackFrontMostTask();
+ Task newFrontMostTask = getFrontMostTask();
if (mCb != null) {
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
@@ -183,7 +182,7 @@ public class TaskStack {
// Only callback for the removed tasks after the stack has updated
int removedTaskCount = removedTasks.size();
- Task newFrontMostTask = getStackFrontMostTask();
+ Task newFrontMostTask = getFrontMostTask();
for (int i = 0; i < removedTaskCount; i++) {
mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
AnimationProps.IMMEDIATE, false /* fromDockGesture */,
@@ -205,7 +204,7 @@ public class TaskStack {
/**
* Gets the front-most task in the stack.
*/
- public Task getStackFrontMostTask() {
+ public Task getFrontMostTask() {
ArrayList<Task> stackTasks = mStackTaskList.getTasks();
if (stackTasks.isEmpty()) {
return null;
@@ -228,7 +227,7 @@ public class TaskStack {
/**
* Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
*/
- public ArrayList<Task> getStackTasks() {
+ public ArrayList<Task> getTasks() {
return mStackTaskList.getTasks();
}
@@ -242,16 +241,9 @@ public class TaskStack {
}
/**
- * Returns the number of stacktasks.
- */
- public int getTaskCount() {
- return mStackTaskList.size();
- }
-
- /**
* Returns the number of stack tasks.
*/
- public int getStackTaskCount() {
+ public int getTaskCount() {
return mStackTaskList.size();
}
@@ -298,7 +290,7 @@ public class TaskStack {
if (nextLaunchTarget != null) {
return nextLaunchTarget;
}
- return getStackTasks().get(getTaskCount() - 1);
+ return getTasks().get(getTaskCount() - 1);
}
private Task getNextLaunchTargetRaw() {
@@ -306,15 +298,15 @@ public class TaskStack {
if (taskCount == 0) {
return null;
}
- int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+ int launchTaskIndex = indexOfTask(getLaunchTarget());
if (launchTaskIndex != -1 && launchTaskIndex > 0) {
- return getStackTasks().get(launchTaskIndex - 1);
+ return getTasks().get(launchTaskIndex - 1);
}
return null;
}
/** Returns the index of this task in this current task stack */
- public int indexOfStackTask(Task t) {
+ public int indexOfTask(Task t) {
return mStackTaskList.indexOf(t);
}
diff --git a/com/android/systemui/recents/views/AnimateableViewBounds.java b/com/android/systemui/shared/recents/view/AnimateableViewBounds.java
index b598ec6f..45728c40 100644
--- a/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/com/android/systemui/shared/recents/view/AnimateableViewBounds.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recents.views;
+package com.android.systemui.shared.recents.view;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -24,22 +24,19 @@ import android.view.ViewOutlineProvider;
import com.android.systemui.shared.recents.utilities.Utilities;
-/* An outline provider that has a clip and outline that can be animated. */
+/**
+ * An outline provider that has a clip and outline that can be animated.
+ */
public class AnimateableViewBounds extends ViewOutlineProvider {
private static final float MIN_ALPHA = 0.1f;
private static final float MAX_ALPHA = 0.8f;
protected View mSourceView;
- @ViewDebug.ExportedProperty(category="recents")
protected Rect mClipRect = new Rect();
- @ViewDebug.ExportedProperty(category="recents")
protected Rect mClipBounds = new Rect();
- @ViewDebug.ExportedProperty(category="recents")
protected Rect mLastClipBounds = new Rect();
- @ViewDebug.ExportedProperty(category="recents")
protected int mCornerRadius;
- @ViewDebug.ExportedProperty(category="recents")
protected float mAlpha = 1f;
public AnimateableViewBounds(View source, int cornerRadius) {
@@ -73,7 +70,7 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
/**
* Sets the view outline alpha.
*/
- void setAlpha(float alpha) {
+ public void setAlpha(float alpha) {
if (Float.compare(alpha, mAlpha) != 0) {
mAlpha = alpha;
// TODO, If both clip and alpha change in the same frame, only invalidate once
@@ -88,28 +85,43 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
return mAlpha;
}
- /** Sets the top clip. */
+ /**
+ * Sets the top clip.
+ */
public void setClipTop(int top) {
mClipRect.top = top;
updateClipBounds();
}
- /** Returns the top clip. */
+ /**
+ * @return the top clip.
+ */
public int getClipTop() {
return mClipRect.top;
}
- /** Sets the bottom clip. */
+ /**
+ * Sets the bottom clip.
+ */
public void setClipBottom(int bottom) {
mClipRect.bottom = bottom;
updateClipBounds();
}
- /** Returns the bottom clip. */
+ /**
+ * @return the bottom clip.
+ */
public int getClipBottom() {
return mClipRect.bottom;
}
+ /**
+ * @return the clip bounds.
+ */
+ public Rect getClipBounds() {
+ return mClipBounds;
+ }
+
protected void updateClipBounds() {
mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top),
mSourceView.getWidth() - Math.max(0, mClipRect.right),
diff --git a/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java
new file mode 100644
index 00000000..ebdc8842
--- /dev/null
+++ b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.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.recents.view;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.view.AppTransitionAnimationSpec;
+
+/**
+ * Wraps the internal app transition animation spec.
+ */
+public class AppTransitionAnimationSpecCompat {
+
+ private int mTaskId;
+ private Bitmap mBuffer;
+ private Rect mRect;
+
+ public AppTransitionAnimationSpecCompat(int taskId, Bitmap buffer, Rect rect) {
+ mTaskId = taskId;
+ mBuffer = buffer;
+ mRect = rect;
+ }
+
+ public AppTransitionAnimationSpec toAppTransitionAnimationSpec() {
+ return new AppTransitionAnimationSpec(mTaskId,
+ mBuffer != null ? mBuffer.createGraphicBufferHandle() : null, mRect);
+ }
+}
diff --git a/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
new file mode 100644
index 00000000..85d362a7
--- /dev/null
+++ b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.view;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * To be implemented by a particular animation to asynchronously provide the animation specs for a
+ * particular transition.
+ */
+public abstract class AppTransitionAnimationSpecsFuture {
+
+ private final Handler mHandler;
+ private FutureTask<List<AppTransitionAnimationSpecCompat>> mComposeTask = new FutureTask<>(
+ new Callable<List<AppTransitionAnimationSpecCompat>>() {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> call() throws Exception {
+ return composeSpecs();
+ }
+ });
+
+ private final IAppTransitionAnimationSpecsFuture mFuture =
+ new IAppTransitionAnimationSpecsFuture.Stub() {
+ @Override
+ public AppTransitionAnimationSpec[] get() throws RemoteException {
+ try {
+ if (!mComposeTask.isDone()) {
+ mHandler.post(mComposeTask);
+ }
+ List<AppTransitionAnimationSpecCompat> specs = mComposeTask.get();
+ if (specs == null) {
+ return null;
+ }
+
+ AppTransitionAnimationSpec[] arr = new AppTransitionAnimationSpec[specs.size()];
+ for (int i = 0; i < specs.size(); i++) {
+ arr[i] = specs.get(i).toAppTransitionAnimationSpec();
+ }
+ return arr;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ };
+
+ public AppTransitionAnimationSpecsFuture(Handler handler) {
+ mHandler = handler;
+ }
+
+ /**
+ * Returns the future to handle the call from window manager.
+ */
+ public final IAppTransitionAnimationSpecsFuture getFuture() {
+ return mFuture;
+ }
+
+ /**
+ * Called ahead of the future callback to compose the specs to be returned in the future.
+ */
+ public final void composeSpecsSynchronous() {
+ if (Looper.myLooper() != mHandler.getLooper()) {
+ throw new RuntimeException("composeSpecsSynchronous() called from wrong looper");
+ }
+ mComposeTask.run();
+ }
+
+ public abstract List<AppTransitionAnimationSpecCompat> composeSpecs();
+}
diff --git a/com/android/systemui/shared/recents/view/RecentsTransition.java b/com/android/systemui/shared/recents/view/RecentsTransition.java
new file mode 100644
index 00000000..ab890439
--- /dev/null
+++ b/com/android/systemui/shared/recents/view/RecentsTransition.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.view;
+
+import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+import java.util.function.Consumer;
+
+/**
+ * A helper class to create transitions to/from an App to Recents.
+ */
+public class RecentsTransition {
+
+ /**
+ * Creates a new transition aspect scaled transition activity options.
+ */
+ public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler,
+ boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture,
+ final Runnable animationStartCallback) {
+ final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() {
+ private boolean mHandled;
+
+ @Override
+ public void onAnimationStarted() {
+ // OnAnimationStartedListener can be called numerous times, so debounce here to
+ // prevent multiple callbacks
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ if (animationStartCallback != null) {
+ animationStartCallback.run();
+ }
+ }
+ };
+ final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(
+ context, handler,
+ animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null,
+ animStartedListener, scaleUp);
+ return opts;
+ }
+
+ /**
+ * Wraps a animation-start callback in a binder that can be called from window manager.
+ */
+ public static IRemoteCallback wrapStartedListener(final Handler handler,
+ final Runnable animationStartCallback) {
+ if (animationStartCallback == null) {
+ return null;
+ }
+ return new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ handler.post(animationStartCallback);
+ }
+ };
+ }
+
+ /**
+ * @return a {@link GraphicBuffer} with the {@param view} drawn into it. Result can be null if
+ * we were unable to allocate a hardware bitmap.
+ */
+ public static Bitmap drawViewIntoHardwareBitmap(int width, int height, final View view,
+ final float scale, final int eraseColor) {
+ return createHardwareBitmap(width, height, new Consumer<Canvas>() {
+ @Override
+ public void accept(Canvas c) {
+ c.scale(scale, scale);
+ if (eraseColor != 0) {
+ c.drawColor(eraseColor);
+ }
+ if (view != null) {
+ view.draw(c);
+ }
+ }
+ });
+ }
+
+ /**
+ * @return a hardware {@link Bitmap} after being drawn with the {@param consumer}. Result can be
+ * null if we were unable to allocate a hardware bitmap.
+ */
+ public static Bitmap createHardwareBitmap(int width, int height, Consumer<Canvas> consumer) {
+ RenderNode node = RenderNode.create("RecentsTransition", null);
+ node.setLeftTopRightBottom(0, 0, width, height);
+ node.setClipToBounds(false);
+ DisplayListCanvas c = node.start(width, height);
+ consumer.accept(c);
+ node.end(c);
+ return ThreadedRenderer.createHardwareBitmap(node, width, height);
+ }
+}
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 3f93f76a..f6fab86c 100644
--- a/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -17,11 +17,19 @@
package com.android.systemui.shared.system;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+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.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.IAssistDataReceiver;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -32,15 +40,21 @@ import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.IconDrawableFactory;
import android.util.Log;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
public class ActivityManagerWrapper {
@@ -50,11 +64,15 @@ public class ActivityManagerWrapper {
private final PackageManager mPackageManager;
private final IconDrawableFactory mDrawableFactory;
+ private final BackgroundExecutor mBackgroundExecutor;
+ private final TaskStackChangeListeners mTaskStackChangeListeners;
private ActivityManagerWrapper() {
final Context context = AppGlobals.getInitialApplication();
mPackageManager = context.getPackageManager();
mDrawableFactory = IconDrawableFactory.newInstance(context);
+ mBackgroundExecutor = BackgroundExecutor.get();
+ mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
}
public static ActivityManagerWrapper getInstance() {
@@ -75,6 +93,25 @@ public class ActivityManagerWrapper {
}
/**
+ * @return the top running task (can be {@code null}).
+ */
+ public ActivityManager.RunningTaskInfo getRunningTask() {
+ // Note: The set of running tasks from the system is ordered by recency
+ try {
+ List<ActivityManager.RunningTaskInfo> tasks =
+ ActivityManager.getService().getFilteredTasks(1,
+ ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
+ WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
+ if (tasks.isEmpty()) {
+ return null;
+ }
+ return tasks.get(0);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* @return a list of the recents tasks.
*/
public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
@@ -198,4 +235,186 @@ public class ActivityManagerWrapper {
}
return label;
}
+
+ /**
+ * Starts the recents activity. The caller should manage the thread on which this is called.
+ */
+ public void startRecentsActivity(AssistDataReceiverCompat assistDataReceiver, Bundle options,
+ ActivityOptions opts, int userId, Consumer<Boolean> resultCallback,
+ Handler resultCallbackHandler) {
+ Bundle activityOptions = opts != null ? opts.toBundle() : null;
+ try {
+ IAssistDataReceiver receiver = null;
+ if (assistDataReceiver != null) {
+ receiver = new IAssistDataReceiver.Stub() {
+ public void onHandleAssistData(Bundle resultData) {
+ assistDataReceiver.onHandleAssistData(resultData);
+ }
+ public void onHandleAssistScreenshot(Bitmap screenshot) {
+ assistDataReceiver.onHandleAssistScreenshot(screenshot);
+ }
+ };
+ }
+ ActivityManager.getService().startRecentsActivity(receiver, options, activityOptions,
+ userId);
+ if (resultCallback != null) {
+ resultCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ resultCallback.accept(true);
+ }
+ });
+ }
+ } catch (Exception e) {
+ if (resultCallback != null) {
+ resultCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ resultCallback.accept(false);
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Starts a task from Recents.
+ *
+ * @see {@link #startActivityFromRecentsAsync(TaskKey, ActivityOptions, int, int, Consumer, Handler)}
+ */
+ public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options,
+ Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
+ startActivityFromRecentsAsync(taskKey, options, WINDOWING_MODE_UNDEFINED,
+ ACTIVITY_TYPE_UNDEFINED, resultCallback, resultCallbackHandler);
+ }
+
+ /**
+ * Starts a task from Recents.
+ *
+ * @param resultCallback The result success callback
+ * @param resultCallbackHandler The handler to receive the result callback
+ */
+ public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options,
+ int windowingMode, int activityType, Consumer<Boolean> resultCallback,
+ Handler resultCallbackHandler) {
+ if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // We show non-visible docked tasks in Recents, but we always want to launch
+ // them in the fullscreen stack.
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ } else if (windowingMode != WINDOWING_MODE_UNDEFINED
+ || activityType != ACTIVITY_TYPE_UNDEFINED) {
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ options.setLaunchWindowingMode(windowingMode);
+ options.setLaunchActivityType(activityType);
+ }
+ final ActivityOptions finalOptions = options;
+
+ // Execute this from another thread such that we can do other things (like caching the
+ // bitmap for the thumbnail) while AM is busy starting our activity.
+ mBackgroundExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ 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);
+ }
+ });
+ }
+ } catch (Exception e) {
+ if (resultCallback != null) {
+ resultCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ resultCallback.accept(false);
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Registers a task stack listener with the system.
+ * This should be called on the main thread.
+ */
+ public void registerTaskStackListener(TaskStackChangeListener listener) {
+ synchronized (mTaskStackChangeListeners) {
+ mTaskStackChangeListeners.addListener(ActivityManager.getService(), listener);
+ }
+ }
+
+ /**
+ * Unregisters a task stack listener with the system.
+ * This should be called on the main thread.
+ */
+ public void unregisterTaskStackListener(TaskStackChangeListener listener) {
+ synchronized (mTaskStackChangeListeners) {
+ mTaskStackChangeListeners.removeListener(listener);
+ }
+ }
+
+ /**
+ * Requests that the system close any open system windows (including other SystemUI).
+ */
+ public void closeSystemWindows(String reason) {
+ mBackgroundExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ActivityManager.getService().closeSystemDialogs(reason);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to close system windows", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes a task by id.
+ */
+ public void removeTask(int taskId) {
+ mBackgroundExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ActivityManager.getService().removeTask(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to remove task=" + taskId, e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Cancels the current window transtion to/from Recents for the given task id.
+ */
+ public void cancelWindowTransition(int taskId) {
+ try {
+ ActivityManager.getService().cancelTaskWindowTransition(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+ }
+ }
+
+ /**
+ * Cancels the current thumbnail transtion to/from Recents for the given task id.
+ */
+ public void cancelThumbnailTransition(int taskId) {
+ try {
+ ActivityManager.getService().cancelTaskThumbnailTransition(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+ }
+ }
}
diff --git a/com/android/systemui/shared/system/AssistDataReceiverCompat.java b/com/android/systemui/shared/system/AssistDataReceiverCompat.java
new file mode 100644
index 00000000..cd943f62
--- /dev/null
+++ b/com/android/systemui/shared/system/AssistDataReceiverCompat.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+
+/**
+ * Abstract class for assist data receivers.
+ */
+public abstract class AssistDataReceiverCompat {
+ public abstract void onHandleAssistData(Bundle resultData);
+ public abstract void onHandleAssistScreenshot(Bitmap screenshot);
+}
diff --git a/com/android/systemui/shared/system/BackgroundExecutor.java b/com/android/systemui/shared/system/BackgroundExecutor.java
new file mode 100644
index 00000000..cfd1f9a5
--- /dev/null
+++ b/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Offloads work from other threads by running it in a background thread.
+ */
+public class BackgroundExecutor {
+
+ private static final BackgroundExecutor sInstance = new BackgroundExecutor();
+
+ private final ExecutorService mExecutorService = Executors.newFixedThreadPool(2);
+
+ /**
+ * @return the static instance of the background executor.
+ */
+ public static BackgroundExecutor get() {
+ return sInstance;
+ }
+
+ /**
+ * Runs the given {@param runnable} on one of the background executor threads.
+ */
+ public Future<?> submit(Runnable runnable) {
+ return mExecutorService.submit(runnable);
+ }
+
+ /**
+ * Runs the given {@param runnable} on one of the background executor threads. Return
+ * {@param result} when the future is resolved.
+ */
+ public <T> Future<T> submit(Runnable runnable, T result) {
+ return mExecutorService.submit(runnable, result);
+ }
+}
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListener.java b/com/android/systemui/shared/system/TaskStackChangeListener.java
index 6d0952ab..17cb0d80 100644
--- a/com/android/systemui/recents/misc/TaskStackChangeListener.java
+++ b/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -14,26 +14,26 @@
* limitations under the License.
*/
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
import android.app.ActivityManager.TaskSnapshot;
-import android.content.Context;
import android.os.UserHandle;
import android.util.Log;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
/**
- * An abstract class to track task stack changes.
- * Classes should implement this instead of {@link android.app.ITaskStackListener}
- * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+ * An interface to track task stack changes. Classes should implement this instead of
+ * {@link android.app.ITaskStackListener} to reduce IPC calls from system services.
*/
public abstract class TaskStackChangeListener {
- /**
- * NOTE: This call is made of the thread that the binder call comes in on.
- */
+ // Binder thread callbacks
public void onTaskStackChangedBackground() { }
+
+ // Main thread callbacks
public void onTaskStackChanged() { }
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
+ public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { }
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
public void onActivityUnpinned() { }
public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
@@ -45,17 +45,16 @@ public abstract class TaskStackChangeListener {
public void onTaskProfileLocked(int taskId, int userId) { }
/**
- * Checks that the current user matches the user's SystemUI process. Since
+ * Checks that the current user matches the process. Since
* {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
- * TaskStackChangeListener should make this call to verify that we don't act on events from other
- * user's processes.
+ * {@link TaskStackChangeListener} should make this call to verify that we don't act on events
+ * originating from another user's interactions.
*/
- protected final boolean checkCurrentUserId(Context context, boolean debug) {
+ protected final boolean checkCurrentUserId(int currentUserId, boolean debug) {
int processUserId = UserHandle.myUserId();
- int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
if (processUserId != currentUserId) {
if (debug) {
- Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+ Log.d("TaskStackChangeListener", "UID mismatch. Process is uid=" + processUserId
+ " and the current user is uid=" + currentUserId);
}
return false;
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 8eb70f04..81c37a95 100644
--- a/com/android/systemui/recents/misc/TaskStackChangeListeners.java
+++ b/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
import android.app.ActivityManager.TaskSnapshot;
import android.app.IActivityManager;
@@ -26,6 +26,8 @@ import android.os.RemoteException;
import android.os.Trace;
import android.util.Log;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
import java.util.ArrayList;
import java.util.List;
@@ -43,6 +45,7 @@ public class TaskStackChangeListeners extends TaskStackListener {
private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
private final Handler mHandler;
+ private boolean mRegistered;
public TaskStackChangeListeners(Looper looper) {
mHandler = new H(looper);
@@ -50,16 +53,21 @@ public class TaskStackChangeListeners extends TaskStackListener {
public void addListener(IActivityManager am, TaskStackChangeListener listener) {
mTaskStackListeners.add(listener);
- if (mTaskStackListeners.size() == 1) {
+ if (!mRegistered) {
// Register mTaskStackListener to IActivityManager only once if needed.
try {
am.registerTaskStackListener(this);
+ mRegistered = true;
} catch (Exception e) {
Log.w(TAG, "Failed to call registerTaskStackListener", e);
}
}
}
+ public void removeListener(TaskStackChangeListener listener) {
+ mTaskStackListeners.remove(listener);
+ }
+
@Override
public void onTaskStackChanged() throws RemoteException {
// Call the task changed callback for the non-ui thread listeners first
@@ -170,7 +178,7 @@ public class TaskStackChangeListeners extends TaskStackListener {
Trace.beginSection("onTaskSnapshotChanged");
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
- (TaskSnapshot) msg.obj);
+ new ThumbnailData((TaskSnapshot) msg.obj));
}
Trace.endSection();
break;
diff --git a/com/android/systemui/shared/system/WindowManagerWrapper.java b/com/android/systemui/shared/system/WindowManagerWrapper.java
new file mode 100644
index 00000000..1477558a
--- /dev/null
+++ b/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Display.DEFAULT_DISPLAY;
+
+import android.graphics.Rect;
+import android.view.WindowManagerGlobal;
+
+public class WindowManagerWrapper {
+
+ private static final String TAG = "WindowManagerWrapper";
+
+ private static final WindowManagerWrapper sInstance = new WindowManagerWrapper();
+
+ public static WindowManagerWrapper getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * @return the stable insets for the primary display.
+ */
+ public void getStableInsets(Rect outStableInsets) {
+ try {
+ WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY,
+ outStableInsets);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 195f4d3f..1cda3011 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,8 +16,8 @@
package com.android.systemui.shortcut;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.os.UserHandle.USER_CURRENT;
import android.app.ActivityManager;
@@ -92,8 +92,8 @@ public class ShortcutKeyDispatcher extends SystemUI
// If there is no window docked, we dock the top-most window.
Recents recents = getComponent(Recents.class);
int dockMode = (shortcutCode == SC_DOCK_LEFT)
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+ ? 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(
diff --git a/com/android/systemui/stackdivider/DividerView.java b/com/android/systemui/stackdivider/DividerView.java
index 7bcef574..1596d120 100644
--- a/com/android/systemui/stackdivider/DividerView.java
+++ b/com/android/systemui/stackdivider/DividerView.java
@@ -439,7 +439,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
if (mMinimizedSnapAlgorithm == null) {
mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
- mStableInsets, mDockedStackMinimized && mHomeStackResizable);
+ mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
}
}
diff --git a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 0997983a..826fa6ce 100644
--- a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -31,8 +31,9 @@ import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
import com.android.systemui.recents.events.component.ShowUserToastEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.events.StartedDragingEvent;
import com.android.systemui.stackdivider.events.StoppedDragingEvent;
@@ -75,8 +76,8 @@ public class ForcedResizableInfoActivityController {
public ForcedResizableInfoActivityController(Context context) {
mContext = context;
EventBus.getDefault().register(this);
- SystemServicesProxy.getInstance(context).registerTaskStackListener(
- new TaskStackChangeListener() {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(
+ new SysUiTaskStackChangeListener() {
@Override
public void onActivityForcedResizable(String packageName, int taskId,
int reason) {
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6c5f4b23..8ff950ed 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -69,6 +69,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -453,6 +454,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
headsUpheight = mMaxHeadsUpHeight;
}
+ NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP);
+ if (headsUpWrapper != null) {
+ headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight());
+ }
layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
mNotificationAmbientHeight);
}
@@ -1256,16 +1262,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void initDimens() {
- mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
- mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
- mNotificationMinHeightLarge = getFontScaledHeight(
+ mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_legacy);
+ mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height);
+ mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
- mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
- mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
- mMaxHeadsUpHeightLegacy = getFontScaledHeight(
+ mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_height);
+ mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_ambient_height);
+ mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
- mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
- mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+ mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height);
+ mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_increased);
Resources res = getResources();
@@ -1280,17 +1291,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * @param dimenId the dimen to look up
- * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
- */
- private int getFontScaledHeight(int dimenId) {
- int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
- float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
- getResources().getDisplayMetrics().density);
- return (int) (dimensionPixelSize * factor);
- }
-
- /**
* Resets this view so it can be re-used for an updated notification.
*/
public void reset() {
diff --git a/com/android/systemui/statusbar/NotificationGuts.java b/com/android/systemui/statusbar/NotificationGuts.java
index 54d622b3..c4024a57 100644
--- a/com/android/systemui/statusbar/NotificationGuts.java
+++ b/com/android/systemui/statusbar/NotificationGuts.java
@@ -18,46 +18,21 @@ package com.android.systemui.statusbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.app.INotificationManager;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.Utils;
+
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.stack.StackStateAnimator;
-import java.util.Set;
-
/**
* The guts of a notification revealed when performing a long press.
*/
diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java
new file mode 100644
index 00000000..b585bdff
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
+ * closing guts, and keeping track of the currently exposed notification guts.
+ */
+public class NotificationGutsManager implements Dumpable {
+ private static final String TAG = "NotificationGutsManager";
+
+ // Must match constant in Settings. Used to highlight preferences when linking to Settings.
+ private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final Set<String> mNonBlockablePkgs;
+ private final NotificationPresenter mPresenter;
+ // TODO: Create NotificationListContainer interface and use it instead of
+ // NotificationStackScrollLayout here
+ private final NotificationStackScrollLayout mStackScroller;
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+ // which notification is currently being longpress-examined by the user
+ private NotificationGuts mNotificationGutsExposed;
+ private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
+ private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+ private String mKeyToRemoveOnGutsClosed;
+
+ public NotificationGutsManager(
+ NotificationPresenter presenter,
+ NotificationStackScrollLayout stackScroller,
+ NotificationInfo.CheckSaveListener checkSaveListener,
+ Context context) {
+ mPresenter = presenter;
+ mStackScroller = stackScroller;
+ mCheckSaveListener = checkSaveListener;
+ mContext = context;
+ Resources res = context.getResources();
+
+ mNonBlockablePkgs = new HashSet<>();
+ Collections.addAll(mNonBlockablePkgs, res.getStringArray(
+ com.android.internal.R.array.config_nonBlockableNotificationPackages));
+
+ mAccessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ public String getKeyToRemoveOnGutsClosed() {
+ return mKeyToRemoveOnGutsClosed;
+ }
+
+ public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
+ mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
+ }
+
+ private void saveAndCloseNotificationMenu(
+ ExpandableNotificationRow row, NotificationGuts guts, View done) {
+ guts.resetFalsingCheck();
+ int[] rowLocation = new int[2];
+ int[] doneLocation = new int[2];
+ row.getLocationOnScreen(rowLocation);
+ done.getLocationOnScreen(doneLocation);
+
+ final int centerX = done.getWidth() / 2;
+ final int centerY = done.getHeight() / 2;
+ final int x = doneLocation[0] - rowLocation[0] + centerX;
+ final int y = doneLocation[1] - rowLocation[1] + centerY;
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, x, y, true /* resetMenu */);
+ }
+
+ /**
+ * Sends an intent to open the notification settings for a particular package and optional
+ * channel.
+ */
+ private void startAppNotificationSettingsActivity(String packageName, final int appUid,
+ final NotificationChannel channel) {
+ final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+ intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+ if (channel != null) {
+ intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+ }
+ mPresenter.startNotificationGutsIntent(intent, appUid);
+ }
+
+ public void bindGuts(final ExpandableNotificationRow row) {
+ bindGuts(row, mGutsMenuItem);
+ }
+
+ private void bindGuts(final ExpandableNotificationRow row,
+ NotificationMenuRowPlugin.MenuItem item) {
+ row.inflateGuts();
+ row.setGutsView(item);
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ row.setTag(sbn.getPackageName());
+ final NotificationGuts guts = row.getGuts();
+ guts.setClosedListener((NotificationGuts g) -> {
+ if (!g.willBeRemoved() && !row.isRemoved()) {
+ mStackScroller.onHeightChanged(
+ row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
+ }
+ if (mNotificationGutsExposed == g) {
+ mNotificationGutsExposed = null;
+ mGutsMenuItem = null;
+ }
+ String key = sbn.getKey();
+ if (key.equals(mKeyToRemoveOnGutsClosed)) {
+ mKeyToRemoveOnGutsClosed = null;
+ mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+ }
+ });
+
+ View gutsView = item.getGutsView();
+ if (gutsView instanceof NotificationSnooze) {
+ NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
+ snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+ snoozeGuts.setStatusBarNotification(sbn);
+ snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
+ guts.setHeightChangedListener((NotificationGuts g) -> {
+ mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+ });
+ }
+
+ if (gutsView instanceof NotificationInfo) {
+ final UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ userHandle.getIdentifier());
+ final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final String pkg = sbn.getPackageName();
+ NotificationInfo info = (NotificationInfo) gutsView;
+ // Settings link is only valid for notifications that specify a user, unless this is the
+ // system user.
+ NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+ if (!userHandle.equals(UserHandle.ALL)
+ || mPresenter.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ startAppNotificationSettingsActivity(pkg, appUid, channel);
+ };
+ }
+ final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
+ Intent intent) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+ guts.resetFalsingCheck();
+ mPresenter.startNotificationGutsIntent(intent, sbn.getUid());
+ };
+ final View.OnClickListener onDoneClick = (View v) -> {
+ saveAndCloseNotificationMenu(row, guts, v);
+ };
+
+ ArraySet<NotificationChannel> channels = new ArraySet<>();
+ channels.add(row.getEntry().channel);
+ if (row.isSummaryWithChildren()) {
+ // If this is a summary, then add in the children notification channels for the
+ // same user and pkg.
+ final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
+ final int numChildren = childrenRows.size();
+ for (int i = 0; i < numChildren; i++) {
+ final ExpandableNotificationRow childRow = childrenRows.get(i);
+ final NotificationChannel childChannel = childRow.getEntry().channel;
+ final StatusBarNotification childSbn = childRow.getStatusBarNotification();
+ if (childSbn.getUser().equals(userHandle) &&
+ childSbn.getPackageName().equals(pkg)) {
+ channels.add(childChannel);
+ }
+ }
+ }
+ try {
+ info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
+ row.getEntry().channel.getImportance(), sbn, onSettingsClick,
+ onAppSettingsClick, onDoneClick, mCheckSaveListener,
+ mNonBlockablePkgs);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ }
+
+ /**
+ * Closes guts or notification menus that might be visible and saves any changes.
+ *
+ * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
+ * @param force true if guts should be closed regardless of state (used for snooze only).
+ * @param removeControls true if controls (e.g. info) should be closed.
+ * @param x if closed based on touch location, this is the x touch location.
+ * @param y if closed based on touch location, this is the y touch location.
+ * @param resetMenu if any notification menus that might be revealed should be closed.
+ */
+ public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
+ int x, int y, boolean resetMenu) {
+ if (mNotificationGutsExposed != null) {
+ mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
+ }
+ if (resetMenu) {
+ mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+ }
+ }
+
+ /**
+ * Returns the exposed NotificationGuts or null if none are exposed.
+ */
+ public NotificationGuts getExposedGuts() {
+ return mNotificationGutsExposed;
+ }
+
+ public void setExposedGuts(NotificationGuts guts) {
+ mNotificationGutsExposed = guts;
+ }
+
+ /**
+ * Opens guts on the given ExpandableNotificationRow |v|.
+ *
+ * @param v ExpandableNotificationRow to open guts on
+ * @param x x coordinate of origin of circular reveal
+ * @param y y coordinate of origin of circular reveal
+ * @param item MenuItem the guts should display
+ * @return true if guts was opened
+ */
+ public boolean openGuts(View v, int x, int y,
+ NotificationMenuRowPlugin.MenuItem item) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ return false;
+ }
+
+ if (v.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to window");
+ return false;
+ }
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (row.isDark()) {
+ return false;
+ }
+ v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ if (row.areGutsExposed()) {
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ true /* resetMenu */);
+ return false;
+ }
+ bindGuts(row, item);
+ NotificationGuts guts = row.getGuts();
+
+ // Assume we are a status_bar_notification_row
+ if (guts == null) {
+ // This view has no guts. Examples are the more card or the dismiss all view
+ return false;
+ }
+
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+
+ // ensure that it's laid but not visible until actually laid out
+ guts.setVisibility(View.INVISIBLE);
+ // Post to ensure the the guts are properly laid out.
+ guts.post(new Runnable() {
+ @Override
+ public void run() {
+ if (row.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to "
+ + "window");
+ return;
+ }
+ closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
+ guts.setVisibility(View.VISIBLE);
+ final double horz = Math.max(guts.getWidth() - x, x);
+ final double vert = Math.max(guts.getHeight() - y, y);
+ final float r = (float) Math.hypot(horz, vert);
+ final Animator a
+ = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ a.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ // Move the notification view back over the menu
+ row.resetTranslation();
+ }
+ });
+ a.start();
+ final boolean needsFalsingProtection =
+ (mPresenter.isPresenterLocked() &&
+ !mAccessibilityManager.isTouchExplorationEnabled());
+ guts.setExposed(true /* exposed */, needsFalsingProtection);
+ row.closeRemoteInput();
+ mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+ mNotificationGutsExposed = guts;
+ mGutsMenuItem = item;
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print("mKeyToRemoveOnGutsClosed: ");
+ pw.println(mKeyToRemoveOnGutsClosed);
+ }
+}
diff --git a/com/android/systemui/statusbar/NotificationInfo.java b/com/android/systemui/statusbar/NotificationInfo.java
index 3b23a0c0..8d1bb5fe 100644
--- a/com/android/systemui/statusbar/NotificationInfo.java
+++ b/com/android/systemui/statusbar/NotificationInfo.java
@@ -84,7 +84,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
public interface CheckSaveListener {
// Invoked when importance has changed and the NotificationInfo wants to try to save it.
// Listener should run saveImportance unless the change should be canceled.
- void checkSave(Runnable saveImportance);
+ void checkSave(Runnable saveImportance, StatusBarNotification sbn);
}
public interface OnSettingsClickListener {
@@ -409,7 +409,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
public boolean handleCloseControls(boolean save, boolean force) {
if (save && hasImportanceChanged()) {
if (mCheckSaveListener != null) {
- mCheckSaveListener.checkSave(() -> { saveImportance(); });
+ mCheckSaveListener.checkSave(this::saveImportance, mSbn);
} else {
saveImportance();
}
diff --git a/com/android/systemui/statusbar/NotificationMediaManager.java b/com/android/systemui/statusbar/NotificationMediaManager.java
new file mode 100644
index 00000000..e65bab29
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -0,0 +1,245 @@
+package com.android.systemui.statusbar;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles tasks and state related to media notifications. For example, there is a 'current' media
+ * notification, which this class keeps track of.
+ */
+public class NotificationMediaManager implements Dumpable {
+ private static final String TAG = "NotificationMediaManager";
+ public static final boolean DEBUG_MEDIA = false;
+
+ private final NotificationPresenter mPresenter;
+ private final Context mContext;
+ private final MediaSessionManager mMediaSessionManager;
+ private MediaController mMediaController;
+ private String mMediaNotificationKey;
+ private MediaMetadata mMediaMetadata;
+
+ private final MediaController.Callback mMediaListener = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ super.onPlaybackStateChanged(state);
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
+ }
+ if (state != null) {
+ if (!isPlaybackActive(state.getState())) {
+ clearCurrentMediaNotification();
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ }
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ super.onMetadataChanged(metadata);
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
+ }
+ mMediaMetadata = metadata;
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ };
+
+ public NotificationMediaManager(NotificationPresenter presenter, Context context) {
+ mPresenter = presenter;
+ mContext = context;
+ mMediaSessionManager
+ = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
+ // in session state
+ }
+
+ public void onNotificationRemoved(String key) {
+ if (key.equals(mMediaNotificationKey)) {
+ clearCurrentMediaNotification();
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ }
+
+ public String getMediaNotificationKey() {
+ return mMediaNotificationKey;
+ }
+
+ public MediaMetadata getMediaMetadata() {
+ return mMediaMetadata;
+ }
+
+ public void findAndUpdateMediaNotifications() {
+ boolean metaDataChanged = false;
+
+ synchronized (mPresenter.getNotificationData()) {
+ ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+ .getNotificationData().getActiveNotifications();
+ final int N = activeNotifications.size();
+
+ // Promote the media notification with a controller in 'playing' state, if any.
+ NotificationData.Entry mediaNotification = null;
+ MediaController controller = null;
+ for (int i = 0; i < N; i++) {
+ final NotificationData.Entry entry = activeNotifications.get(i);
+
+ if (isMediaNotification(entry)) {
+ final MediaSession.Token token =
+ entry.notification.getNotification().extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION);
+ if (token != null) {
+ MediaController aController = new MediaController(mContext, token);
+ if (PlaybackState.STATE_PLAYING ==
+ getMediaControllerPlaybackState(aController)) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+ + entry.notification.getKey());
+ }
+ mediaNotification = entry;
+ controller = aController;
+ break;
+ }
+ }
+ }
+ }
+ if (mediaNotification == null) {
+ // Still nothing? OK, let's just look for live media sessions and see if they match
+ // one of our notifications. This will catch apps that aren't (yet!) using media
+ // notifications.
+
+ if (mMediaSessionManager != null) {
+ // TODO: Should this really be for all users?
+ final List<MediaController> sessions
+ = mMediaSessionManager.getActiveSessionsForUser(
+ null,
+ UserHandle.USER_ALL);
+
+ for (MediaController aController : sessions) {
+ if (PlaybackState.STATE_PLAYING ==
+ getMediaControllerPlaybackState(aController)) {
+ // now to see if we have one like this
+ final String pkg = aController.getPackageName();
+
+ for (int i = 0; i < N; i++) {
+ final NotificationData.Entry entry = activeNotifications.get(i);
+ if (entry.notification.getPackageName().equals(pkg)) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: found controller matching "
+ + entry.notification.getKey());
+ }
+ controller = aController;
+ mediaNotification = entry;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (controller != null && !sameSessions(mMediaController, controller)) {
+ // We have a new media session
+ clearCurrentMediaNotification();
+ mMediaController = controller;
+ mMediaController.registerCallback(mMediaListener);
+ mMediaMetadata = mMediaController.getMetadata();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
+ + mMediaMetadata);
+ }
+
+ if (mediaNotification != null) {
+ mMediaNotificationKey = mediaNotification.notification.getKey();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+ + mMediaNotificationKey + " controller=" + mMediaController);
+ }
+ }
+ metaDataChanged = true;
+ }
+ }
+
+ if (metaDataChanged) {
+ mPresenter.updateNotifications();
+ }
+ mPresenter.updateMediaMetaData(metaDataChanged, true);
+ }
+
+ public void clearCurrentMediaNotification() {
+ mMediaNotificationKey = null;
+ mMediaMetadata = null;
+ if (mMediaController != null) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ + mMediaController.getPackageName());
+ }
+ mMediaController.unregisterCallback(mMediaListener);
+ }
+ mMediaController = null;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print(" mMediaSessionManager=");
+ pw.println(mMediaSessionManager);
+ pw.print(" mMediaNotificationKey=");
+ pw.println(mMediaNotificationKey);
+ pw.print(" mMediaController=");
+ pw.print(mMediaController);
+ if (mMediaController != null) {
+ pw.print(" state=" + mMediaController.getPlaybackState());
+ }
+ pw.println();
+ pw.print(" mMediaMetadata=");
+ pw.print(mMediaMetadata);
+ if (mMediaMetadata != null) {
+ pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
+ }
+ pw.println();
+ }
+
+ private boolean isPlaybackActive(int state) {
+ return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+ && state != PlaybackState.STATE_NONE;
+ }
+
+ private boolean sameSessions(MediaController a, MediaController b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null) {
+ return false;
+ }
+ return a.controlsSameSession(b);
+ }
+
+ private int getMediaControllerPlaybackState(MediaController controller) {
+ if (controller != null) {
+ final PlaybackState playbackState = controller.getPlaybackState();
+ if (playbackState != null) {
+ return playbackState.getState();
+ }
+ }
+ return PlaybackState.STATE_NONE;
+ }
+
+ private boolean isMediaNotification(NotificationData.Entry entry) {
+ // TODO: confirm that there's a valid media key
+ return entry.getExpandedContentView() != null &&
+ entry.getExpandedContentView()
+ .findViewById(com.android.internal.R.id.media_actions) != null;
+ }
+}
diff --git a/com/android/systemui/statusbar/NotificationPresenter.java b/com/android/systemui/statusbar/NotificationPresenter.java
new file mode 100644
index 00000000..1aca60c9
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationPresenter.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 com.android.systemui.statusbar;
+
+import android.content.Intent;
+import android.service.notification.NotificationListenerService;
+
+/**
+ * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
+ * for both querying the state of the system (some modularised piece of functionality may
+ * want to act differently based on e.g. whether the presenter is visible to the user or not) and
+ * for affecting the state of the system (e.g. starting an intent, given that the presenter may
+ * want to perform some action before doing so).
+ */
+public interface NotificationPresenter {
+
+ /**
+ * Returns true if the presenter is not visible. For example, it may not be necessary to do
+ * animations if this returns true.
+ */
+ boolean isPresenterFullyCollapsed();
+
+ /**
+ * Returns true if the presenter is locked. For example, if the keyguard is active.
+ */
+ boolean isPresenterLocked();
+
+ /**
+ * Returns the current user id. This can change if the user is switched.
+ */
+ int getCurrentUserId();
+
+ /**
+ * Runs the given intent. The presenter may want to run some animations or close itself when
+ * this happens.
+ */
+ void startNotificationGutsIntent(Intent intent, int appUid);
+
+ /**
+ * Returns NotificationData.
+ */
+ NotificationData getNotificationData();
+
+ // TODO: Create NotificationEntryManager and move this method to there.
+ /**
+ * Signals that some notifications have changed, and NotificationPresenter should update itself.
+ */
+ void updateNotifications();
+
+ /**
+ * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+ */
+ void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
+
+ // TODO: Create NotificationUpdateHandler and move this method to there.
+ /**
+ * Removes a notification.
+ */
+ void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+ // TODO: Create NotificationEntryManager and move this method to there.
+ /**
+ * Gets the latest ranking map.
+ */
+ NotificationListenerService.RankingMap getLatestRankingMap();
+}
diff --git a/com/android/systemui/statusbar/OperatorNameView.java b/com/android/systemui/statusbar/OperatorNameView.java
new file mode 100644
index 00000000..5090f74d
--- /dev/null
+++ b/com/android/systemui/statusbar/OperatorNameView.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 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;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.List;
+
+public class OperatorNameView extends TextView implements DemoMode, DarkReceiver,
+ SignalCallback, Tunable {
+
+ private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private boolean mDemoMode;
+
+ private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ updateText();
+ }
+ };
+
+ public OperatorNameView(Context context) {
+ this(context, null);
+ }
+
+ public OperatorNameView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public OperatorNameView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardUpdateMonitor.registerCallback(mCallback);
+ Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
+ Dependency.get(NetworkController.class).addCallback(this);
+ Dependency.get(TunerService.class).addTunable(this, KEY_SHOW_OPERATOR_NAME);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mKeyguardUpdateMonitor.removeCallback(mCallback);
+ Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this);
+ Dependency.get(NetworkController.class).removeCallback(this);
+ Dependency.get(TunerService.class).removeTunable(this);
+ }
+
+ @Override
+ public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+ setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+ }
+
+ @Override
+ public void setIsAirplaneMode(IconState icon) {
+ update();
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ update();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+ mDemoMode = true;
+ } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+ mDemoMode = false;
+ update();
+ } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) {
+ setText(args.getString("name"));
+ }
+ }
+
+ private void update() {
+ boolean showOperatorName = Dependency.get(TunerService.class)
+ .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
+ setVisibility(showOperatorName ? VISIBLE : GONE);
+
+ boolean hasMobile = ConnectivityManager.from(mContext)
+ .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
+ if (!hasMobile || airplaneMode) {
+ setText(null);
+ setVisibility(GONE);
+ return;
+ }
+
+ if (!mDemoMode) {
+ updateText();
+ }
+ }
+
+ private void updateText() {
+ CharSequence displayText = null;
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ final int N = subs.size();
+ for (int i = 0; i < N; i++) {
+ int subId = subs.get(i).getSubscriptionId();
+ State simState = mKeyguardUpdateMonitor.getSimState(subId);
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ if (!TextUtils.isEmpty(carrierName) && simState == State.READY) {
+ ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId);
+ if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) {
+ displayText = carrierName;
+ break;
+ }
+ }
+ }
+
+ setText(displayText);
+ }
+}
diff --git a/com/android/systemui/statusbar/ViewTransformationHelper.java b/com/android/systemui/statusbar/ViewTransformationHelper.java
index 53530057..09b11c29 100644
--- a/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -35,7 +35,8 @@ import java.util.Stack;
/**
* A view that can be transformed to and from.
*/
-public class ViewTransformationHelper implements TransformableView {
+public class ViewTransformationHelper implements TransformableView,
+ TransformState.TransformInfo {
private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
@@ -59,7 +60,7 @@ public class ViewTransformationHelper implements TransformableView {
public TransformState getCurrentState(int fadingView) {
View view = mTransformedViews.get(fadingView);
if (view != null && view.getVisibility() != View.GONE) {
- return TransformState.createFrom(view);
+ return TransformState.createFrom(view, this);
}
return null;
}
@@ -88,6 +89,7 @@ public class ViewTransformationHelper implements TransformableView {
endRunnable.run();
}
setVisible(false);
+ mViewTransformationAnimation = null;
} else {
abortTransformations();
}
@@ -245,7 +247,7 @@ public class ViewTransformationHelper implements TransformableView {
}
public void resetTransformedView(View view) {
- TransformState state = TransformState.createFrom(view);
+ TransformState state = TransformState.createFrom(view, this);
state.setVisible(true /* visible */, true /* force */);
state.recycle();
}
@@ -257,6 +259,11 @@ public class ViewTransformationHelper implements TransformableView {
return new ArraySet<>(mTransformedViews.values());
}
+ @Override
+ public boolean isAnimating() {
+ return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
+ }
+
public static abstract class CustomTransformation {
/**
* Transform a state to the given view
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index fed2ebe9..5941af24 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -45,8 +45,9 @@ import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
@@ -84,7 +85,7 @@ public class CarStatusBar extends StatusBar implements
public void start() {
super.start();
mTaskStackListener = new TaskStackListenerImpl();
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
registerPackageChangeReceivers();
mStackScroller.setScrollingEnabled(true);
@@ -305,14 +306,14 @@ public class CarStatusBar extends StatusBar implements
}
/**
- * An implementation of TaskStackChangeListener, that listens for changes in the system task
+ * An implementation of SysUiTaskStackChangeListener, that listens for changes in the system task
* stack and notifies the navigation bar.
*/
- private class TaskStackListenerImpl extends TaskStackChangeListener {
+ private class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
@Override
public void onTaskStackChanged() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTaskInfo =
+ ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
runningTaskInfo);
diff --git a/com/android/systemui/statusbar/notification/ImageTransformState.java b/com/android/systemui/statusbar/notification/ImageTransformState.java
index 92f26d65..8227b779 100644
--- a/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -39,8 +39,8 @@ public class ImageTransformState extends TransformState {
private Icon mIcon;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof ImageView) {
mIcon = (Icon) view.getTag(ICON_TAG);
}
diff --git a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
new file mode 100644
index 00000000..fc420eb7
--- /dev/null
+++ b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.res.Resources;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingLayout;
+import com.android.internal.widget.MessagingLinearLayout;
+import com.android.internal.widget.MessagingMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A transform state of the action list
+*/
+public class MessagingLayoutTransformState extends TransformState {
+
+ private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private MessagingLinearLayout mMessageContainer;
+ private MessagingLayout mMessagingLayout;
+ private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
+ private float mRelativeTranslationOffset;
+
+ public static MessagingLayoutTransformState obtain() {
+ MessagingLayoutTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new MessagingLayoutTransformState();
+ }
+
+ @Override
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
+ if (mTransformedView instanceof MessagingLinearLayout) {
+ mMessageContainer = (MessagingLinearLayout) mTransformedView;
+ mMessagingLayout = mMessageContainer.getMessagingLayout();
+ Resources resources = view.getContext().getResources();
+ mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8;
+ }
+ }
+
+ @Override
+ public boolean transformViewTo(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ true /* to */);
+ return true;
+ } else {
+ return super.transformViewTo(otherState, transformationAmount);
+ }
+ }
+
+ @Override
+ public void transformViewFrom(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ false /* to */);
+ } else {
+ super.transformViewFrom(otherState, transformationAmount);
+ }
+ }
+
+ private void transformViewInternal(MessagingLayoutTransformState mlt,
+ float transformationAmount, boolean to) {
+ ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
+ mMessagingLayout.getMessagingGroups());
+ ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
+ mlt.mMessagingLayout.getMessagingGroups());
+ HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups);
+ MessagingGroup lastPairedGroup = null;
+ float currentTranslation = 0;
+ float transformationDistanceRemaining = 0;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup matchingGroup = pairs.get(ownGroup);
+ if (!isGone(ownGroup)) {
+ if (matchingGroup != null) {
+ transformGroups(ownGroup, matchingGroup, transformationAmount, to);
+ if (lastPairedGroup == null) {
+ lastPairedGroup = ownGroup;
+ if (to){
+ float totalTranslation = ownGroup.getTop() - matchingGroup.getTop();
+ transformationDistanceRemaining
+ = matchingGroup.getAvatar().getTranslationY();
+ currentTranslation = transformationDistanceRemaining - totalTranslation;
+ } else {
+ float totalTranslation = matchingGroup.getTop() - ownGroup.getTop();
+ currentTranslation = ownGroup.getAvatar().getTranslationY();
+ transformationDistanceRemaining = currentTranslation - totalTranslation;
+ }
+ }
+ } else {
+ float groupTransformationAmount = transformationAmount;
+ if (lastPairedGroup != null) {
+ adaptGroupAppear(ownGroup, transformationAmount, currentTranslation,
+ to);
+ int distance = lastPairedGroup.getTop() - ownGroup.getTop();
+ float transformationDistance = mTransformInfo.isAnimating()
+ ? distance
+ : ownGroup.getHeight() * 0.75f;
+ float translationProgress = transformationDistanceRemaining
+ - (distance - transformationDistance);
+ groupTransformationAmount =
+ translationProgress / transformationDistance;
+ groupTransformationAmount = Math.max(0.0f, Math.min(1.0f,
+ groupTransformationAmount));
+ if (to) {
+ groupTransformationAmount = 1.0f - groupTransformationAmount;
+ }
+ }
+ if (to) {
+ disappear(ownGroup, groupTransformationAmount);
+ } else {
+ appear(ownGroup, groupTransformationAmount);
+ }
+ }
+ }
+ }
+ }
+
+ private void appear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ appear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ appear(ownGroup.getAvatar(), transformationAmount);
+ appear(ownGroup.getSender(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSender(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount,
+ float overallTranslation, boolean to) {
+ float relativeOffset;
+ if (to) {
+ relativeOffset = transformationAmount * mRelativeTranslationOffset;
+ } else {
+ relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset;
+ }
+ if (ownGroup.getSender().getVisibility() != View.GONE) {
+ relativeOffset *= 0.5f;
+ }
+ ownGroup.getMessageContainer().setTranslationY(relativeOffset);
+ ownGroup.setTranslationY(overallTranslation * 0.85f);
+ }
+
+ private void disappear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ disappear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ disappear(ownGroup.getAvatar(), transformationAmount);
+ disappear(ownGroup.getSender(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSender(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void appear(View child, float transformationAmount) {
+ if (child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.appear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private void disappear(View child, float transformationAmount) {
+ if (child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.disappear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private ArrayList<MessagingGroup> filterHiddenGroups(
+ ArrayList<MessagingGroup> groups) {
+ ArrayList<MessagingGroup> result = new ArrayList<>(groups);
+ for (int i = 0; i < result.size(); i++) {
+ MessagingGroup messagingGroup = result.get(i);
+ if (isGone(messagingGroup)) {
+ result.remove(i);
+ i--;
+ }
+ }
+ return result;
+ }
+
+ private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
+ float transformationAmount, boolean to) {
+ transformView(transformationAmount, to, ownGroup.getSender(), otherGroup.getSender(),
+ true /* sameAsAny */);
+ transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
+ true /* sameAsAny */);
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+ float previousTranslation = 0;
+ for (int i = 0; i < ownMessages.getChildCount(); i++) {
+ View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+ if (isGone(child)) {
+ continue;
+ }
+ int otherIndex = otherMessages.getChildCount() - 1 - i;
+ View otherChild = null;
+ if (otherIndex >= 0) {
+ otherChild = otherMessages.getChildAt(otherIndex);
+ if (isGone(otherChild)) {
+ otherChild = null;
+ }
+ }
+ if (otherChild == null) {
+ float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
+ transformationAmount = distanceToTop / child.getHeight();
+ transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
+ if (to) {
+ transformationAmount = 1.0f - transformationAmount;
+ }
+ }
+ transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+ if (otherChild == null) {
+ child.setTranslationY(previousTranslation);
+ setClippingDeactivated(child, true);
+ } else if (to) {
+ float totalTranslation = child.getTop() + ownGroup.getTop()
+ - otherChild.getTop() - otherChild.getTop();
+ previousTranslation = otherChild.getTranslationY() - totalTranslation;
+ } else {
+ previousTranslation = child.getTranslationY();
+ }
+ }
+ }
+
+ private void transformView(float transformationAmount, boolean to, View ownView,
+ View otherView, boolean sameAsAny) {
+ TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
+ if (!mTransformInfo.isAnimating()) {
+ ownState.setDefaultInterpolator(Interpolators.LINEAR);
+ }
+ ownState.setIsSameAsAnyView(sameAsAny);
+ if (to) {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewTo(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.disappear(transformationAmount, null);
+ }
+ } else {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewFrom(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.appear(transformationAmount, null);
+ }
+ }
+ ownState.recycle();
+ }
+
+ private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups,
+ ArrayList<MessagingGroup> otherGroups) {
+ mGroupMap.clear();
+ int lastMatch = Integer.MAX_VALUE;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup bestMatch = null;
+ int bestCompatibility = 0;
+ for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) {
+ MessagingGroup otherGroup = otherGroups.get(j);
+ int compatibility = ownGroup.calculateGroupCompatibility(otherGroup);
+ if (compatibility > bestCompatibility) {
+ bestCompatibility = compatibility;
+ bestMatch = otherGroup;
+ lastMatch = j;
+ }
+ }
+ if (bestMatch != null) {
+ mGroupMap.put(ownGroup, bestMatch);
+ }
+ }
+ return mGroupMap;
+ }
+
+ private boolean isGone(View view) {
+ if (view.getVisibility() == View.GONE) {
+ return true;
+ }
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setVisible(boolean visible, boolean force) {
+ resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+ setVisible(child, visible, force);
+ }
+ setVisible(ownGroup.getAvatar(), visible, force);
+ setVisible(ownGroup.getSender(), visible, force);
+ }
+ }
+ }
+
+ private void setVisible(View child, boolean visible, boolean force) {
+ if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.setVisible(visible, force);
+ ownState.recycle();
+ }
+
+ @Override
+ protected void resetTransformedView() {
+ super.resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ resetTransformedView(child);
+ setClippingDeactivated(child, false);
+ }
+ resetTransformedView(ownGroup.getAvatar());
+ resetTransformedView(ownGroup.getSender());
+ setClippingDeactivated(ownGroup.getAvatar(), false);
+ setClippingDeactivated(ownGroup.getSender(), false);
+ ownGroup.setTranslationY(0);
+ ownGroup.getMessageContainer().setTranslationY(0);
+ }
+ }
+ }
+
+ @Override
+ public void prepareFadeIn() {
+ super.prepareFadeIn();
+ setVisible(true /* visible */, false /* force */);
+ }
+
+ private void resetTransformedView(View child) {
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.resetTransformedView();
+ ownState.recycle();
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mMessageContainer = null;
+ mMessagingLayout = null;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ mGroupMap.clear();;
+ sInstancePool.release(this);
+ }
+}
diff --git a/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1cac..fb5644f7 100644
--- a/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification;
+import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
+import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -32,41 +34,20 @@ import java.util.ArrayList;
*/
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
- private View mContractedMessage;
- private ArrayList<View> mHistoricMessages = new ArrayList<View>();
+ private final int mMinHeightWithActions;
+ private MessagingLayout mMessagingLayout;
+ private MessagingLinearLayout mMessagingLinearLayout;
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
+ mMessagingLayout = (MessagingLayout) view;
+ mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
+ R.dimen.notification_messaging_actions_min_height);
}
private void resolveViews() {
- mContractedMessage = null;
-
- View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
- if (container instanceof MessagingLinearLayout
- && ((MessagingLinearLayout) container).getChildCount() > 0) {
- MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
- int childCount = messagingContainer.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = messagingContainer.getChildAt(i);
-
- if (child.getVisibility() == View.GONE
- && child instanceof TextView
- && !TextUtils.isEmpty(((TextView) child).getText())) {
- mHistoricMessages.add(child);
- }
-
- // Only consider the first visible child - transforming to a position other than the
- // first looks bad because we have to move across other messages that are fading in.
- if (child.getId() == messagingContainer.getContractedChildId()) {
- mContractedMessage = child;
- } else if (child.getVisibility() == View.VISIBLE) {
- break;
- }
- }
- }
+ mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
}
@Override
@@ -81,16 +62,22 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla
protected void updateTransformedTypes() {
// This also clears the existing types
super.updateTransformedTypes();
- if (mContractedMessage != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
- mContractedMessage);
+ if (mMessagingLinearLayout != null) {
+ mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
+ mMessagingLinearLayout);
}
}
@Override
public void setRemoteInputVisible(boolean visible) {
- for (int i = 0; i < mHistoricMessages.size(); i++) {
- mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+ mMessagingLayout.showHistoricMessages(visible);
+ }
+
+ @Override
+ public int getMinLayoutHeight() {
+ if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) {
+ return mMinHeightWithActions;
}
+ return super.getMinLayoutHeight();
}
}
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index bb979ebd..fd085d9c 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -41,7 +41,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
private ProgressBar mProgressBar;
private TextView mTitle;
private TextView mText;
- private View mActionsContainer;
+ protected View mActionsContainer;
private View mReplyAction;
private Rect mTmpRect = new Rect();
diff --git a/com/android/systemui/statusbar/notification/NotificationUtils.java b/com/android/systemui/statusbar/notification/NotificationUtils.java
index 3115361c..af393c91 100644
--- a/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -66,4 +66,14 @@ public class NotificationUtils {
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
}
+ /**
+ * @param dimenId the dimen to look up
+ * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
+ */
+ public static int getFontScaledHeight(Context context, int dimenId) {
+ int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId);
+ float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity /
+ context.getResources().getDisplayMetrics().density);
+ return (int) (dimensionPixelSize * factor);
+ }
}
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5200d696..1cd5f15b 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,4 +190,8 @@ public abstract class NotificationViewWrapper implements TransformableView {
public boolean disallowSingleClick(float x, float y) {
return false;
}
+
+ public int getMinLayoutHeight() {
+ return 0;
+ }
}
diff --git a/com/android/systemui/statusbar/notification/TextViewTransformState.java b/com/android/systemui/statusbar/notification/TextViewTransformState.java
index c4aabe48..178c8130 100644
--- a/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -33,8 +33,8 @@ public class TextViewTransformState extends TransformState {
private TextView mText;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof TextView) {
mText = (TextView) view;
}
diff --git a/com/android/systemui/statusbar/notification/TransformState.java b/com/android/systemui/statusbar/notification/TransformState.java
index bafedff4..ad07af0a 100644
--- a/com/android/systemui/statusbar/notification/TransformState.java
+++ b/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,8 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,23 +45,46 @@ public class TransformState {
public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
private static final float UNDEFINED = -1f;
- private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
- private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
- private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+ private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
+ = new ViewClippingUtil.ClippingParameters() {
+ @Override
+ public boolean shouldFinish(View view) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ return !row.isChildInGroup();
+ }
+ return false;
+ }
+
+ @Override
+ public void onClippingStateChanged(View view, boolean isClipping) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isClipping) {
+ row.setClipToActualHeight(true);
+ } else if (row.isChildInGroup()) {
+ row.setClipToActualHeight(false);
+ }
+ }
+ }
+ };
protected View mTransformedView;
+ protected TransformInfo mTransformInfo;
private int[] mOwnPosition = new int[2];
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
+ private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
- public void initFrom(View view) {
+ public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
+ mTransformInfo = transformInfo;
}
/**
@@ -108,13 +133,16 @@ public class TransformState {
final View transformedView = mTransformedView;
boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
- boolean transformScale = transformScale(otherState);
+ boolean differentHeight = otherState.getViewHeight() != getViewHeight();
+ boolean differentWidth = otherState.getViewWidth() != getViewWidth();
+ boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
// lets animate the positions correctly
if (transformationAmount == 0.0f
|| transformX && getTransformationStartX() == UNDEFINED
|| transformY && getTransformationStartY() == UNDEFINED
- || transformScale && getTransformationStartScaleX() == UNDEFINED
- || transformScale && getTransformationStartScaleY() == UNDEFINED) {
+ || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
+ || transformScale && getTransformationStartScaleY() == UNDEFINED
+ && differentHeight) {
int[] otherPosition;
if (transformationAmount != 0.0f) {
otherPosition = otherState.getLaidOutLocationOnScreen();
@@ -132,14 +160,14 @@ public class TransformState {
}
// we also want to animate the scale if we're the same
View otherView = otherState.getTransformedView();
- if (transformScale && otherState.getViewWidth() != getViewWidth()) {
+ if (transformScale && differentWidth) {
setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
/ (float) getViewWidth());
transformedView.setPivotX(0);
} else {
setTransformationStartScaleX(UNDEFINED);
}
- if (transformScale && otherState.getViewHeight() != getViewHeight()) {
+ if (transformScale && differentHeight) {
setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
/ (float) getViewHeight());
transformedView.setPivotY(0);
@@ -159,7 +187,7 @@ public class TransformState {
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
if (transformX) {
float interpolation = interpolatedValue;
@@ -297,7 +325,7 @@ public class TransformState {
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = getLaidOutLocationOnScreen();
@@ -354,59 +382,8 @@ public class TransformState {
}
}
- public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
- if (!(transformedView.getParent() instanceof ViewGroup)) {
- return;
- }
- ViewGroup view = (ViewGroup) transformedView.getParent();
- while (true) {
- ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
- if (clipSet == null) {
- clipSet = new ArraySet<>();
- view.setTag(CLIP_CLIPPING_SET, clipSet);
- }
- Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
- if (clipChildren == null) {
- clipChildren = view.getClipChildren();
- view.setTag(CLIP_CHILDREN_TAG, clipChildren);
- }
- Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
- if (clipToPadding == null) {
- clipToPadding = view.getClipToPadding();
- view.setTag(CLIP_TO_PADDING, clipToPadding);
- }
- ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
- ? (ExpandableNotificationRow) view
- : null;
- if (!deactivated) {
- clipSet.remove(transformedView);
- if (clipSet.isEmpty()) {
- view.setClipChildren(clipChildren);
- view.setClipToPadding(clipToPadding);
- view.setTag(CLIP_CLIPPING_SET, null);
- if (row != null) {
- row.setClipToActualHeight(true);
- }
- }
- } else {
- clipSet.add(transformedView);
- view.setClipChildren(false);
- view.setClipToPadding(false);
- if (row != null && row.isChildInGroup()) {
- // We still want to clip to the parent's height
- row.setClipToActualHeight(false);
- }
- }
- if (row != null && !row.isChildInGroup()) {
- return;
- }
- final ViewParent parent = view.getParent();
- if (parent instanceof ViewGroup) {
- view = (ViewGroup) parent;
- } else {
- return;
- }
- }
+ protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
}
public int[] getLaidOutLocationOnScreen() {
@@ -423,6 +400,9 @@ public class TransformState {
// remove scale
mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
+
+ // Remove local translations
+ mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
return mOwnPosition;
}
@@ -444,20 +424,26 @@ public class TransformState {
CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
}
- public static TransformState createFrom(View view) {
+ public static TransformState createFrom(View view,
+ TransformInfo transformInfo) {
if (view instanceof TextView) {
TextViewTransformState result = TextViewTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
if (view.getId() == com.android.internal.R.id.actions_container) {
ActionListTransformState result = ActionListTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
+ return result;
+ }
+ if (view.getId() == com.android.internal.R.id.notification_messaging) {
+ MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
+ result.initFrom(view, transformInfo);
return result;
}
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
if (view.getId() == com.android.internal.R.id.reply_icon_action) {
((TransformState) result).setIsSameAsAnyView(true);
}
@@ -465,15 +451,15 @@ public class TransformState {
}
if (view instanceof ProgressBar) {
ProgressTransformState result = ProgressTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
TransformState result = obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
- private void setIsSameAsAnyView(boolean sameAsAny) {
+ public void setIsSameAsAnyView(boolean sameAsAny) {
mSameAsAny = sameAsAny;
}
@@ -533,6 +519,7 @@ public class TransformState {
mSameAsAny = false;
mTransformationEndX = UNDEFINED;
mTransformationEndY = UNDEFINED;
+ mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
}
public void setVisible(boolean visible, boolean force) {
@@ -578,4 +565,12 @@ public class TransformState {
public View getTransformedView() {
return mTransformedView;
}
+
+ public void setDefaultInterpolator(Interpolator interpolator) {
+ mDefaultInterpolator = interpolator;
+ }
+
+ public interface TransformInfo {
+ boolean isAnimating();
+ }
}
diff --git a/com/android/systemui/statusbar/phone/AutoTileManager.java b/com/android/systemui/statusbar/phone/AutoTileManager.java
index 1bd90fa9..149ec0b3 100644
--- a/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,7 +20,7 @@ import android.os.Looper;
import android.provider.Settings.Secure;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.Prefs.Key;
@@ -78,8 +78,8 @@ public class AutoTileManager {
}
if (!mAutoTracker.isAdded(NIGHT)
- && NightDisplayController.isAvailable(mContext)) {
- Dependency.get(NightDisplayController.class).setListener(mNightDisplayCallback);
+ && ColorDisplayController.isAvailable(mContext)) {
+ Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
}
}
@@ -89,7 +89,7 @@ public class AutoTileManager {
Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
- Dependency.get(NightDisplayController.class).setListener(null);
+ Dependency.get(ColorDisplayController.class).setListener(null);
}
private final ManagedProfileController.Callback mProfileCallback =
@@ -139,8 +139,8 @@ public class AutoTileManager {
};
@VisibleForTesting
- final NightDisplayController.Callback mNightDisplayCallback =
- new NightDisplayController.Callback() {
+ final ColorDisplayController.Callback mColorDisplayCallback =
+ new ColorDisplayController.Callback() {
@Override
public void onActivated(boolean activated) {
if (activated) {
@@ -150,8 +150,8 @@ public class AutoTileManager {
@Override
public void onAutoModeChanged(int autoMode) {
- if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM
- || autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+ if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM
+ || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
addNightTile();
}
}
@@ -160,7 +160,7 @@ public class AutoTileManager {
if (mAutoTracker.isAdded(NIGHT)) return;
mHost.addTile(NIGHT);
mAutoTracker.setTileAdded(NIGHT);
- mHandler.post(() -> Dependency.get(NightDisplayController.class)
+ mHandler.post(() -> Dependency.get(ColorDisplayController.class)
.setListener(null));
}
};
diff --git a/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 2c3f452e..61f3130b 100644
--- a/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -60,6 +60,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private StatusBar mStatusBarComponent;
private DarkIconManager mDarkIconManager;
private SignalClusterView mSignalClusterView;
+ private View mOperatorNameFrame;
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
@@ -97,6 +98,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
// Default to showing until we know otherwise.
showSystemIconArea(false);
initEmergencyCryptkeeperText();
+ initOperatorName();
}
@Override
@@ -150,8 +152,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
if ((diff1 & DISABLE_SYSTEM_INFO) != 0) {
if ((state1 & DISABLE_SYSTEM_INFO) != 0) {
hideSystemIconArea(animate);
+ hideOperatorName(animate);
} else {
showSystemIconArea(animate);
+ showOperatorName(animate);
}
}
if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) {
@@ -207,6 +211,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
animateShow(mNotificationIconAreaInner, animate);
}
+ public void hideOperatorName(boolean animate) {
+ if (mOperatorNameFrame != null) {
+ animateHide(mOperatorNameFrame, animate);
+ }
+ }
+
+ public void showOperatorName(boolean animate) {
+ if (mOperatorNameFrame != null) {
+ animateShow(mOperatorNameFrame, animate);
+ }
+ }
+
/**
* Hides a view.
*/
@@ -268,4 +284,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
parent.removeView(emergencyViewStub);
}
}
+
+ private void initOperatorName() {
+ if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
+ ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
+ mOperatorNameFrame = stub.inflate();
+ }
+ }
}
diff --git a/com/android/systemui/statusbar/phone/LightBarController.java b/com/android/systemui/statusbar/phone/LightBarController.java
index d226fede..1c361caa 100644
--- a/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/com/android/systemui/statusbar/phone/LightBarController.java
@@ -175,9 +175,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private boolean isLight(int vis, int barMode, int flag) {
boolean isTransparentBar = (barMode == MODE_TRANSPARENT
|| barMode == MODE_LIGHTS_OUT_TRANSPARENT);
- boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
boolean light = (vis & flag) != 0;
- return allowLight && light;
+ return isTransparentBar && light;
}
private boolean animateChange() {
diff --git a/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index ee9a7915..bed6d821 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -19,20 +19,24 @@ package com.android.systemui.statusbar.phone;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.tuner.TunerService;
@@ -46,6 +50,7 @@ import static android.view.WindowManager.DOCKED_TOP;
public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener
implements TunerService.Tunable, GestureHelper {
+ private static final String TAG = "NavBarGestureHelper";
private static final String KEY_DOCK_WINDOW_GESTURE = "overview_nav_bar_gesture";
/**
* When dragging from the navigation bar, we drag in recents.
@@ -72,10 +77,13 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
private final GestureDetector mTaskSwitcherDetector;
private final int mScrollTouchSlop;
private final int mMinFlingVelocity;
+ private final Matrix mTransformGlobalMatrix = new Matrix();
+ private final Matrix mTransformLocalMatrix = new Matrix();
private int mTouchDownX;
private int mTouchDownY;
private boolean mDownOnRecents;
private VelocityTracker mVelocityTracker;
+ private OverviewProxyService mOverviewEventSender = Dependency.get(OverviewProxyService.class);
private boolean mDockWindowEnabled;
private boolean mDockWindowTouchSlopExceeded;
@@ -107,15 +115,34 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
mIsRTL = isRTL;
}
+ private boolean proxyMotionEvents(MotionEvent event) {
+ final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+ if (overviewProxy != null) {
+ mNavigationBarView.requestUnbufferedDispatch(event);
+ event.transform(mTransformGlobalMatrix);
+ try {
+ overviewProxy.onMotionEvent(event);
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ } finally {
+ event.transform(mTransformLocalMatrix);
+ }
+ }
+ return false;
+ }
+
public boolean onInterceptTouchEvent(MotionEvent event) {
- // If we move more than a fixed amount, then start capturing for the
- // task switcher detector
- mTaskSwitcherDetector.onTouchEvent(event);
int action = event.getAction();
+ boolean result = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mTouchDownX = (int) event.getX();
mTouchDownY = (int) event.getY();
+ mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX);
+ mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
+ mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
+ mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
break;
}
case MotionEvent.ACTION_MOVE: {
@@ -123,11 +150,10 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
int y = (int) event.getY();
int xDiff = Math.abs(x - mTouchDownX);
int yDiff = Math.abs(y - mTouchDownY);
- boolean exceededTouchSlop = !mIsVertical
- ? xDiff > mScrollTouchSlop && xDiff > yDiff
- : yDiff > mScrollTouchSlop && yDiff > xDiff;
+ boolean exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff
+ || yDiff > mScrollTouchSlop && yDiff > xDiff;
if (exceededTouchSlop) {
- return true;
+ result = true;
}
break;
}
@@ -135,7 +161,12 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
case MotionEvent.ACTION_UP:
break;
}
- return mDockWindowEnabled && interceptDockWindowEvent(event);
+ if (!proxyMotionEvents(event)) {
+ // If we move more than a fixed amount, then start capturing for the
+ // task switcher detector, disabled when proxying motion events to launcher service
+ mTaskSwitcherDetector.onTouchEvent(event);
+ }
+ return result || (mDockWindowEnabled && interceptDockWindowEvent(event));
}
private boolean interceptDockWindowEvent(MotionEvent event) {
@@ -206,7 +237,7 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
&& mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
Rect initialBounds = null;
int dragMode = calculateDragMode();
- int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ int createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
if (dragMode == DRAG_MODE_DIVIDER) {
initialBounds = new Rect();
mDivider.getView().calculateBoundsForPosition(mIsVertical
@@ -218,10 +249,10 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
initialBounds);
} else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX
< mContext.getResources().getDisplayMetrics().widthPixels / 2) {
- createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+ createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
}
- boolean docked = mRecentsComponent.dockTopTask(dragMode, createMode, initialBounds,
- MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
+ boolean docked = mRecentsComponent.splitPrimaryTask(dragMode, createMode,
+ initialBounds, MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
if (docked) {
mDragMode = dragMode;
if (mDragMode == DRAG_MODE_DIVIDER) {
@@ -275,7 +306,7 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
}
public boolean onTouchEvent(MotionEvent event) {
- boolean result = mTaskSwitcherDetector.onTouchEvent(event);
+ boolean result = proxyMotionEvents(event) || mTaskSwitcherDetector.onTouchEvent(event);
if (mDockWindowEnabled) {
result |= handleDockWindowEvent(event);
}
diff --git a/com/android/systemui/statusbar/phone/NavigationBarView.java b/com/android/systemui/statusbar/phone/NavigationBarView.java
index 094129c5..4e7f205f 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -47,6 +47,8 @@ import android.widget.FrameLayout;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.DockedStackExistsListener;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.plugins.PluginListener;
@@ -196,6 +198,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
}
+ private final OverviewProxyListener mOverviewProxyListener =
+ isConnected -> setSlippery(!isConnected);
+
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -531,6 +536,24 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
}
+ private void setSlippery(boolean slippery) {
+ boolean changed = false;
+ final ViewGroup navbarView = ((ViewGroup) getParent());
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView
+ .getLayoutParams();
+ if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
+ changed = true;
+ } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) {
+ lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
+ changed = true;
+ }
+ if (changed) {
+ WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+ wm.updateViewLayout(navbarView, lp);
+ }
+ }
+
public void setMenuVisibility(final boolean show) {
setMenuVisibility(show, false);
}
@@ -756,6 +779,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
onPluginDisconnected(null); // Create default gesture helper
Dependency.get(PluginManager.class).addPluginListener(this,
NavGesture.class, false /* Only one */);
+ Dependency.get(OverviewProxyService.class).addCallback(mOverviewProxyListener);
}
@Override
@@ -765,6 +789,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
if (mGestureHelper != null) {
mGestureHelper.destroy();
}
+ Dependency.get(OverviewProxyService.class).removeCallback(mOverviewProxyListener);
}
@Override
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index af034406..86a8f411 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -596,7 +596,7 @@ public class NotificationPanelView extends PanelView implements
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
}
closeQs();
- mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
true /* cancelAnimators */);
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index b876286b..09fe579b 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -65,8 +65,9 @@ import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -249,7 +250,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mLocationController.addCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
// Clear out all old notifications on startup (only present in the case where sysui dies)
NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
@@ -768,7 +769,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
}
- private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
+ private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
// Listen for changes to stacks and then check which instant apps are foreground.
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 9f039543..67756159 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -16,14 +16,16 @@
package com.android.systemui.statusbar.phone;
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
-
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -39,10 +41,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -79,10 +79,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
@@ -114,14 +111,12 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
-import android.view.HapticFeedbackConstants;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
-import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
@@ -145,6 +140,8 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -157,6 +154,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.Interpolators;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
@@ -177,7 +175,6 @@ import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
@@ -203,10 +200,11 @@ import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
@@ -239,7 +237,6 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
.OnChildLocationsChangedListener;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
@@ -251,10 +248,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.Stack;
public class StatusBar extends SystemUI implements DemoMode,
@@ -263,7 +258,7 @@ public class StatusBar extends SystemUI implements DemoMode,
ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
- ColorExtractor.OnColorsChangedListener, ConfigurationListener {
+ ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
public static final boolean MULTIUSER_DEBUG = false;
public static final boolean ENABLE_REMOTE_INPUT =
@@ -271,7 +266,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
public static final boolean FORCE_REMOTE_INPUT_HISTORY =
- SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+ SystemProperties.getBoolean("debug.force_remoteinput_history", true);
private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -283,9 +278,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected static final boolean ENABLE_HEADS_UP = true;
protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
- // Must match constant in Settings. Used to highlight preferences when linking to Settings.
- private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
-
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
// Should match the values in PhoneWindowManager
@@ -304,7 +296,6 @@ public class StatusBar extends SystemUI implements DemoMode,
public static final boolean SPEW = false;
public static final boolean DUMPTRUCK = true; // extra dumpsys info
public static final boolean DEBUG_GESTURES = false;
- public static final boolean DEBUG_MEDIA = false;
public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
public static final boolean DEBUG_CAMERA_LIFT = false;
@@ -454,6 +445,8 @@ public class StatusBar extends SystemUI implements DemoMode,
private final int[] mAbsPos = new int[2];
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ private NotificationGutsManager mGutsManager;
+
// for disabling the status bar
private int mDisabled1 = 0;
private int mDisabled2 = 0;
@@ -549,31 +542,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected final PorterDuffXfermode mSrcOverXferMode =
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
- private MediaSessionManager mMediaSessionManager;
- private MediaController mMediaController;
- private String mMediaNotificationKey;
- private MediaMetadata mMediaMetadata;
- private final MediaController.Callback mMediaListener = new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- super.onPlaybackStateChanged(state);
- if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
- if (state != null) {
- if (!isPlaybackActive(state.getState())) {
- clearCurrentMediaNotification();
- updateMediaMetaData(true, true);
- }
- }
- }
-
- @Override
- public void onMetadataChanged(MediaMetadata metadata) {
- super.onMetadataChanged(metadata);
- if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
- mMediaMetadata = metadata;
- updateMediaMetaData(true, true);
- }
- };
+ private NotificationMediaManager mMediaManager;
/** Keys of notifications currently visible to the user. */
private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
@@ -696,7 +665,6 @@ public class StatusBar extends SystemUI implements DemoMode,
private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private boolean mClearAllEnabled;
@Nullable private View mAmbientIndicationContainer;
- private String mKeyToRemoveOnGutsClosed;
private SysuiColorExtractor mColorExtractor;
private ForegroundServiceController mForegroundServiceController;
private ScreenLifecycle mScreenLifecycle;
@@ -813,6 +781,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mLockPatternUtils = new LockPatternUtils(mContext);
+ mMediaManager = new NotificationMediaManager(this, mContext);
+
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
mCommandQueue.addCallbacks(this);
@@ -874,6 +844,7 @@ public class StatusBar extends SystemUI implements DemoMode,
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_PRESENT);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiver(mBaseBroadcastReceiver, filter);
IntentFilter internalFilter = new IntentFilter();
@@ -898,16 +869,8 @@ public class StatusBar extends SystemUI implements DemoMode,
Slog.e(TAG, "Failed to register VR mode state listener: " + e);
}
- mNonBlockablePkgs = new HashSet<>();
- Collections.addAll(mNonBlockablePkgs, res.getStringArray(
- com.android.internal.R.array.config_nonBlockableNotificationPackages));
// end old BaseStatusBar.start().
- mMediaSessionManager
- = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
- // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
- // in session state
-
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
mSettingsObserver.onChange(false); // set up
@@ -955,6 +918,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mGutsManager = new NotificationGutsManager(this, mStackScroller,
+ mCheckSaveListener, mContext);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1229,6 +1194,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public void onDensityOrFontScaleChanged() {
+ MessagingMessage.dropCache();
+ MessagingGroup.dropCache();
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
updateNotificationsOnDensityOrFontScaleChanged();
@@ -1295,12 +1262,12 @@ public class StatusBar extends SystemUI implements DemoMode,
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
for (int i = 0; i < activeNotifications.size(); i++) {
Entry entry = activeNotifications.get(i);
- boolean exposedGuts = mNotificationGutsExposed != null
- && entry.row.getGuts() == mNotificationGutsExposed;
+ boolean exposedGuts = mGutsManager.getExposedGuts() != null
+ && entry.row.getGuts() == mGutsManager.getExposedGuts();
entry.row.onDensityOrFontScaleChanged();
if (exposedGuts) {
- mNotificationGutsExposed = entry.row.getGuts();
- bindGuts(entry.row, mGutsMenuItem);
+ mGutsManager.setExposedGuts(entry.row.getGuts());
+ mGutsManager.bindGuts(entry.row);
}
}
}
@@ -1538,8 +1505,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
- return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
- ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
+ return mRecents.splitPrimaryTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
+ ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
Divider divider = getComponent(Divider.class);
if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) {
@@ -1665,6 +1632,7 @@ public class StatusBar extends SystemUI implements DemoMode,
updateNotifications();
}
+ @Override
public void removeNotification(String key, RankingMap ranking) {
boolean deferRemoval = false;
abortExistingInflation(key);
@@ -1678,12 +1646,11 @@ public class StatusBar extends SystemUI implements DemoMode,
|| !mVisualStabilityManager.isReorderingAllowed();
deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
}
- if (key.equals(mMediaNotificationKey)) {
- clearCurrentMediaNotification();
- updateMediaMetaData(true, true);
- }
- if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
- Entry entry = mNotificationData.get(key);
+ mMediaManager.onNotificationRemoved(key);
+
+ Entry entry = mNotificationData.get(key);
+ if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
+ && entry.row != null && !entry.row.isDismissed()) {
StatusBarNotification sbn = entry.notification;
Notification.Builder b = Notification.Builder
@@ -1719,6 +1686,7 @@ public class StatusBar extends SystemUI implements DemoMode,
deferRemoval = false;
}
if (updated) {
+ Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
mKeysKeptForRemoteInput.add(entry.key);
return;
}
@@ -1728,7 +1696,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
return;
}
- Entry entry = mNotificationData.get(key);
if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
&& (entry.row != null && !entry.row.isDismissed())) {
@@ -1736,12 +1703,12 @@ public class StatusBar extends SystemUI implements DemoMode,
mRemoteInputEntriesToRemoveOnCollapse.add(entry);
return;
}
- if (entry != null && mNotificationGutsExposed != null
- && mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null
- && !entry.row.getGuts().isLeavebehind()) {
+ if (entry != null && mGutsManager.getExposedGuts() != null
+ && mGutsManager.getExposedGuts() == entry.row.getGuts()
+ && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
Log.w(TAG, "Keeping notification because it's showing guts. " + key);
mLatestRankingMap = ranking;
- mKeyToRemoveOnGutsClosed = key;
+ mGutsManager.setKeyToRemoveOnGutsClosed(key);
return;
}
@@ -2134,7 +2101,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return entry.row.getParent() instanceof NotificationStackScrollLayout;
}
- protected void updateNotifications() {
+ @Override
+ public void updateNotifications() {
mNotificationData.filterAndSort();
updateNotificationShade();
@@ -2176,136 +2144,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- findAndUpdateMediaNotifications();
- }
-
- public void findAndUpdateMediaNotifications() {
- boolean metaDataChanged = false;
-
- synchronized (mNotificationData) {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- final int N = activeNotifications.size();
-
- // Promote the media notification with a controller in 'playing' state, if any.
- Entry mediaNotification = null;
- MediaController controller = null;
- for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
-
- if (isMediaNotification(entry)) {
- final MediaSession.Token token =
- entry.notification.getNotification().extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION);
- if (token != null) {
- MediaController aController = new MediaController(mContext, token);
- if (PlaybackState.STATE_PLAYING ==
- getMediaControllerPlaybackState(aController)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
- + entry.notification.getKey());
- }
- mediaNotification = entry;
- controller = aController;
- break;
- }
- }
- }
- }
- if (mediaNotification == null) {
- // Still nothing? OK, let's just look for live media sessions and see if they match
- // one of our notifications. This will catch apps that aren't (yet!) using media
- // notifications.
-
- if (mMediaSessionManager != null) {
- final List<MediaController> sessions
- = mMediaSessionManager.getActiveSessionsForUser(
- null,
- UserHandle.USER_ALL);
-
- for (MediaController aController : sessions) {
- if (PlaybackState.STATE_PLAYING ==
- getMediaControllerPlaybackState(aController)) {
- // now to see if we have one like this
- final String pkg = aController.getPackageName();
-
- for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
- if (entry.notification.getPackageName().equals(pkg)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found controller matching "
- + entry.notification.getKey());
- }
- controller = aController;
- mediaNotification = entry;
- break;
- }
- }
- }
- }
- }
- }
-
- if (controller != null && !sameSessions(mMediaController, controller)) {
- // We have a new media session
- clearCurrentMediaNotification();
- mMediaController = controller;
- mMediaController.registerCallback(mMediaListener);
- mMediaMetadata = mMediaController.getMetadata();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
- + mMediaMetadata);
- }
-
- if (mediaNotification != null) {
- mMediaNotificationKey = mediaNotification.notification.getKey();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
- + mMediaNotificationKey + " controller=" + mMediaController);
- }
- }
- metaDataChanged = true;
- }
- }
-
- if (metaDataChanged) {
- updateNotifications();
- }
- updateMediaMetaData(metaDataChanged, true);
- }
-
- private int getMediaControllerPlaybackState(MediaController controller) {
- if (controller != null) {
- final PlaybackState playbackState = controller.getPlaybackState();
- if (playbackState != null) {
- return playbackState.getState();
- }
- }
- return PlaybackState.STATE_NONE;
- }
-
- private boolean isPlaybackActive(int state) {
- return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
- && state != PlaybackState.STATE_NONE;
- }
-
- private void clearCurrentMediaNotification() {
- mMediaNotificationKey = null;
- mMediaMetadata = null;
- if (mMediaController != null) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
- + mMediaController.getPackageName());
- }
- mMediaController.unregisterCallback(mMediaListener);
- }
- mMediaController = null;
+ mMediaManager.findAndUpdateMediaNotifications();
}
- private boolean sameSessions(MediaController a, MediaController b) {
- if (a == b) return true;
- if (a == null) return false;
- return a.controlsSameSession(b);
- }
/**
* Hide the album artwork that is fading out and release its bitmap.
@@ -2322,9 +2163,11 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
+ // TODO: Move this to NotificationMediaManager.
/**
* Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
*/
+ @Override
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
Trace.beginSection("StatusBar#updateMediaMetaData");
if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
@@ -2343,19 +2186,22 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
+ MediaMetadata mediaMetadata = mMediaManager.getMediaMetadata();
+
if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey
- + " metadata=" + mMediaMetadata
+ Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
+ + mMediaManager.getMediaNotificationKey()
+ + " metadata=" + mediaMetadata
+ " metaDataChanged=" + metaDataChanged
+ " state=" + mState);
}
Drawable artworkDrawable = null;
- if (mMediaMetadata != null) {
+ if (mediaMetadata != null) {
Bitmap artworkBitmap = null;
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
if (artworkBitmap == null) {
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
// might still be null
}
if (artworkBitmap != null) {
@@ -2628,7 +2474,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override // NotificationData.Environment
public String getCurrentMediaNotificationKey() {
- return mMediaNotificationKey;
+ return mMediaManager.getMediaNotificationKey();
}
public boolean isScrimSrcModeEnabled() {
@@ -3093,8 +2939,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarWindowManager.setForceStatusBarVisible(false);
// Close any guts that might be visible
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
- -1 /* x */, -1 /* y */, true /* resetMenu */);
+ mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
@@ -3275,12 +3121,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
void checkBarMode(int mode, int windowState, BarTransitions transitions) {
- final boolean powerSave = mBatteryController.isPowerSave();
final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
- && windowState != WINDOW_STATE_HIDDEN && !powerSave;
- if (powerSave && getBarState() == StatusBarState.SHADE) {
- mode = MODE_WARNING;
- }
+ && windowState != WINDOW_STATE_HIDDEN;
transitions.transitionTo(mode, anim);
}
@@ -3445,28 +3287,18 @@ public class StatusBar extends SystemUI implements DemoMode,
pw.println(Settings.Global.zenModeToString(mZenMode));
pw.print(" mUseHeadsUp=");
pw.println(mUseHeadsUp);
- pw.print(" mKeyToRemoveOnGutsClosed=");
- pw.println(mKeyToRemoveOnGutsClosed);
+ pw.print(" mGutsManager: ");
+ if (mGutsManager != null) {
+ mGutsManager.dump(fd, pw, args);
+ }
if (mStatusBarView != null) {
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
}
- pw.print(" mMediaSessionManager=");
- pw.println(mMediaSessionManager);
- pw.print(" mMediaNotificationKey=");
- pw.println(mMediaNotificationKey);
- pw.print(" mMediaController=");
- pw.print(mMediaController);
- if (mMediaController != null) {
- pw.print(" state=" + mMediaController.getPlaybackState());
- }
- pw.println();
- pw.print(" mMediaMetadata=");
- pw.print(mMediaMetadata);
- if (mMediaMetadata != null) {
- pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
+ pw.println(" mMediaManager: ");
+ if (mMediaManager != null) {
+ mMediaManager.dump(fd, pw, args);
}
- pw.println();
pw.println(" Panels: ");
if (mNotificationPanel != null) {
@@ -3788,7 +3620,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mReinflateNotificationsOnUserSwitched = false;
}
updateNotificationShade();
- clearCurrentMediaNotification();
+ mMediaManager.clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
@@ -3868,10 +3700,10 @@ public class StatusBar extends SystemUI implements DemoMode,
if (visibleToUser) {
boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
boolean clearNotificationEffects =
- !isPanelFullyCollapsed() &&
+ !isPresenterFullyCollapsed() &&
(mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
int notificationLoad = mNotificationData.getActiveNotifications().size();
- if (pinnedHeadsUp && isPanelFullyCollapsed()) {
+ if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
notificationLoad = 1;
}
mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
@@ -4128,6 +3960,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
}
+ if (modeChange || command.equals(COMMAND_OPERATOR)) {
+ dispatchDemoCommandToView(command, args, R.id.operator_name);
+ }
}
private void dispatchDemoCommandToView(String command, Bundle args, int id) {
@@ -4145,7 +3980,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return mState;
}
- public boolean isPanelFullyCollapsed() {
+ @Override
+ public boolean isPresenterFullyCollapsed() {
return mNotificationPanel.isFullyCollapsed();
}
@@ -4515,12 +4351,14 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean useDarkTheme = systemColors != null
&& (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
if (isUsingDarkTheme() != useDarkTheme) {
- try {
- mOverlayManager.setEnabled("com.android.systemui.theme.dark",
- useDarkTheme, mCurrentUserId);
- } catch (RemoteException e) {
- Log.w(TAG, "Can't change theme", e);
- }
+ mUiOffloadThread.submit(() -> {
+ try {
+ mOverlayManager.setEnabled("com.android.systemui.theme.dark",
+ useDarkTheme, mCurrentUserId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't change theme", e);
+ }
+ });
}
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
@@ -4726,7 +4564,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public void onClosingFinished() {
runPostCollapseRunnables();
- if (!isPanelFullyCollapsed()) {
+ if (!isPresenterFullyCollapsed()) {
// if we set it not to be focusable when collapsing, we have to undo it when we aborted
// the closing
mStatusBarWindowManager.setStatusBarFocusable(true);
@@ -5260,7 +5098,8 @@ public class StatusBar extends SystemUI implements DemoMode,
boolean isCameraAllowedByAdmin() {
if (mDevicePolicyManager.getCameraDisabled(null, mCurrentUserId)) {
return false;
- } else if (isKeyguardShowing() && isKeyguardSecure()) {
+ } else if (mStatusBarKeyguardViewManager == null ||
+ (isKeyguardShowing() && isKeyguardSecure())) {
// Check if the admin has disabled the camera specifically for the keyguard
return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUserId)
& DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
@@ -5590,10 +5429,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected int mZenMode;
- // which notification is currently being longpress-examined by the user
- private NotificationGuts mNotificationGutsExposed;
- private MenuItem mGutsMenuItem;
-
protected NotificationShelf mNotificationShelf;
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
@@ -5604,8 +5439,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected boolean mVrMode;
- private Set<String> mNonBlockablePkgs;
-
public boolean isDeviceInteractive() {
return mDeviceInteractive;
}
@@ -5877,6 +5710,9 @@ public class StatusBar extends SystemUI implements DemoMode,
userSwitched(mCurrentUserId);
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
updateCurrentProfilesCache();
+ } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ // Start the overview connection to the launcher service
+ Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
List<ActivityManager.RecentTaskInfo> recentTask = null;
try {
@@ -6134,26 +5970,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return mGroupManager;
}
- public boolean isMediaNotification(NotificationData.Entry entry) {
- // TODO: confirm that there's a valid media key
- return entry.getExpandedContentView() != null &&
- entry.getExpandedContentView()
- .findViewById(com.android.internal.R.id.media_actions) != null;
- }
-
- // The button in the guts that links to the system notification settings for that app
- private void startAppNotificationSettingsActivity(String packageName, final int appUid,
- final NotificationChannel channel) {
- final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
- intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- if (channel != null) {
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
- }
- startNotificationGutsIntent(intent, appUid);
- }
-
- private void startNotificationGutsIntent(final Intent intent, final int appUid) {
+ @Override
+ public void startNotificationGutsIntent(final Intent intent, final int appUid) {
dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
TaskStackBuilder.create(mContext)
@@ -6176,231 +5994,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
- row.inflateGuts();
- row.setGutsView(item);
- final StatusBarNotification sbn = row.getStatusBarNotification();
- row.setTag(sbn.getPackageName());
- final NotificationGuts guts = row.getGuts();
- guts.setClosedListener((NotificationGuts g) -> {
- if (!g.willBeRemoved() && !row.isRemoved()) {
- mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
- }
- if (mNotificationGutsExposed == g) {
- mNotificationGutsExposed = null;
- mGutsMenuItem = null;
- }
- String key = sbn.getKey();
- if (key.equals(mKeyToRemoveOnGutsClosed)) {
- mKeyToRemoveOnGutsClosed = null;
- removeNotification(key, mLatestRankingMap);
- }
- });
-
- View gutsView = item.getGutsView();
- if (gutsView instanceof NotificationSnooze) {
- NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
- snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
- snoozeGuts.setStatusBarNotification(sbn);
- snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
- guts.setHeightChangedListener((NotificationGuts g) -> {
- mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
- });
- }
-
- if (gutsView instanceof NotificationInfo) {
- final UserHandle userHandle = sbn.getUser();
- PackageManager pmUser = getPackageManagerForUser(mContext,
- userHandle.getIdentifier());
- final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- final String pkg = sbn.getPackageName();
- NotificationInfo info = (NotificationInfo) gutsView;
- // Settings link is only valid for notifications that specify a user, unless this is the
- // system user.
- NotificationInfo.OnSettingsClickListener onSettingsClick = null;
- if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) {
- onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
- guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid, channel);
- };
- }
- final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
- Intent intent) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS);
- guts.resetFalsingCheck();
- startNotificationGutsIntent(intent, sbn.getUid());
- };
- final View.OnClickListener onDoneClick = (View v) -> {
- saveAndCloseNotificationMenu(row, guts, v);
- };
- final NotificationInfo.CheckSaveListener checkSaveListener =
- (Runnable saveImportance) -> {
- // If the user has security enabled, show challenge if the setting is changed.
- if (isLockscreenPublicMode(userHandle.getIdentifier())
- && (mState == StatusBarState.KEYGUARD
- || mState == StatusBarState.SHADE_LOCKED)) {
- onLockedNotificationImportanceChange(() -> {
- saveImportance.run();
- return true;
- });
- } else {
- saveImportance.run();
- }
- };
-
- ArraySet<NotificationChannel> channels = new ArraySet<>();
- channels.add(row.getEntry().channel);
- if (row.isSummaryWithChildren()) {
- // If this is a summary, then add in the children notification channels for the
- // same user and pkg.
- final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
- final int numChildren = childrenRows.size();
- for (int i = 0; i < numChildren; i++) {
- final ExpandableNotificationRow childRow = childrenRows.get(i);
- final NotificationChannel childChannel = childRow.getEntry().channel;
- final StatusBarNotification childSbn = childRow.getStatusBarNotification();
- if (childSbn.getUser().equals(userHandle) &&
- childSbn.getPackageName().equals(pkg)) {
- channels.add(childChannel);
- }
- }
- }
- try {
- info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
- row.getEntry().channel.getImportance(), sbn, onSettingsClick,
- onAppSettingsClick, onDoneClick, checkSaveListener,
- mNonBlockablePkgs);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- }
- }
-
- private void saveAndCloseNotificationMenu(
- ExpandableNotificationRow row, NotificationGuts guts, View done) {
- guts.resetFalsingCheck();
- int[] rowLocation = new int[2];
- int[] doneLocation = new int[2];
- row.getLocationOnScreen(rowLocation);
- done.getLocationOnScreen(doneLocation);
-
- final int centerX = done.getWidth() / 2;
- final int centerY = done.getHeight() / 2;
- final int x = doneLocation[0] - rowLocation[0] + centerX;
- final int y = doneLocation[1] - rowLocation[1] + centerY;
- closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, x, y, true /* resetMenu */);
- }
-
protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
- return new ExpandableNotificationRow.LongPressListener() {
- @Override
- public boolean onLongPress(View v, final int x, final int y,
- MenuItem item) {
- if (!(v instanceof ExpandableNotificationRow)) {
- return false;
- }
-
- if (v.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to window");
- return false;
- }
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- if (row.isDark()) {
- return false;
- }
- v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- if (row.areGutsExposed()) {
- closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */,
- true /* resetMenu */);
- return true;
- }
- bindGuts(row, item);
- NotificationGuts guts = row.getGuts();
-
- // Assume we are a status_bar_notification_row
- if (guts == null) {
- // This view has no guts. Examples are the more card or the dismiss all view
- return false;
- }
-
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS);
-
- // ensure that it's laid but not visible until actually laid out
- guts.setVisibility(View.INVISIBLE);
- // Post to ensure the the guts are properly laid out.
- guts.post(new Runnable() {
- @Override
- public void run() {
- if (row.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to "
- + "window");
- return;
- }
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */,
- false /* resetMenu */);
- guts.setVisibility(View.VISIBLE);
- final double horz = Math.max(guts.getWidth() - x, x);
- final double vert = Math.max(guts.getHeight() - y, y);
- final float r = (float) Math.hypot(horz, vert);
- final Animator a
- = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- a.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- // Move the notification view back over the menu
- row.resetTranslation();
- }
- });
- a.start();
- final boolean needsFalsingProtection =
- (mState == StatusBarState.KEYGUARD &&
- !mAccessibilityManager.isTouchExplorationEnabled());
- guts.setExposed(true /* exposed */, needsFalsingProtection);
- row.closeRemoteInput();
- mStackScroller.onHeightChanged(row, true /* needsAnimation */);
- mNotificationGutsExposed = guts;
- mGutsMenuItem = item;
- }
- });
- return true;
- }
- };
- }
-
- /**
- * Returns the exposed NotificationGuts or null if none are exposed.
- */
- public NotificationGuts getExposedGuts() {
- return mNotificationGutsExposed;
- }
-
- /**
- * Closes guts or notification menus that might be visible and saves any changes.
- *
- * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
- * @param force true if guts should be closed regardless of state (used for snooze only).
- * @param removeControls true if controls (e.g. info) should be closed.
- * @param x if closed based on touch location, this is the x touch location.
- * @param y if closed based on touch location, this is the y touch location.
- * @param resetMenu if any notification menus that might be revealed should be closed.
- */
- public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
- int x, int y, boolean resetMenu) {
- if (mNotificationGutsExposed != null) {
- mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
- }
- if (resetMenu) {
- mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
- }
+ return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
}
@Override
@@ -6789,7 +6384,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
// Release the HUN notification to the shade.
- if (isPanelFullyCollapsed()) {
+ if (isPresenterFullyCollapsed()) {
HeadsUpManager.setIsClickedNotification(row, true);
}
//
@@ -6921,7 +6516,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
}
@@ -7091,7 +6686,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
- return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
+ return mShowLockscreenNotifications
+ && ((mDisabled2 & DISABLE2_NOTIFICATION_SHADE) == 0)
+ && !mNotificationData.isAmbient(sbn.getKey());
}
// extended in StatusBar
@@ -7103,7 +6700,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
}
- private void updateLockscreenNotificationSetting() {
+ protected void updateLockscreenNotificationSetting() {
final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
1,
@@ -7141,8 +6738,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
- if (key.equals(mKeyToRemoveOnGutsClosed)) {
- mKeyToRemoveOnGutsClosed = null;
+ if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+ mGutsManager.setKeyToRemoveOnGutsClosed(null);
Log.w(TAG, "Notification that was kept for guts was updated. " + key);
}
@@ -7241,6 +6838,15 @@ public class StatusBar extends SystemUI implements DemoMode,
return false;
}
+ if (mIsOccluded && !isDozing()) {
+ boolean devicePublic = isLockscreenPublicMode(mCurrentUserId);
+ boolean userPublic = devicePublic || isLockscreenPublicMode(sbn.getUserId());
+ boolean needsRedaction = needsRedaction(entry);
+ if (userPublic && needsRedaction) {
+ return false;
+ }
+ }
+
if (sbn.getNotification().fullScreenIntent != null) {
if (mAccessibilityManager.isTouchExplorationEnabled()) {
if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
@@ -7340,4 +6946,43 @@ public class StatusBar extends SystemUI implements DemoMode,
mNavigationBar.getBarTransitions().setAutoDim(true);
}
};
+
+ public NotificationGutsManager getGutsManager() {
+ return mGutsManager;
+ }
+
+ @Override
+ public boolean isPresenterLocked() {
+ return mState == StatusBarState.KEYGUARD;
+ }
+
+ @Override
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ @Override
+ public NotificationData getNotificationData() {
+ return mNotificationData;
+ }
+
+ @Override
+ public RankingMap getLatestRankingMap() {
+ return mLatestRankingMap;
+ }
+
+ final NotificationInfo.CheckSaveListener mCheckSaveListener =
+ (Runnable saveImportance, StatusBarNotification sbn) -> {
+ // If the user has security enabled, show challenge if the setting is changed.
+ if (isLockscreenPublicMode(sbn.getUser().getIdentifier()) && (
+ mState == StatusBarState.KEYGUARD
+ || mState == StatusBarState.SHADE_LOCKED)) {
+ onLockedNotificationImportanceChange(() -> {
+ saveImportance.run();
+ return true;
+ });
+ } else {
+ saveImportance.run();
+ }
+ };
}
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 1e14626d..efe049ab 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1343,7 +1343,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
// Check if we need to clear any snooze leavebehinds
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
if (guts != null && !isTouchInView(ev, guts)
&& guts.getGutsContent() instanceof NotificationSnooze) {
NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
@@ -2508,12 +2508,13 @@ public class NotificationStackScrollLayout extends ViewGroup
}
// Check if we need to clear any snooze leavebehinds
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
&& !scrollWantsIt) {
mCheckForLeavebehind = false;
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
}
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mCheckForLeavebehind = true;
@@ -3065,7 +3066,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
if (!childWasSwipedOut) {
Rect clipBounds = child.getClipBounds();
- childWasSwipedOut = clipBounds.height() == 0;
+ childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
}
int animationType = childWasSwipedOut
? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
@@ -3355,8 +3356,9 @@ public class NotificationStackScrollLayout extends ViewGroup
public void checkSnoozeLeavebehind() {
if (mCheckForLeavebehind) {
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
mCheckForLeavebehind = false;
}
}
@@ -4438,8 +4440,9 @@ public class NotificationStackScrollLayout extends ViewGroup
// of the panel early.
handleChildDismissed(view);
}
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
handleMenuCoveredOrDismissed();
}
@@ -4524,7 +4527,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
public void closeControlsIfOutsideTouch(MotionEvent ev) {
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
View view = null;
if (guts != null && !guts.getGutsContent().isLeavebehind()) {
// Only close visible guts if they're not a leavebehind.
@@ -4536,8 +4539,9 @@ public class NotificationStackScrollLayout extends ViewGroup
}
if (view != null && !isTouchInView(ev, view)) {
// Touch was outside visible guts / menu notification, close what's visible
- mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */,
+ false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
resetExposedMenuView(true /* animate */, true /* force */);
}
}
diff --git a/com/android/systemui/tuner/PluginFragment.java b/com/android/systemui/tuner/PluginFragment.java
index f91e45d0..27bf534c 100644
--- a/com/android/systemui/tuner/PluginFragment.java
+++ b/com/android/systemui/tuner/PluginFragment.java
@@ -169,16 +169,23 @@ public class PluginFragment extends PreferenceFragment {
protected boolean persistBoolean(boolean value) {
final int desiredState = value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ boolean shouldSendBroadcast = false;
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
- mPm.setComponentEnabledSetting(componentName, desiredState,
- PackageManager.DONT_KILL_APP);
+
+ if (mPm.getComponentEnabledSetting(componentName) != desiredState) {
+ mPm.setComponentEnabledSetting(componentName, desiredState,
+ PackageManager.DONT_KILL_APP);
+ shouldSendBroadcast = true;
+ }
+ }
+ if (shouldSendBroadcast) {
+ final String pkg = mInfo.packageName;
+ final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
+ pkg != null ? Uri.fromParts("package", pkg, null) : null);
+ getContext().sendBroadcast(intent);
}
- final String pkg = mInfo.packageName;
- final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
- pkg != null ? Uri.fromParts("package", pkg, null) : null);
- getContext().sendBroadcast(intent);
return true;
}
diff --git a/com/android/systemui/usb/UsbConfirmActivity.java b/com/android/systemui/usb/UsbConfirmActivity.java
index 3eccccdd..e117969c 100644
--- a/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/com/android/systemui/usb/UsbConfirmActivity.java
@@ -71,10 +71,12 @@ public class UsbConfirmActivity extends AlertActivity
ap.mIcon = mResolveInfo.loadIcon(packageManager);
ap.mTitle = appName;
if (mDevice == null) {
- ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName);
+ ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName,
+ mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName);
+ ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName,
+ mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -88,9 +90,11 @@ public class UsbConfirmActivity extends AlertActivity
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
if (mDevice == null) {
- mAlwaysUse.setText(R.string.always_use_accessory);
+ mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+ mAccessory.getDescription()));
} else {
- mAlwaysUse.setText(R.string.always_use_device);
+ mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+ mDevice.getProductName()));
}
mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView)ap.mView.findViewById(
diff --git a/com/android/systemui/usb/UsbPermissionActivity.java b/com/android/systemui/usb/UsbPermissionActivity.java
index 1e69fc5c..87d11b24 100644
--- a/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/com/android/systemui/usb/UsbPermissionActivity.java
@@ -16,13 +16,17 @@
package com.android.systemui.usb;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
@@ -41,8 +45,13 @@ import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
+import com.android.internal.util.XmlUtils;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
import com.android.systemui.R;
+import org.xmlpull.v1.XmlPullParser;
+
public class UsbPermissionActivity extends AlertActivity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
@@ -84,10 +93,12 @@ public class UsbPermissionActivity extends AlertActivity
ap.mIcon = aInfo.loadIcon(packageManager);
ap.mTitle = appName;
if (mDevice == null) {
- ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName);
+ ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
+ mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_permission_prompt, appName);
+ ap.mMessage = getString(R.string.usb_device_permission_prompt, appName,
+ mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -95,25 +106,123 @@ public class UsbPermissionActivity extends AlertActivity
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
- // add "always use" checkbox
- LayoutInflater inflater = (LayoutInflater)getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
- mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
- if (mDevice == null) {
- mAlwaysUse.setText(R.string.always_use_accessory);
- } else {
- mAlwaysUse.setText(R.string.always_use_device);
+ try {
+ PackageInfo packageInfo = packageManager.getPackageInfo(mPackageName,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+ if ((mDevice != null && canBeDefault(mDevice, packageInfo))
+ || (mAccessory != null && canBeDefault(mAccessory, packageInfo))) {
+ // add "open when" checkbox
+ LayoutInflater inflater = (LayoutInflater) getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+ mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+ if (mDevice == null) {
+ mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+ mAccessory.getDescription()));
+ } else {
+ mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+ mDevice.getProductName()));
+ }
+ mAlwaysUse.setOnCheckedChangeListener(this);
+
+ mClearDefaultHint = (TextView)ap.mView.findViewById(
+ com.android.internal.R.id.clearDefaultHint);
+ mClearDefaultHint.setVisibility(View.GONE);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignore
}
- mAlwaysUse.setOnCheckedChangeListener(this);
- mClearDefaultHint = (TextView)ap.mView.findViewById(
- com.android.internal.R.id.clearDefaultHint);
- mClearDefaultHint.setVisibility(View.GONE);
setupAlert();
}
+ /**
+ * Can the app be the default for the USB device. I.e. can the app be launched by default if
+ * the device is plugged in.
+ *
+ * @param device The device the app would be default for
+ * @param packageInfo The package info of the app
+ *
+ * @return {@code true} iff the app can be default
+ */
+ private boolean canBeDefault(@NonNull UsbDevice device, @NonNull PackageInfo packageInfo) {
+ ActivityInfo[] activities = packageInfo.activities;
+ if (activities != null) {
+ int numActivities = activities.length;
+ for (int i = 0; i < numActivities; i++) {
+ ActivityInfo activityInfo = activities[i];
+
+ try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+ UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ if (parser == null) {
+ continue;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("usb-device".equals(parser.getName())) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (filter.matches(device)) {
+ return true;
+ }
+ }
+
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Can the app be the default for the USB accessory. I.e. can the app be launched by default if
+ * the accessory is plugged in.
+ *
+ * @param accessory The accessory the app would be default for
+ * @param packageInfo The package info of the app
+ *
+ * @return {@code true} iff the app can be default
+ */
+ private boolean canBeDefault(@NonNull UsbAccessory accessory,
+ @NonNull PackageInfo packageInfo) {
+ ActivityInfo[] activities = packageInfo.activities;
+ if (activities != null) {
+ int numActivities = activities.length;
+ for (int i = 0; i < numActivities; i++) {
+ ActivityInfo activityInfo = activities[i];
+
+ try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+ UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ if (parser == null) {
+ continue;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("usb-accessory".equals(parser.getName())) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (filter.matches(accessory)) {
+ return true;
+ }
+ }
+
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
public void onDestroy() {
IBinder b = ServiceManager.getService(USB_SERVICE);
diff --git a/com/android/systemui/volume/SystemUIInterpolators.java b/com/android/systemui/volume/SystemUIInterpolators.java
new file mode 100644
index 00000000..5ad8840a
--- /dev/null
+++ b/com/android/systemui/volume/SystemUIInterpolators.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.animation.TimeInterpolator;
+
+public class SystemUIInterpolators {
+ public static final class LogDecelerateInterpolator implements TimeInterpolator {
+ private final float mBase;
+ private final float mDrift;
+ private final float mTimeScale;
+ private final float mOutputScale;
+
+ public LogDecelerateInterpolator() {
+ this(400f, 1.4f, 0);
+ }
+
+ private LogDecelerateInterpolator(float base, float timeScale, float drift) {
+ mBase = base;
+ mDrift = drift;
+ mTimeScale = 1f / timeScale;
+
+ mOutputScale = 1f / computeLog(1f);
+ }
+
+ private float computeLog(float t) {
+ return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return computeLog(t) * mOutputScale;
+ }
+ }
+
+ public static final class LogAccelerateInterpolator implements TimeInterpolator {
+ private final int mBase;
+ private final int mDrift;
+ private final float mLogScale;
+
+ public LogAccelerateInterpolator() {
+ this(100, 0);
+ }
+
+ private LogAccelerateInterpolator(int base, int drift) {
+ mBase = base;
+ mDrift = drift;
+ mLogScale = 1f / computeLog(1, mBase, mDrift);
+ }
+
+ private static float computeLog(float t, int base, int drift) {
+ return (float) -Math.pow(base, -t) + 1 + (drift * t);
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
+ }
+ }
+
+ public interface Callback {
+ void onAnimatingChanged(boolean animating);
+ }
+}
diff --git a/com/android/systemui/volume/VolumeDialogComponent.java b/com/android/systemui/volume/VolumeDialogComponent.java
index f6d36e85..4dff9bd5 100644
--- a/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/com/android/systemui/volume/VolumeDialogComponent.java
@@ -62,7 +62,6 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS);
- private final Extension mExtension;
private VolumeDialog mDialog;
private VolumePolicy mVolumePolicy = new VolumePolicy(
DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
@@ -79,7 +78,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
// Allow plugins to reference the VolumeDialogController.
Dependency.get(PluginDependencyProvider.class)
.allowPluginDependency(VolumeDialogController.class);
- mExtension = Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
+ Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
.withPlugin(VolumeDialog.class)
.withDefault(this::createDefault)
.withCallback(dialog -> {
@@ -96,7 +95,6 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
private VolumeDialog createDefault() {
VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
- impl.setStreamImportant(AudioManager.STREAM_ALARM, true);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
@@ -152,7 +150,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
- mExtension.reload();
+ mController.mCallbacks.onConfigurationChanged();
}
}
diff --git a/com/android/systemui/volume/VolumeDialogImpl.java b/com/android/systemui/volume/VolumeDialogImpl.java
index 761e979d..4b8f581a 100644
--- a/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/com/android/systemui/volume/VolumeDialogImpl.java
@@ -19,6 +19,8 @@ package com.android.systemui.volume;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
import android.annotation.NonNull;
@@ -26,16 +28,13 @@ import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.KeyguardManager;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.Debug;
@@ -44,15 +43,10 @@ import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings.Global;
-import android.transition.AutoTransition;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -60,7 +54,6 @@ import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
@@ -74,16 +67,13 @@ import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
import com.android.systemui.Interpolators;
-import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerZenModePanel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -96,11 +86,9 @@ import java.util.List;
*
* Methods ending in "H" must be called on the (ui) handler.
*/
-public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
+public class VolumeDialogImpl implements VolumeDialog {
private static final String TAG = Util.logTag(VolumeDialogImpl.class);
- public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
-
private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
private static final int UPDATE_ANIMATION_DURATION = 80;
@@ -109,29 +97,22 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private final VolumeDialogController mController;
private Window mWindow;
+ private HardwareUiLayout mHardwareLayout;
private CustomDialog mDialog;
private ViewGroup mDialogView;
private ViewGroup mDialogRowsView;
private ViewGroup mDialogContentView;
- private ImageButton mExpandButton;
private final List<VolumeRow> mRows = new ArrayList<>();
private ConfigurableTexts mConfigurableTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
private final KeyguardManager mKeyguard;
- private final AudioManager mAudioManager;
private final AccessibilityManager mAccessibilityMgr;
- private int mExpandButtonAnimationDuration;
- private ZenFooter mZenFooter;
private final Object mSafetyWarningLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
private final ColorStateList mActiveSliderTint;
private final ColorStateList mInactiveSliderTint;
- private VolumeDialogMotion mMotion;
- private int mWindowType;
- private final ZenModeController mZenModeController;
private boolean mShowing;
- private boolean mExpanded;
private boolean mShowA11yStream;
private int mActiveStream;
@@ -139,24 +120,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
private State mState;
- private boolean mExpandButtonAnimationRunning;
private SafetyWarningDialog mSafetyWarning;
- private Callback mCallback;
- private boolean mPendingStateChanged;
- private boolean mPendingRecheckAll;
- private long mCollapseTime;
private boolean mHovering = false;
- private int mDensity;
-
- private boolean mShowFullZen;
- private TunerZenModePanel mZenPanel;
public VolumeDialogImpl(Context context) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
- mZenModeController = Dependency.get(ZenModeController.class);
mController = Dependency.get(VolumeDialogController.class);
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAccessibilityMgr =
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
@@ -164,29 +134,18 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
public void init(int windowType, Callback callback) {
- mCallback = callback;
- mWindowType = windowType;
-
initDialog();
mAccessibility.init();
mController.addCallback(mControllerCallbackH, mHandler);
mController.getState();
- Dependency.get(TunerService.class).addTunable(this, SHOW_FULL_ZEN);
-
- final Configuration currentConfig = mContext.getResources().getConfiguration();
- mDensity = currentConfig.densityDpi;
}
@Override
public void destroy() {
mAccessibility.destroy();
mController.removeCallback(mControllerCallbackH);
- if (mZenFooter != null) {
- mZenFooter.cleanup();
- }
- Dependency.get(TunerService.class).removeTunable(this);
mHandler.removeCallbacksAndMessages(null);
}
@@ -199,25 +158,16 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
mWindow = mDialog.getWindow();
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- mDialog.setCanceledOnTouchOutside(true);
- final Resources res = mContext.getResources();
- final WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.type = mWindowType;
- lp.format = PixelFormat.TRANSLUCENT;
- lp.setTitle(VolumeDialogImpl.class.getSimpleName());
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
- lp.gravity = Gravity.TOP;
- lp.windowAnimations = -1;
- mWindow.setAttributes(lp);
- mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ mWindow.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
mDialog.setContentView(R.layout.volume_dialog);
mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
@@ -231,33 +181,11 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
return true;
}
});
+ mHardwareLayout = HardwareUiLayout.get(mDialogView);
+ mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE));
mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
- mExpanded = false;
- mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
- mExpandButton.setOnClickListener(mClickExpand);
-
- mExpandButton.setVisibility(
- AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE);
- updateWindowWidthH();
- updateExpandButtonH();
-
- mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
- new VolumeDialogMotion.Callback() {
- @Override
- public void onAnimatingChanged(boolean animating) {
- if (animating) return;
- if (mPendingStateChanged) {
- mHandler.sendEmptyMessage(H.STATE_CHANGED);
- mPendingStateChanged = false;
- }
- if (mPendingRecheckAll) {
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- mPendingRecheckAll = false;
- }
- }
- });
if (mRows.isEmpty()) {
addRow(AudioManager.STREAM_MUSIC,
@@ -279,40 +207,12 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
} else {
addExistingRows();
}
- mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
- mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
- mZenFooter.init(mZenModeController);
- mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
- mZenPanel.init(mZenModeController);
- mZenPanel.setCallback(mZenPanelCallback);
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (SHOW_FULL_ZEN.equals(key)) {
- mShowFullZen = newValue != null && Integer.parseInt(newValue) != 0;
- }
}
private ColorStateList loadColorStateList(int colorResId) {
return ColorStateList.valueOf(mContext.getColor(colorResId));
}
- private void updateWindowWidthH() {
- final ViewGroup.MarginLayoutParams lp =
- (ViewGroup.MarginLayoutParams) mDialogView.getLayoutParams();
- final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
- if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
- int w = dm.widthPixels;
- final int max = mContext.getResources()
- .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
- if (w > max) {
- w = max;
- }
- lp.width = w - lp.getMarginEnd() - lp.getMarginStart();
- mDialogView.setLayoutParams(lp);
- }
-
public void setStreamImportant(int stream, boolean important) {
mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
}
@@ -356,14 +256,10 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
final VolumeRow row = mRows.get(i);
initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important);
mDialogRowsView.addView(row.view);
+ updateVolumeRowH(row);
}
}
-
- private boolean isAttached() {
- return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
- }
-
private VolumeRow getActiveRow() {
for (VolumeRow row : mRows) {
if (row.stream == mActiveStream) {
@@ -383,14 +279,10 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
public void dump(PrintWriter writer) {
writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
writer.print(" mShowing: "); writer.println(mShowing);
- writer.print(" mExpanded: "); writer.println(mExpanded);
- writer.print(" mExpandButtonAnimationRunning: ");
- writer.println(mExpandButtonAnimationRunning);
writer.print(" mActiveStream: "); writer.println(mActiveStream);
writer.print(" mDynamic: "); writer.println(mDynamic);
writer.print(" mAutomute: "); writer.println(mAutomute);
writer.print(" mSilentMode: "); writer.println(mSilentMode);
- writer.print(" mCollapseTime: "); writer.println(mCollapseTime);
writer.print(" mAccessibility.mFeedbackEnabled: ");
writer.println(mAccessibility.mFeedbackEnabled);
}
@@ -413,9 +305,9 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
row.view.setId(row.stream);
row.view.setTag(row);
- row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
+ row.header = row.view.findViewById(R.id.volume_row_header);
row.header.setId(20 * row.stream);
- row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+ row.slider = row.view.findViewById(R.id.volume_row_slider);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.anim = null;
@@ -496,11 +388,27 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
rescheduleTimeoutH();
if (mShowing) return;
mShowing = true;
- mMotion.startShow();
+ mHardwareLayout.setTranslationX(getAnimTranslation());
+ mHardwareLayout.setAlpha(0);
+ mHardwareLayout.animate()
+ .alpha(1)
+ .translationX(0)
+ .setDuration(300)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> {
+ mDialog.show();
+ mWindow.getDecorView().requestAccessibilityFocus();
+ })
+ .start();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
}
+ private float getAnimTranslation() {
+ return mContext.getResources().getDimension(
+ R.dimen.volume_dialog_panel_width) / 2;
+ }
+
protected void rescheduleTimeoutH() {
mHandler.removeMessages(H.DISMISS);
final int timeout = computeTimeoutH();
@@ -514,28 +422,24 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
if (mAccessibility.mFeedbackEnabled) return 20000;
if (mHovering) return 16000;
if (mSafetyWarning != null) return 5000;
- if (mExpanded || mExpandButtonAnimationRunning) return 5000;
if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
- if (mZenFooter.shouldShowIntroduction()) {
- return 6000;
- }
return 3000;
}
protected void dismissH(int reason) {
- if (mMotion.isAnimating()) {
- return;
- }
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
if (!mShowing) return;
mShowing = false;
- mMotion.startDismiss(new Runnable() {
- @Override
- public void run() {
- updateExpandedH(false /* expanding */, true /* dismissing */);
- }
- });
+ mHardwareLayout.setTranslationX(0);
+ mHardwareLayout.setAlpha(1);
+ mHardwareLayout.animate()
+ .alpha(0)
+ .translationX(getAnimTranslation())
+ .setDuration(300)
+ .withEndAction(() -> mDialog.dismiss())
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .start();
if (mAccessibilityMgr.isEnabled()) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -555,83 +459,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
}
- private void updateDialogBottomMarginH() {
- final long diff = System.currentTimeMillis() - mCollapseTime;
- final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
- final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
- final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
- mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
- if (bottomMargin != mlp.bottomMargin) {
- if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
- mlp.bottomMargin = bottomMargin;
- mDialogView.setLayoutParams(mlp);
- }
- }
-
- private long getConservativeCollapseDuration() {
- return mExpandButtonAnimationDuration * 3;
- }
-
- private void prepareForCollapse() {
- mHandler.removeMessages(H.UPDATE_BOTTOM_MARGIN);
- mCollapseTime = System.currentTimeMillis();
- updateDialogBottomMarginH();
- mHandler.sendEmptyMessageDelayed(H.UPDATE_BOTTOM_MARGIN, getConservativeCollapseDuration());
- }
-
- private void updateExpandedH(final boolean expanded, final boolean dismissing) {
- if (mExpanded == expanded) return;
- mExpanded = expanded;
- mExpandButtonAnimationRunning = isAttached();
- if (D.BUG) Log.d(TAG, "updateExpandedH " + expanded);
- updateExpandButtonH();
- updateFooterH();
- TransitionManager.endTransitions(mDialogView);
- final VolumeRow activeRow = getActiveRow();
- if (!dismissing) {
- mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT);
- TransitionManager.beginDelayedTransition(mDialogView, getTransition());
- }
- updateRowsH(activeRow);
- rescheduleTimeoutH();
- }
-
- private void updateExpandButtonH() {
- if (D.BUG) Log.d(TAG, "updateExpandButtonH");
- mExpandButton.setClickable(!mExpandButtonAnimationRunning);
- if (!(mExpandButtonAnimationRunning && isAttached())) {
- final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
- : R.drawable.ic_volume_expand_animation;
- if (hasTouchFeature()) {
- mExpandButton.setImageResource(res);
- } else {
- // if there is no touch feature, show the volume ringer instead
- mExpandButton.setImageResource(R.drawable.ic_volume_ringer);
- mExpandButton.setBackgroundResource(0); // remove gray background emphasis
- }
- mExpandButton.setContentDescription(mContext.getString(mExpanded ?
- R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand));
- }
- if (mExpandButtonAnimationRunning) {
- final Drawable d = mExpandButton.getDrawable();
- if (d instanceof AnimatedVectorDrawable) {
- // workaround to reset drawable
- final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState()
- .newDrawable();
- mExpandButton.setImageDrawable(avd);
- avd.start();
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mExpandButtonAnimationRunning = false;
- updateExpandButtonH();
- rescheduleTimeoutH();
- }
- }, mExpandButtonAnimationDuration);
- }
- }
- }
-
private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) {
boolean isActive = row == activeRow;
if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
@@ -639,15 +466,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
// if the active row is accessibility, then continue to display previous
- // active row since accessibility is dispalyed under it
+ // active row since accessibility is displayed under it
if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY &&
row.stream == mPrevActiveStream) {
return true;
}
- return mExpanded && row.view.getVisibility() == View.VISIBLE
- || (mExpanded && (row.important || isActive))
- || !mExpanded && isActive;
+ return row.important || isActive;
}
private void updateRowsH(final VolumeRow activeRow) {
@@ -680,13 +505,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
private void onStateChangedH(State state) {
- final boolean animating = mMotion.isAnimating();
- if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
mState = state;
- if (animating) {
- mPendingStateChanged = true;
- return;
- }
mDynamic.clear();
// add any new dynamic rows
for (int i = 0; i < state.states.size(); i++) {
@@ -709,38 +528,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
for (VolumeRow row : mRows) {
updateVolumeRowH(row);
}
- updateFooterH();
- }
-
- private void updateFooterH() {
- if (D.BUG) Log.d(TAG, "updateFooterH");
- final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
- final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
- && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
- && !mZenPanel.isEditing();
-
- TransitionManager.endTransitions(mDialogView);
- TransitionManager.beginDelayedTransition(mDialogView, getTransition());
- if (wasVisible != visible && !visible) {
- prepareForCollapse();
- }
- Util.setVisOrGone(mZenFooter, visible);
- mZenFooter.update();
-
- final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
- final boolean fullVisible = mShowFullZen && !visible;
- if (fullWasVisible != fullVisible) {
- Util.setVisOrGone(mZenPanel, fullVisible);
- if (fullVisible) {
- mZenPanel.setZenState(mState.zenMode);
- mZenPanel.setDoneListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mHandler.sendEmptyMessage(H.UPDATE_FOOTER);
- }
- });
- }
- }
}
private void updateVolumeRowH(VolumeRow row) {
@@ -787,19 +574,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
final int iconRes =
isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
- : isRingSilent || zenMuted ? row.cachedIconRes
+ : isRingSilent || zenMuted ? row.iconMuteRes
: ss.routedToBluetooth ?
(ss.muted ? R.drawable.ic_volume_media_bt_mute
: R.drawable.ic_volume_media_bt)
: mAutomute && ss.level == 0 ? row.iconMuteRes
: (ss.muted ? row.iconMuteRes : row.iconRes);
- if (iconRes != row.cachedIconRes) {
- if (row.cachedIconRes != 0 && isRingVibrate) {
- mController.vibrate();
- }
- row.cachedIconRes = iconRes;
- row.icon.setImageResource(iconRes);
- }
+ row.icon.setImageResource(iconRes);
row.iconState =
iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
: (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
@@ -860,7 +641,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
- if (isActive && mExpanded) {
+ if (isActive) {
row.slider.requestFocus();
}
final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint
@@ -980,43 +761,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
}
- private AutoTransition getTransition() {
- AutoTransition transition = new AutoTransition();
- transition.setDuration(mExpandButtonAnimationDuration);
- transition.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- transition.addListener(new Transition.TransitionListener() {
- @Override
- public void onTransitionStart(Transition transition) {
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- public void onTransitionCancel(Transition transition) {
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- }
- });
- return transition;
- }
-
- private boolean hasTouchFeature() {
- final PackageManager pm = mContext.getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
- }
-
private final VolumeDialogController.Callbacks mControllerCallbackH
= new VolumeDialogController.Callbacks() {
@Override
@@ -1046,17 +790,9 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
@Override
public void onConfigurationChanged() {
- Configuration newConfig = mContext.getResources().getConfiguration();
- final int density = newConfig.densityDpi;
- if (density != mDensity) {
- mDialog.dismiss();
- mZenFooter.cleanup();
- initDialog();
- mDensity = density;
- }
- updateWindowWidthH();
+ mDialog.dismiss();
+ initDialog();
mConfigurableTexts.update();
- mZenFooter.onConfigurationChanged();
}
@Override
@@ -1092,33 +828,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
};
- private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
- @Override
- public void onPrioritySettings() {
- mCallback.onZenPrioritySettingsClicked();
- }
-
- @Override
- public void onInteraction() {
- mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT);
- }
-
- @Override
- public void onExpanded(boolean expanded) {
- // noop.
- }
- };
-
- private final OnClickListener mClickExpand = new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mExpandButtonAnimationRunning) return;
- final boolean newExpand = !mExpanded;
- Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
- updateExpandedH(newExpand, false /* dismissing */);
- }
- };
-
private final class H extends Handler {
private static final int SHOW = 1;
private static final int DISMISS = 2;
@@ -1127,8 +836,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private static final int SET_STREAM_IMPORTANT = 5;
private static final int RESCHEDULE_TIMEOUT = 6;
private static final int STATE_CHANGED = 7;
- private static final int UPDATE_BOTTOM_MARGIN = 8;
- private static final int UPDATE_FOOTER = 9;
public H() {
super(Looper.getMainLooper());
@@ -1144,15 +851,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
case STATE_CHANGED: onStateChangedH(mState); break;
- case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
- case UPDATE_FOOTER: updateFooterH(); break;
}
}
}
- private final class CustomDialog extends Dialog {
+ private final class CustomDialog extends Dialog implements DialogInterface {
public CustomDialog(Context context) {
- super(context);
+ super(context, com.android.systemui.R.style.qs_theme);
}
@Override
@@ -1162,26 +867,15 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
@Override
- protected void onStop() {
- super.onStop();
- final boolean animating = mMotion.isAnimating();
- if (D.BUG) Log.d(TAG, "onStop animating=" + animating);
- if (animating) {
- mPendingRecheckAll = true;
- return;
- }
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
}
@Override
- public boolean onTouchEvent(MotionEvent event) {
- if (isShowing()) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
- return true;
- }
- }
- return false;
+ protected void onStop() {
+ super.onStop();
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
}
@Override
@@ -1324,7 +1018,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private int iconRes;
private int iconMuteRes;
private boolean important;
- private int cachedIconRes;
private ColorStateList cachedSliderTint;
private int iconState; // from Events
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
diff --git a/com/android/systemui/volume/VolumeDialogMotion.java b/com/android/systemui/volume/VolumeDialogMotion.java
deleted file mode 100644
index 01d31e2a..00000000
--- a/com/android/systemui/volume/VolumeDialogMotion.java
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.DialogInterface.OnShowListener;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.PathInterpolator;
-
-public class VolumeDialogMotion {
- private static final String TAG = Util.logTag(VolumeDialogMotion.class);
-
- private static final float ANIMATION_SCALE = 1.0f;
- private static final int PRE_DISMISS_DELAY = 50;
-
- private final Dialog mDialog;
- private final View mDialogView;
- private final ViewGroup mContents; // volume rows + zen footer
- private final View mChevron;
- private final Handler mHandler = new Handler();
- private final Callback mCallback;
-
- private boolean mAnimating; // show or dismiss animation is running
- private boolean mShowing; // show animation is running
- private boolean mDismissing; // dismiss animation is running
- private ValueAnimator mChevronPositionAnimator;
- private ValueAnimator mContentsPositionAnimator;
-
- public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
- Callback callback) {
- mDialog = dialog;
- mDialogView = dialogView;
- mContents = contents;
- mChevron = chevron;
- mCallback = callback;
- mDialog.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
- }
- });
- mDialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- if (D.BUG) Log.d(TAG, "mDialog.onShow");
- final int h = mDialogView.getHeight();
- mDialogView.setTranslationY(-h);
- startShowAnimation();
- }
- });
- }
-
- public boolean isAnimating() {
- return mAnimating;
- }
-
- private void setShowing(boolean showing) {
- if (showing == mShowing) return;
- mShowing = showing;
- if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
- updateAnimating();
- }
-
- private void setDismissing(boolean dismissing) {
- if (dismissing == mDismissing) return;
- mDismissing = dismissing;
- if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
- updateAnimating();
- }
-
- private void updateAnimating() {
- final boolean animating = mShowing || mDismissing;
- if (animating == mAnimating) return;
- mAnimating = animating;
- if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
- if (mCallback != null) {
- mCallback.onAnimatingChanged(mAnimating);
- }
- }
-
- public void startShow() {
- if (D.BUG) Log.d(TAG, "startShow");
- if (mShowing) return;
- setShowing(true);
- if (mDismissing) {
- mDialogView.animate().cancel();
- setDismissing(false);
- startShowAnimation();
- return;
- }
- if (D.BUG) Log.d(TAG, "mDialog.show()");
- mDialog.show();
- }
-
- private int chevronDistance() {
- return mChevron.getHeight() / 6;
- }
-
- private int chevronPosY() {
- final Object tag = mChevron == null ? null : mChevron.getTag();
- return tag == null ? 0 : (Integer) tag;
- }
-
- private void startShowAnimation() {
- if (D.BUG) Log.d(TAG, "startShowAnimation");
- mDialogView.animate()
- .translationY(0)
- .setDuration(scaledDuration(300))
- .setInterpolator(new LogDecelerateInterpolator())
- .setListener(null)
- .setUpdateListener(animation -> {
- if (mChevronPositionAnimator != null) {
- final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
- if (mChevronPositionAnimator == null) return;
- // reposition chevron
- final int posY = chevronPosY();
- mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
- }
- })
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (mChevronPositionAnimator == null) return;
- // reposition chevron
- final int posY = chevronPosY();
- mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
- }
- })
- .start();
-
- mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
- .setDuration(scaledDuration(400));
- mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) return;
- if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
- setShowing(false);
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
- mCancelled = true;
- }
- });
- mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float v = (Float) animation.getAnimatedValue();
- mContents.setTranslationY(v + -mDialogView.getTranslationY());
- }
- });
- mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
- mContentsPositionAnimator.start();
-
- mContents.setAlpha(0);
- mContents.animate()
- .alpha(1)
- .setDuration(scaledDuration(150))
- .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
- .start();
-
- mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
- .setDuration(scaledDuration(250));
- mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f));
- mChevronPositionAnimator.start();
-
- mChevron.setAlpha(0);
- mChevron.animate()
- .alpha(1)
- .setStartDelay(scaledDuration(50))
- .setDuration(scaledDuration(150))
- .setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f))
- .start();
- }
-
- public void startDismiss(final Runnable onComplete) {
- if (D.BUG) Log.d(TAG, "startDismiss");
- if (mDismissing) return;
- setDismissing(true);
- if (mShowing) {
- mDialogView.animate().cancel();
- if (mContentsPositionAnimator != null) {
- mContentsPositionAnimator.cancel();
- }
- mContents.animate().cancel();
- if (mChevronPositionAnimator != null) {
- mChevronPositionAnimator.cancel();
- }
- mChevron.animate().cancel();
- setShowing(false);
- }
- mDialogView.animate()
- .translationY(-mDialogView.getHeight())
- .setDuration(scaledDuration(250))
- .setInterpolator(new LogAccelerateInterpolator())
- .setUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mContents.setTranslationY(-mDialogView.getTranslationY());
- final int posY = chevronPosY();
- mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
- }
- })
- .setListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) return;
- if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
- mDialog.dismiss();
- onComplete.run();
- setDismissing(false);
- }
- }, PRE_DISMISS_DELAY);
-
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
- mCancelled = true;
- }
- }).start();
- }
-
- private static int scaledDuration(int base) {
- return (int) (base * ANIMATION_SCALE);
- }
-
- public static final class LogDecelerateInterpolator implements TimeInterpolator {
- private final float mBase;
- private final float mDrift;
- private final float mTimeScale;
- private final float mOutputScale;
-
- public LogDecelerateInterpolator() {
- this(400f, 1.4f, 0);
- }
-
- private LogDecelerateInterpolator(float base, float timeScale, float drift) {
- mBase = base;
- mDrift = drift;
- mTimeScale = 1f / timeScale;
-
- mOutputScale = 1f / computeLog(1f);
- }
-
- private float computeLog(float t) {
- return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
- }
-
- @Override
- public float getInterpolation(float t) {
- return computeLog(t) * mOutputScale;
- }
- }
-
- public static final class LogAccelerateInterpolator implements TimeInterpolator {
- private final int mBase;
- private final int mDrift;
- private final float mLogScale;
-
- public LogAccelerateInterpolator() {
- this(100, 0);
- }
-
- private LogAccelerateInterpolator(int base, int drift) {
- mBase = base;
- mDrift = drift;
- mLogScale = 1f / computeLog(1, mBase, mDrift);
- }
-
- private static float computeLog(float t, int base, int drift) {
- return (float) -Math.pow(base, -t) + 1 + (drift * t);
- }
-
- @Override
- public float getInterpolation(float t) {
- return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
- }
- }
-
- public interface Callback {
- void onAnimatingChanged(boolean animating);
- }
-}
diff --git a/com/android/systemui/volume/ZenFooter.java b/com/android/systemui/volume/ZenFooter.java
deleted file mode 100644
index 80e16298..00000000
--- a/com/android/systemui/volume/ZenFooter.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.volume;
-
-import android.animation.LayoutTransition;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.provider.Settings.Global;
-import android.service.notification.ZenModeConfig;
-import android.transition.AutoTransition;
-import android.transition.TransitionManager;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import java.util.Objects;
-
-/**
- * Zen mode information (and end button) attached to the bottom of the volume dialog.
- */
-public class ZenFooter extends LinearLayout {
- private static final String TAG = Util.logTag(ZenFooter.class);
-
- private final Context mContext;
- private final ConfigurableTexts mConfigurableTexts;
-
- private ImageView mIcon;
- private TextView mSummaryLine1;
- private TextView mSummaryLine2;
- private TextView mEndNowButton;
- private View mZenIntroduction;
- private View mZenIntroductionConfirm;
- private TextView mZenIntroductionMessage;
- private int mZen = -1;
- private ZenModeConfig mConfig;
- private ZenModeController mController;
-
- public ZenFooter(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mConfigurableTexts = new ConfigurableTexts(mContext);
- final LayoutTransition layoutTransition = new LayoutTransition();
- layoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
- setLayoutTransition(layoutTransition);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIcon = findViewById(R.id.volume_zen_icon);
- mSummaryLine1 = findViewById(R.id.volume_zen_summary_line_1);
- mSummaryLine2 = findViewById(R.id.volume_zen_summary_line_2);
- mEndNowButton = findViewById(R.id.volume_zen_end_now);
- mZenIntroduction = findViewById(R.id.zen_introduction);
- mZenIntroductionMessage = findViewById(R.id.zen_introduction_message);
- mConfigurableTexts.add(mZenIntroductionMessage, R.string.zen_alarms_introduction);
- mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm);
- mZenIntroductionConfirm.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- confirmZenIntroduction();
- }
- });
- Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction());
- mConfigurableTexts.add(mSummaryLine1);
- mConfigurableTexts.add(mSummaryLine2);
- mConfigurableTexts.add(mEndNowButton, R.string.volume_zen_end_now);
- }
-
- public void init(final ZenModeController controller) {
- mEndNowButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setZen(Global.ZEN_MODE_OFF);
- controller.setZen(Global.ZEN_MODE_OFF, null, TAG);
- }
- });
- mZen = controller.getZen();
- mConfig = controller.getConfig();
- mController = controller;
- mController.addCallback(mZenCallback);
- update();
- updateIntroduction();
- }
-
- public void cleanup() {
- mController.removeCallback(mZenCallback);
- }
-
- private void setZen(int zen) {
- if (mZen == zen) return;
- mZen = zen;
- update();
- post(() -> {
- updateIntroduction();
- });
- }
-
- private void setConfig(ZenModeConfig config) {
- if (Objects.equals(mConfig, config)) return;
- mConfig = config;
- update();
- }
-
- private void confirmZenIntroduction() {
- Prefs.putBoolean(mContext, Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, true);
- updateIntroduction();
- }
-
- private boolean isZenPriority() {
- return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- }
-
- private boolean isZenAlarms() {
- return mZen == Global.ZEN_MODE_ALARMS;
- }
-
- private boolean isZenNone() {
- return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
-
- public void update() {
- mIcon.setImageResource(isZenNone() ? R.drawable.ic_dnd_total_silence : R.drawable.ic_dnd);
- final String line1 =
- isZenPriority() ? mContext.getString(R.string.interruption_level_priority)
- : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms)
- : isZenNone() ? mContext.getString(R.string.interruption_level_none)
- : null;
- Util.setText(mSummaryLine1, line1);
-
- final CharSequence line2 = ZenModeConfig.getConditionSummary(mContext, mConfig,
- mController.getCurrentUser(), true /*shortVersion*/);
- Util.setText(mSummaryLine2, line2);
- }
-
- public boolean shouldShowIntroduction() {
- final boolean confirmed = Prefs.getBoolean(mContext,
- Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, false);
- return !confirmed && isZenAlarms();
- }
-
- public void updateIntroduction() {
- Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction());
- }
-
- public void onConfigurationChanged() {
- mConfigurableTexts.update();
- }
-
- private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
- @Override
- public void onZenChanged(int zen) {
- setZen(zen);
- }
- @Override
- public void onConfigChanged(ZenModeConfig config) {
- setConfig(config);
- }
- };
-}
diff --git a/java/lang/invoke/ByteArrayVarHandle.java b/java/lang/invoke/ByteArrayViewVarHandle.java
index 4237058d..abdb1cbd 100644
--- a/java/lang/invoke/ByteArrayVarHandle.java
+++ b/java/lang/invoke/ByteArrayViewVarHandle.java
@@ -22,16 +22,16 @@ import java.nio.ByteOrder;
* A VarHandle to access byte array elements as an array of primitive types.
* @hide
*/
-final class ByteArrayVarHandle extends VarHandle {
- private ByteOrder byteOrder;
+final class ByteArrayViewVarHandle extends VarHandle {
+ private boolean nativeByteOrder;
- private ByteArrayVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
+ private ByteArrayViewVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
byte[].class, int.class);
- this.byteOrder = byteOrder;
+ this.nativeByteOrder = byteOrder.equals(ByteOrder.nativeOrder());
}
- static ByteArrayVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
- return new ByteArrayVarHandle(arrayClass, byteOrder);
+ static ByteArrayViewVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
+ return new ByteArrayViewVarHandle(arrayClass, byteOrder);
}
}
diff --git a/java/lang/invoke/ByteBufferViewVarHandle.java b/java/lang/invoke/ByteBufferViewVarHandle.java
index 74f3b0cb..75fcbab1 100644
--- a/java/lang/invoke/ByteBufferViewVarHandle.java
+++ b/java/lang/invoke/ByteBufferViewVarHandle.java
@@ -24,12 +24,12 @@ import java.nio.ByteOrder;
* @hide
*/
final class ByteBufferViewVarHandle extends VarHandle {
- private ByteOrder byteOrder;
+ private boolean nativeByteOrder;
private ByteBufferViewVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
ByteBuffer.class, int.class);
- this.byteOrder = byteOrder;
+ this.nativeByteOrder = byteOrder.equals(ByteOrder.nativeOrder());
}
static ByteBufferViewVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
diff --git a/java/lang/invoke/CallSite.java b/java/lang/invoke/CallSite.java
index 1ff1eb84..85b4bb9f 100644
--- a/java/lang/invoke/CallSite.java
+++ b/java/lang/invoke/CallSite.java
@@ -25,15 +25,363 @@
package java.lang.invoke;
+// Android-changed: Not using Empty
+//import sun.invoke.empty.Empty;
+import static java.lang.invoke.MethodHandleStatics.*;
+import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
+
+/**
+ * A {@code CallSite} is a holder for a variable {@link MethodHandle},
+ * which is called its {@code target}.
+ * An {@code invokedynamic} instruction linked to a {@code CallSite} delegates
+ * all calls to the site's current target.
+ * A {@code CallSite} may be associated with several {@code invokedynamic}
+ * instructions, or it may be "free floating", associated with none.
+ * In any case, it may be invoked through an associated method handle
+ * called its {@linkplain #dynamicInvoker dynamic invoker}.
+ * <p>
+ * {@code CallSite} is an abstract class which does not allow
+ * direct subclassing by users. It has three immediate,
+ * concrete subclasses that may be either instantiated or subclassed.
+ * <ul>
+ * <li>If a mutable target is not required, an {@code invokedynamic} instruction
+ * may be permanently bound by means of a {@linkplain ConstantCallSite constant call site}.
+ * <li>If a mutable target is required which has volatile variable semantics,
+ * because updates to the target must be immediately and reliably witnessed by other threads,
+ * a {@linkplain VolatileCallSite volatile call site} may be used.
+ * <li>Otherwise, if a mutable target is required,
+ * a {@linkplain MutableCallSite mutable call site} may be used.
+ * </ul>
+ * <p>
+ * A non-constant call site may be <em>relinked</em> by changing its target.
+ * The new target must have the same {@linkplain MethodHandle#type() type}
+ * as the previous target.
+ * Thus, though a call site can be relinked to a series of
+ * successive targets, it cannot change its type.
+ * <p>
+ * Here is a sample use of call sites and bootstrap methods which links every
+ * dynamic call site to print its arguments:
+<blockquote><pre>{@code
+static void test() throws Throwable {
+ // THE FOLLOWING LINE IS PSEUDOCODE FOR A JVM INSTRUCTION
+ InvokeDynamic[#bootstrapDynamic].baz("baz arg", 2, 3.14);
+}
+private static void printArgs(Object... args) {
+ System.out.println(java.util.Arrays.deepToString(args));
+}
+private static final MethodHandle printArgs;
+static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ Class thisClass = lookup.lookupClass(); // (who am I?)
+ printArgs = lookup.findStatic(thisClass,
+ "printArgs", MethodType.methodType(void.class, Object[].class));
+}
+private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
+ // ignore caller and name, but match the type:
+ return new ConstantCallSite(printArgs.asType(type));
+}
+}</pre></blockquote>
+ * @author John Rose, JSR 292 EG
+ */
abstract
public class CallSite {
+ // Android-changed: not used.
+ // static { MethodHandleImpl.initStatics(); }
+
+ // The actual payload of this call site:
+ /*package-private*/
+ MethodHandle target; // Note: This field is known to the JVM. Do not change.
+
+ /**
+ * Make a blank call site object with the given method type.
+ * An initial target method is supplied which will throw
+ * an {@link IllegalStateException} if called.
+ * <p>
+ * Before this {@code CallSite} object is returned from a bootstrap method,
+ * it is usually provided with a more useful target method,
+ * via a call to {@link CallSite#setTarget(MethodHandle) setTarget}.
+ * @throws NullPointerException if the proposed type is null
+ */
+ /*package-private*/
+ CallSite(MethodType type) {
+ // Android-changed: No cache for these so create uninitializedCallSite target here using
+ // method handle transformations to create a method handle that has the expected method
+ // type but throws an IllegalStateException.
+ // target = makeUninitializedCallSite(type);
+ this.target = MethodHandles.throwException(type.returnType(), IllegalStateException.class);
+ this.target = MethodHandles.insertArguments(
+ this.target, 0, new IllegalStateException("uninitialized call site"));
+ if (type.parameterCount() > 0) {
+ this.target = MethodHandles.dropArguments(this.target, 0, type.ptypes());
+ }
+
+ // Android-changed: Using initializer method for GET_TARGET
+ // rather than complex static initializer.
+ initializeGetTarget();
+ }
+
+ /**
+ * Make a call site object equipped with an initial target method handle.
+ * @param target the method handle which will be the initial target of the call site
+ * @throws NullPointerException if the proposed target is null
+ */
+ /*package-private*/
+ CallSite(MethodHandle target) {
+ target.type(); // null check
+ this.target = target;
+
+ // Android-changed: Using initializer method for GET_TARGET
+ // rather than complex static initializer.
+ initializeGetTarget();
+ }
- public MethodType type() { return null; }
+ /**
+ * Make a call site object equipped with an initial target method handle.
+ * @param targetType the desired type of the call site
+ * @param createTargetHook a hook which will bind the call site to the target method handle
+ * @throws WrongMethodTypeException if the hook cannot be invoked on the required arguments,
+ * or if the target returned by the hook is not of the given {@code targetType}
+ * @throws NullPointerException if the hook returns a null value
+ * @throws ClassCastException if the hook returns something other than a {@code MethodHandle}
+ * @throws Throwable anything else thrown by the hook function
+ */
+ /*package-private*/
+ CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable {
+ this(targetType);
+ ConstantCallSite selfCCS = (ConstantCallSite) this;
+ MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS);
+ checkTargetChange(this.target, boundTarget);
+ this.target = boundTarget;
+ // Android-changed: Using initializer method for GET_TARGET
+ // rather than complex static initializer.
+ initializeGetTarget();
+ }
+
+ /**
+ * Returns the type of this call site's target.
+ * Although targets may change, any call site's type is permanent, and can never change to an unequal type.
+ * The {@code setTarget} method enforces this invariant by refusing any new target that does
+ * not have the previous target's type.
+ * @return the type of the current target, which is also the type of any future target
+ */
+ public MethodType type() {
+ // warning: do not call getTarget here, because CCS.getTarget can throw IllegalStateException
+ return target.type();
+ }
+
+ /**
+ * Returns the target method of the call site, according to the
+ * behavior defined by this call site's specific class.
+ * The immediate subclasses of {@code CallSite} document the
+ * class-specific behaviors of this method.
+ *
+ * @return the current linkage state of the call site, its target method handle
+ * @see ConstantCallSite
+ * @see VolatileCallSite
+ * @see #setTarget
+ * @see ConstantCallSite#getTarget
+ * @see MutableCallSite#getTarget
+ * @see VolatileCallSite#getTarget
+ */
public abstract MethodHandle getTarget();
+ /**
+ * Updates the target method of this call site, according to the
+ * behavior defined by this call site's specific class.
+ * The immediate subclasses of {@code CallSite} document the
+ * class-specific behaviors of this method.
+ * <p>
+ * The type of the new target must be {@linkplain MethodType#equals equal to}
+ * the type of the old target.
+ *
+ * @param newTarget the new target
+ * @throws NullPointerException if the proposed new target is null
+ * @throws WrongMethodTypeException if the proposed new target
+ * has a method type that differs from the previous target
+ * @see CallSite#getTarget
+ * @see ConstantCallSite#setTarget
+ * @see MutableCallSite#setTarget
+ * @see VolatileCallSite#setTarget
+ */
public abstract void setTarget(MethodHandle newTarget);
+ void checkTargetChange(MethodHandle oldTarget, MethodHandle newTarget) {
+ MethodType oldType = oldTarget.type();
+ MethodType newType = newTarget.type(); // null check!
+ if (!newType.equals(oldType))
+ throw wrongTargetType(newTarget, oldType);
+ }
+
+ private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) {
+ return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type);
+ }
+
+ /**
+ * Produces a method handle equivalent to an invokedynamic instruction
+ * which has been linked to this call site.
+ * <p>
+ * This method is equivalent to the following code:
+ * <blockquote><pre>{@code
+ * MethodHandle getTarget, invoker, result;
+ * getTarget = MethodHandles.publicLookup().bind(this, "getTarget", MethodType.methodType(MethodHandle.class));
+ * invoker = MethodHandles.exactInvoker(this.type());
+ * result = MethodHandles.foldArguments(invoker, getTarget)
+ * }</pre></blockquote>
+ *
+ * @return a method handle which always invokes this call site's current target
+ */
public abstract MethodHandle dynamicInvoker();
+ /*non-public*/ MethodHandle makeDynamicInvoker() {
+ // Android-changed: Use bindTo() rather than bindArgumentL() (not implemented).
+ MethodHandle getTarget = GET_TARGET.bindTo(this);
+ MethodHandle invoker = MethodHandles.exactInvoker(this.type());
+ return MethodHandles.foldArguments(invoker, getTarget);
+ }
+
+ // Android-changed: no longer final. GET_TARGET assigned in initializeGetTarget().
+ private static MethodHandle GET_TARGET = null;
+
+ private void initializeGetTarget() {
+ // Android-changed: moved from static initializer for
+ // GET_TARGET to avoid issues with running early. Called from
+ // constructors. CallSite creation is not performance critical.
+ synchronized (CallSite.class) {
+ if (GET_TARGET == null) {
+ try {
+ GET_TARGET = IMPL_LOOKUP.
+ findVirtual(CallSite.class, "getTarget",
+ MethodType.methodType(MethodHandle.class));
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+ }
+ }
+
+ // Android-changed: not used.
+ // /** This guy is rolled into the default target if a MethodType is supplied to the constructor. */
+ // /*package-private*/
+ // static Empty uninitializedCallSite() {
+ // throw new IllegalStateException("uninitialized call site");
+ // }
+
+ // unsafe stuff:
+ private static final long TARGET_OFFSET;
+ static {
+ try {
+ TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target"));
+ } catch (Exception ex) { throw new Error(ex); }
+ }
+
+ /*package-private*/
+ void setTargetNormal(MethodHandle newTarget) {
+ // Android-changed: Set value directly.
+ // MethodHandleNatives.setCallSiteTargetNormal(this, newTarget);
+ target = newTarget;
+ }
+ /*package-private*/
+ MethodHandle getTargetVolatile() {
+ return (MethodHandle) UNSAFE.getObjectVolatile(this, TARGET_OFFSET);
+ }
+ /*package-private*/
+ void setTargetVolatile(MethodHandle newTarget) {
+ // Android-changed: Set value directly.
+ // MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget);
+ UNSAFE.putObjectVolatile(this, TARGET_OFFSET, newTarget);
+ }
+
+ // Android-changed: not used.
+ // this implements the upcall from the JVM, MethodHandleNatives.makeDynamicCallSite:
+ // static CallSite makeSite(MethodHandle bootstrapMethod,
+ // // Callee information:
+ // String name, MethodType type,
+ // // Extra arguments for BSM, if any:
+ // Object info,
+ // // Caller information:
+ // Class<?> callerClass) {
+ // MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
+ // CallSite site;
+ // try {
+ // Object binding;
+ // info = maybeReBox(info);
+ // if (info == null) {
+ // binding = bootstrapMethod.invoke(caller, name, type);
+ // } else if (!info.getClass().isArray()) {
+ // binding = bootstrapMethod.invoke(caller, name, type, info);
+ // } else {
+ // Object[] argv = (Object[]) info;
+ // maybeReBoxElements(argv);
+ // switch (argv.length) {
+ // case 0:
+ // binding = bootstrapMethod.invoke(caller, name, type);
+ // break;
+ // case 1:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0]);
+ // break;
+ // case 2:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1]);
+ // break;
+ // case 3:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2]);
+ // break;
+ // case 4:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2], argv[3]);
+ // break;
+ // case 5:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2], argv[3], argv[4]);
+ // break;
+ // case 6:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
+ // break;
+ // default:
+ // final int NON_SPREAD_ARG_COUNT = 3; // (caller, name, type)
+ // if (NON_SPREAD_ARG_COUNT + argv.length > MethodType.MAX_MH_ARITY)
+ // throw new BootstrapMethodError("too many bootstrap method arguments");
+ // MethodType bsmType = bootstrapMethod.type();
+ // MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
+ // MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
+ // MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
+ // binding = spreader.invokeExact(typedBSM, (Object)caller, (Object)name, (Object)type, argv);
+ // }
+ // }
+ // //System.out.println("BSM for "+name+type+" => "+binding);
+ // if (binding instanceof CallSite) {
+ // site = (CallSite) binding;
+ // } else {
+ // throw new ClassCastException("bootstrap method failed to produce a CallSite");
+ // }
+ // if (!site.getTarget().type().equals(type))
+ // throw wrongTargetType(site.getTarget(), type);
+ // } catch (Throwable ex) {
+ // BootstrapMethodError bex;
+ // if (ex instanceof BootstrapMethodError)
+ // bex = (BootstrapMethodError) ex;
+ // else
+ // bex = new BootstrapMethodError("call site initialization exception", ex);
+ // throw bex;
+ // }
+ // return site;
+ // }
+
+ // private static Object maybeReBox(Object x) {
+ // if (x instanceof Integer) {
+ // int xi = (int) x;
+ // if (xi == (byte) xi)
+ // x = xi; // must rebox; see JLS 5.1.7
+ // }
+ // return x;
+ // }
+ // private static void maybeReBoxElements(Object[] xa) {
+ // for (int i = 0; i < xa.length; i++) {
+ // xa[i] = maybeReBox(xa[i]);
+ // }
+ // }
}
diff --git a/java/lang/invoke/MethodHandle.java b/java/lang/invoke/MethodHandle.java
index 159f9dd7..af3db103 100644
--- a/java/lang/invoke/MethodHandle.java
+++ b/java/lang/invoke/MethodHandle.java
@@ -25,28 +25,1353 @@
package java.lang.invoke;
+
+import dalvik.system.EmulatedStackFrame;
+
+import static java.lang.invoke.MethodHandleStatics.*;
+
+/**
+ * A method handle is a typed, directly executable reference to an underlying method,
+ * constructor, field, or similar low-level operation, with optional
+ * transformations of arguments or return values.
+ * These transformations are quite general, and include such patterns as
+ * {@linkplain #asType conversion},
+ * {@linkplain #bindTo insertion},
+ * {@linkplain java.lang.invoke.MethodHandles#dropArguments deletion},
+ * and {@linkplain java.lang.invoke.MethodHandles#filterArguments substitution}.
+ *
+ * <h1>Method handle contents</h1>
+ * Method handles are dynamically and strongly typed according to their parameter and return types.
+ * They are not distinguished by the name or the defining class of their underlying methods.
+ * A method handle must be invoked using a symbolic type descriptor which matches
+ * the method handle's own {@linkplain #type type descriptor}.
+ * <p>
+ * Every method handle reports its type descriptor via the {@link #type type} accessor.
+ * This type descriptor is a {@link java.lang.invoke.MethodType MethodType} object,
+ * whose structure is a series of classes, one of which is
+ * the return type of the method (or {@code void.class} if none).
+ * <p>
+ * A method handle's type controls the types of invocations it accepts,
+ * and the kinds of transformations that apply to it.
+ * <p>
+ * A method handle contains a pair of special invoker methods
+ * called {@link #invokeExact invokeExact} and {@link #invoke invoke}.
+ * Both invoker methods provide direct access to the method handle's
+ * underlying method, constructor, field, or other operation,
+ * as modified by transformations of arguments and return values.
+ * Both invokers accept calls which exactly match the method handle's own type.
+ * The plain, inexact invoker also accepts a range of other call types.
+ * <p>
+ * Method handles are immutable and have no visible state.
+ * Of course, they can be bound to underlying methods or data which exhibit state.
+ * With respect to the Java Memory Model, any method handle will behave
+ * as if all of its (internal) fields are final variables. This means that any method
+ * handle made visible to the application will always be fully formed.
+ * This is true even if the method handle is published through a shared
+ * variable in a data race.
+ * <p>
+ * Method handles cannot be subclassed by the user.
+ * Implementations may (or may not) create internal subclasses of {@code MethodHandle}
+ * which may be visible via the {@link java.lang.Object#getClass Object.getClass}
+ * operation. The programmer should not draw conclusions about a method handle
+ * from its specific class, as the method handle class hierarchy (if any)
+ * may change from time to time or across implementations from different vendors.
+ *
+ * <h1>Method handle compilation</h1>
+ * A Java method call expression naming {@code invokeExact} or {@code invoke}
+ * can invoke a method handle from Java source code.
+ * From the viewpoint of source code, these methods can take any arguments
+ * and their result can be cast to any return type.
+ * Formally this is accomplished by giving the invoker methods
+ * {@code Object} return types and variable arity {@code Object} arguments,
+ * but they have an additional quality called <em>signature polymorphism</em>
+ * which connects this freedom of invocation directly to the JVM execution stack.
+ * <p>
+ * As is usual with virtual methods, source-level calls to {@code invokeExact}
+ * and {@code invoke} compile to an {@code invokevirtual} instruction.
+ * More unusually, the compiler must record the actual argument types,
+ * and may not perform method invocation conversions on the arguments.
+ * Instead, it must push them on the stack according to their own unconverted types.
+ * The method handle object itself is pushed on the stack before the arguments.
+ * The compiler then calls the method handle with a symbolic type descriptor which
+ * describes the argument and return types.
+ * <p>
+ * To issue a complete symbolic type descriptor, the compiler must also determine
+ * the return type. This is based on a cast on the method invocation expression,
+ * if there is one, or else {@code Object} if the invocation is an expression
+ * or else {@code void} if the invocation is a statement.
+ * The cast may be to a primitive type (but not {@code void}).
+ * <p>
+ * As a corner case, an uncasted {@code null} argument is given
+ * a symbolic type descriptor of {@code java.lang.Void}.
+ * The ambiguity with the type {@code Void} is harmless, since there are no references of type
+ * {@code Void} except the null reference.
+ *
+ * <h1>Method handle invocation</h1>
+ * The first time a {@code invokevirtual} instruction is executed
+ * it is linked, by symbolically resolving the names in the instruction
+ * and verifying that the method call is statically legal.
+ * This is true of calls to {@code invokeExact} and {@code invoke}.
+ * In this case, the symbolic type descriptor emitted by the compiler is checked for
+ * correct syntax and names it contains are resolved.
+ * Thus, an {@code invokevirtual} instruction which invokes
+ * a method handle will always link, as long
+ * as the symbolic type descriptor is syntactically well-formed
+ * and the types exist.
+ * <p>
+ * When the {@code invokevirtual} is executed after linking,
+ * the receiving method handle's type is first checked by the JVM
+ * to ensure that it matches the symbolic type descriptor.
+ * If the type match fails, it means that the method which the
+ * caller is invoking is not present on the individual
+ * method handle being invoked.
+ * <p>
+ * In the case of {@code invokeExact}, the type descriptor of the invocation
+ * (after resolving symbolic type names) must exactly match the method type
+ * of the receiving method handle.
+ * In the case of plain, inexact {@code invoke}, the resolved type descriptor
+ * must be a valid argument to the receiver's {@link #asType asType} method.
+ * Thus, plain {@code invoke} is more permissive than {@code invokeExact}.
+ * <p>
+ * After type matching, a call to {@code invokeExact} directly
+ * and immediately invoke the method handle's underlying method
+ * (or other behavior, as the case may be).
+ * <p>
+ * A call to plain {@code invoke} works the same as a call to
+ * {@code invokeExact}, if the symbolic type descriptor specified by the caller
+ * exactly matches the method handle's own type.
+ * If there is a type mismatch, {@code invoke} attempts
+ * to adjust the type of the receiving method handle,
+ * as if by a call to {@link #asType asType},
+ * to obtain an exactly invokable method handle {@code M2}.
+ * This allows a more powerful negotiation of method type
+ * between caller and callee.
+ * <p>
+ * (<em>Note:</em> The adjusted method handle {@code M2} is not directly observable,
+ * and implementations are therefore not required to materialize it.)
+ *
+ * <h1>Invocation checking</h1>
+ * In typical programs, method handle type matching will usually succeed.
+ * But if a match fails, the JVM will throw a {@link WrongMethodTypeException},
+ * either directly (in the case of {@code invokeExact}) or indirectly as if
+ * by a failed call to {@code asType} (in the case of {@code invoke}).
+ * <p>
+ * Thus, a method type mismatch which might show up as a linkage error
+ * in a statically typed program can show up as
+ * a dynamic {@code WrongMethodTypeException}
+ * in a program which uses method handles.
+ * <p>
+ * Because method types contain "live" {@code Class} objects,
+ * method type matching takes into account both types names and class loaders.
+ * Thus, even if a method handle {@code M} is created in one
+ * class loader {@code L1} and used in another {@code L2},
+ * method handle calls are type-safe, because the caller's symbolic type
+ * descriptor, as resolved in {@code L2},
+ * is matched against the original callee method's symbolic type descriptor,
+ * as resolved in {@code L1}.
+ * The resolution in {@code L1} happens when {@code M} is created
+ * and its type is assigned, while the resolution in {@code L2} happens
+ * when the {@code invokevirtual} instruction is linked.
+ * <p>
+ * Apart from the checking of type descriptors,
+ * a method handle's capability to call its underlying method is unrestricted.
+ * If a method handle is formed on a non-public method by a class
+ * that has access to that method, the resulting handle can be used
+ * in any place by any caller who receives a reference to it.
+ * <p>
+ * Unlike with the Core Reflection API, where access is checked every time
+ * a reflective method is invoked,
+ * method handle access checking is performed
+ * <a href="MethodHandles.Lookup.html#access">when the method handle is created</a>.
+ * In the case of {@code ldc} (see below), access checking is performed as part of linking
+ * the constant pool entry underlying the constant method handle.
+ * <p>
+ * Thus, handles to non-public methods, or to methods in non-public classes,
+ * should generally be kept secret.
+ * They should not be passed to untrusted code unless their use from
+ * the untrusted code would be harmless.
+ *
+ * <h1>Method handle creation</h1>
+ * Java code can create a method handle that directly accesses
+ * any method, constructor, or field that is accessible to that code.
+ * This is done via a reflective, capability-based API called
+ * {@link java.lang.invoke.MethodHandles.Lookup MethodHandles.Lookup}
+ * For example, a static method handle can be obtained
+ * from {@link java.lang.invoke.MethodHandles.Lookup#findStatic Lookup.findStatic}.
+ * There are also conversion methods from Core Reflection API objects,
+ * such as {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
+ * <p>
+ * Like classes and strings, method handles that correspond to accessible
+ * fields, methods, and constructors can also be represented directly
+ * in a class file's constant pool as constants to be loaded by {@code ldc} bytecodes.
+ * A new type of constant pool entry, {@code CONSTANT_MethodHandle},
+ * refers directly to an associated {@code CONSTANT_Methodref},
+ * {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref}
+ * constant pool entry.
+ * (For full details on method handle constants,
+ * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
+ * <p>
+ * Method handles produced by lookups or constant loads from methods or
+ * constructors with the variable arity modifier bit ({@code 0x0080})
+ * have a corresponding variable arity, as if they were defined with
+ * the help of {@link #asVarargsCollector asVarargsCollector}.
+ * <p>
+ * A method reference may refer either to a static or non-static method.
+ * In the non-static case, the method handle type includes an explicit
+ * receiver argument, prepended before any other arguments.
+ * In the method handle's type, the initial receiver argument is typed
+ * according to the class under which the method was initially requested.
+ * (E.g., if a non-static method handle is obtained via {@code ldc},
+ * the type of the receiver is the class named in the constant pool entry.)
+ * <p>
+ * Method handle constants are subject to the same link-time access checks
+ * their corresponding bytecode instructions, and the {@code ldc} instruction
+ * will throw corresponding linkage errors if the bytecode behaviors would
+ * throw such errors.
+ * <p>
+ * As a corollary of this, access to protected members is restricted
+ * to receivers only of the accessing class, or one of its subclasses,
+ * and the accessing class must in turn be a subclass (or package sibling)
+ * of the protected member's defining class.
+ * If a method reference refers to a protected non-static method or field
+ * of a class outside the current package, the receiver argument will
+ * be narrowed to the type of the accessing class.
+ * <p>
+ * When a method handle to a virtual method is invoked, the method is
+ * always looked up in the receiver (that is, the first argument).
+ * <p>
+ * A non-virtual method handle to a specific virtual method implementation
+ * can also be created. These do not perform virtual lookup based on
+ * receiver type. Such a method handle simulates the effect of
+ * an {@code invokespecial} instruction to the same method.
+ *
+ * <h1>Usage examples</h1>
+ * Here are some examples of usage:
+ * <blockquote><pre>{@code
+Object x, y; String s; int i;
+MethodType mt; MethodHandle mh;
+MethodHandles.Lookup lookup = MethodHandles.lookup();
+// mt is (char,char)String
+mt = MethodType.methodType(String.class, char.class, char.class);
+mh = lookup.findVirtual(String.class, "replace", mt);
+s = (String) mh.invokeExact("daddy",'d','n');
+// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
+assertEquals(s, "nanny");
+// weakly typed invocation (using MHs.invoke)
+s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
+assertEquals(s, "savvy");
+// mt is (Object[])List
+mt = MethodType.methodType(java.util.List.class, Object[].class);
+mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
+assert(mh.isVarargsCollector());
+x = mh.invoke("one", "two");
+// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList("one","two"));
+// mt is (Object,Object,Object)Object
+mt = MethodType.genericMethodType(3);
+mh = mh.asType(mt);
+x = mh.invokeExact((Object)1, (Object)2, (Object)3);
+// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList(1,2,3));
+// mt is ()int
+mt = MethodType.methodType(int.class);
+mh = lookup.findVirtual(java.util.List.class, "size", mt);
+i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
+// invokeExact(Ljava/util/List;)I
+assert(i == 3);
+mt = MethodType.methodType(void.class, String.class);
+mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
+mh.invokeExact(System.out, "Hello, world.");
+// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
+ * }</pre></blockquote>
+ * Each of the above calls to {@code invokeExact} or plain {@code invoke}
+ * generates a single invokevirtual instruction with
+ * the symbolic type descriptor indicated in the following comment.
+ * In these examples, the helper method {@code assertEquals} is assumed to
+ * be a method which calls {@link java.util.Objects#equals(Object,Object) Objects.equals}
+ * on its arguments, and asserts that the result is true.
+ *
+ * <h1>Exceptions</h1>
+ * The methods {@code invokeExact} and {@code invoke} are declared
+ * to throw {@link java.lang.Throwable Throwable},
+ * which is to say that there is no static restriction on what a method handle
+ * can throw. Since the JVM does not distinguish between checked
+ * and unchecked exceptions (other than by their class, of course),
+ * there is no particular effect on bytecode shape from ascribing
+ * checked exceptions to method handle invocations. But in Java source
+ * code, methods which perform method handle calls must either explicitly
+ * throw {@code Throwable}, or else must catch all
+ * throwables locally, rethrowing only those which are legal in the context,
+ * and wrapping ones which are illegal.
+ *
+ * <h1><a name="sigpoly"></a>Signature polymorphism</h1>
+ * The unusual compilation and linkage behavior of
+ * {@code invokeExact} and plain {@code invoke}
+ * is referenced by the term <em>signature polymorphism</em>.
+ * As defined in the Java Language Specification,
+ * a signature polymorphic method is one which can operate with
+ * any of a wide range of call signatures and return types.
+ * <p>
+ * In source code, a call to a signature polymorphic method will
+ * compile, regardless of the requested symbolic type descriptor.
+ * As usual, the Java compiler emits an {@code invokevirtual}
+ * instruction with the given symbolic type descriptor against the named method.
+ * The unusual part is that the symbolic type descriptor is derived from
+ * the actual argument and return types, not from the method declaration.
+ * <p>
+ * When the JVM processes bytecode containing signature polymorphic calls,
+ * it will successfully link any such call, regardless of its symbolic type descriptor.
+ * (In order to retain type safety, the JVM will guard such calls with suitable
+ * dynamic type checks, as described elsewhere.)
+ * <p>
+ * Bytecode generators, including the compiler back end, are required to emit
+ * untransformed symbolic type descriptors for these methods.
+ * Tools which determine symbolic linkage are required to accept such
+ * untransformed descriptors, without reporting linkage errors.
+ *
+ * <h1>Interoperation between method handles and the Core Reflection API</h1>
+ * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup Lookup} API,
+ * any class member represented by a Core Reflection API object
+ * can be converted to a behaviorally equivalent method handle.
+ * For example, a reflective {@link java.lang.reflect.Method Method} can
+ * be converted to a method handle using
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
+ * The resulting method handles generally provide more direct and efficient
+ * access to the underlying class members.
+ * <p>
+ * As a special case,
+ * when the Core Reflection API is used to view the signature polymorphic
+ * methods {@code invokeExact} or plain {@code invoke} in this class,
+ * they appear as ordinary non-polymorphic methods.
+ * Their reflective appearance, as viewed by
+ * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod},
+ * is unaffected by their special status in this API.
+ * For example, {@link java.lang.reflect.Method#getModifiers Method.getModifiers}
+ * will report exactly those modifier bits required for any similarly
+ * declared method, including in this case {@code native} and {@code varargs} bits.
+ * <p>
+ * As with any reflected method, these methods (when reflected) may be
+ * invoked via {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.
+ * However, such reflective calls do not result in method handle invocations.
+ * Such a call, if passed the required argument
+ * (a single one, of type {@code Object[]}), will ignore the argument and
+ * will throw an {@code UnsupportedOperationException}.
+ * <p>
+ * Since {@code invokevirtual} instructions can natively
+ * invoke method handles under any symbolic type descriptor, this reflective view conflicts
+ * with the normal presentation of these methods via bytecodes.
+ * Thus, these two native methods, when reflectively viewed by
+ * {@code Class.getDeclaredMethod}, may be regarded as placeholders only.
+ * <p>
+ * In order to obtain an invoker method for a particular type descriptor,
+ * use {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker},
+ * or {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}.
+ * The {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual}
+ * API is also able to return a method handle
+ * to call {@code invokeExact} or plain {@code invoke},
+ * for any specified type descriptor .
+ *
+ * <h1>Interoperation between method handles and Java generics</h1>
+ * A method handle can be obtained on a method, constructor, or field
+ * which is declared with Java generic types.
+ * As with the Core Reflection API, the type of the method handle
+ * will constructed from the erasure of the source-level type.
+ * When a method handle is invoked, the types of its arguments
+ * or the return value cast type may be generic types or type instances.
+ * If this occurs, the compiler will replace those
+ * types by their erasures when it constructs the symbolic type descriptor
+ * for the {@code invokevirtual} instruction.
+ * <p>
+ * Method handles do not represent
+ * their function-like types in terms of Java parameterized (generic) types,
+ * because there are three mismatches between function-like types and parameterized
+ * Java types.
+ * <ul>
+ * <li>Method types range over all possible arities,
+ * from no arguments to up to the <a href="MethodHandle.html#maxarity">maximum number</a> of allowed arguments.
+ * Generics are not variadic, and so cannot represent this.</li>
+ * <li>Method types can specify arguments of primitive types,
+ * which Java generic types cannot range over.</li>
+ * <li>Higher order functions over method handles (combinators) are
+ * often generic across a wide range of function types, including
+ * those of multiple arities. It is impossible to represent such
+ * genericity with a Java type parameter.</li>
+ * </ul>
+ *
+ * <h1><a name="maxarity"></a>Arity limits</h1>
+ * The JVM imposes on all methods and constructors of any kind an absolute
+ * limit of 255 stacked arguments. This limit can appear more restrictive
+ * in certain cases:
+ * <ul>
+ * <li>A {@code long} or {@code double} argument counts (for purposes of arity limits) as two argument slots.
+ * <li>A non-static method consumes an extra argument for the object on which the method is called.
+ * <li>A constructor consumes an extra argument for the object which is being constructed.
+ * <li>Since a method handle&rsquo;s {@code invoke} method (or other signature-polymorphic method) is non-virtual,
+ * it consumes an extra argument for the method handle itself, in addition to any non-virtual receiver object.
+ * </ul>
+ * These limits imply that certain method handles cannot be created, solely because of the JVM limit on stacked arguments.
+ * For example, if a static JVM method accepts exactly 255 arguments, a method handle cannot be created for it.
+ * Attempts to create method handles with impossible method types lead to an {@link IllegalArgumentException}.
+ * In particular, a method handle&rsquo;s type must not have an arity of the exact maximum 255.
+ *
+ * @see MethodType
+ * @see MethodHandles
+ * @author John Rose, JSR 292 EG
+ */
public abstract class MethodHandle {
+ // Android-changed:
+ //
+ // static { MethodHandleImpl.initStatics(); }
+ //
+ // LambdaForm and customizationCount are currently unused in our implementation
+ // and will be substituted with appropriate implementation / delegate classes.
+ //
+ // /*private*/ final LambdaForm form;
+ // form is not private so that invokers can easily fetch it
+ // /*non-public*/ byte customizationCount;
+ // customizationCount should be accessible from invokers
+
+
+ /**
+ * Internal marker interface which distinguishes (to the Java compiler)
+ * those methods which are <a href="MethodHandle.html#sigpoly">signature polymorphic</a>.
+ *
+ * @hide
+ */
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+ public @interface PolymorphicSignature { }
+
+ /**
+ * The type of this method handle, this corresponds to the exact type of the method
+ * being invoked.
+ */
+ private final MethodType type;
+
+ /**
+ * The nominal type of this method handle, will be non-null if a method handle declares
+ * a different type from its "real" type, which is either the type of the method being invoked
+ * or the type of the emulated stackframe expected by an underyling adapter.
+ */
+ private MethodType nominalType;
+
+ /**
+ * The spread invoker associated with this type with zero trailing arguments.
+ * This is used to speed up invokeWithArguments.
+ */
+ private MethodHandle cachedSpreadInvoker;
+
+ /**
+ * The INVOKE* constants and SGET/SPUT and IGET/IPUT constants specify the behaviour of this
+ * method handle with respect to the ArtField* or the ArtMethod* that it operates on. These
+ * behaviours are equivalent to the dex bytecode behaviour on the respective method_id or
+ * field_id in the equivalent instruction.
+ *
+ * INVOKE_TRANSFORM is a special type of handle which doesn't encode any dex bytecode behaviour,
+ * instead it transforms the list of input arguments or performs other higher order operations
+ * before (optionally) delegating to another method handle.
+ *
+ * INVOKE_CALLSITE_TRANSFORM is a variation on INVOKE_TRANSFORM where the method type of
+ * a MethodHandle dynamically varies based on the callsite. This is used by
+ * the VarargsCollector implementation which places any number of trailing arguments
+ * into an array before invoking an arity method. The "any number of trailing arguments" means
+ * it would otherwise generate WrongMethodTypeExceptions as the callsite method type and
+ * VarargsCollector method type appear incompatible.
+ */
+
+ /** @hide */ public static final int INVOKE_VIRTUAL = 0;
+ /** @hide */ public static final int INVOKE_SUPER = 1;
+ /** @hide */ public static final int INVOKE_DIRECT = 2;
+ /** @hide */ public static final int INVOKE_STATIC = 3;
+ /** @hide */ public static final int INVOKE_INTERFACE = 4;
+ /** @hide */ public static final int INVOKE_TRANSFORM = 5;
+ /** @hide */ public static final int INVOKE_CALLSITE_TRANSFORM = 6;
+ /** @hide */ public static final int IGET = 7;
+ /** @hide */ public static final int IPUT = 8;
+ /** @hide */ public static final int SGET = 9;
+ /** @hide */ public static final int SPUT = 10;
+
+ // The kind of this method handle (used by the runtime). This is one of the INVOKE_*
+ // constants or SGET/SPUT, IGET/IPUT.
+ /** @hide */ protected final int handleKind;
+
+ // The ArtMethod* or ArtField* associated with this method handle (used by the runtime).
+ /** @hide */ protected final long artFieldOrMethod;
+
+ /** @hide */
+ protected MethodHandle(long artFieldOrMethod, int handleKind, MethodType type) {
+ this.artFieldOrMethod = artFieldOrMethod;
+ this.handleKind = handleKind;
+ this.type = type;
+ }
+
+ /**
+ * Reports the type of this method handle.
+ * Every invocation of this method handle via {@code invokeExact} must exactly match this type.
+ * @return the method handle type
+ */
+ public MethodType type() {
+ if (nominalType != null) {
+ return nominalType;
+ }
+
+ return type;
+ }
+
+ /**
+ * Invokes the method handle, allowing any caller type descriptor, but requiring an exact type match.
+ * The symbolic type descriptor at the call site of {@code invokeExact} must
+ * exactly match this method handle's {@link #type type}.
+ * No conversions are allowed on arguments or return values.
+ * <p>
+ * When this method is observed via the Core Reflection API,
+ * it will appear as a single native method, taking an object array and returning an object.
+ * If this native method is invoked directly via
+ * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
+ * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
+ * it will throw an {@code UnsupportedOperationException}.
+ * @param args the signature-polymorphic parameter list, statically represented using varargs
+ * @return the signature-polymorphic result, statically represented using {@code Object}
+ * @throws WrongMethodTypeException if the target's type is not identical with the caller's symbolic type descriptor
+ * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
+ */
+ public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;
+
+ /**
+ * Invokes the method handle, allowing any caller type descriptor,
+ * and optionally performing conversions on arguments and return values.
+ * <p>
+ * If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type},
+ * the call proceeds as if by {@link #invokeExact invokeExact}.
+ * <p>
+ * Otherwise, the call proceeds as if this method handle were first
+ * adjusted by calling {@link #asType asType} to adjust this method handle
+ * to the required type, and then the call proceeds as if by
+ * {@link #invokeExact invokeExact} on the adjusted method handle.
+ * <p>
+ * There is no guarantee that the {@code asType} call is actually made.
+ * If the JVM can predict the results of making the call, it may perform
+ * adaptations directly on the caller's arguments,
+ * and call the target method handle according to its own exact type.
+ * <p>
+ * The resolved type descriptor at the call site of {@code invoke} must
+ * be a valid argument to the receivers {@code asType} method.
+ * In particular, the caller must specify the same argument arity
+ * as the callee's type,
+ * if the callee is not a {@linkplain #asVarargsCollector variable arity collector}.
+ * <p>
+ * When this method is observed via the Core Reflection API,
+ * it will appear as a single native method, taking an object array and returning an object.
+ * If this native method is invoked directly via
+ * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
+ * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
+ * it will throw an {@code UnsupportedOperationException}.
+ * @param args the signature-polymorphic parameter list, statically represented using varargs
+ * @return the signature-polymorphic result, statically represented using {@code Object}
+ * @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor
+ * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails
+ * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
+ */
+ public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
+
+ // Android-changed: Removed implementation details.
+ //
+ // /*non-public*/ final native @PolymorphicSignature Object invokeBasic(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToVirtual(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToStatic(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToSpecial(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args)
+
+ /**
+ * Performs a variable arity invocation, passing the arguments in the given list
+ * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
+ * which mentions only the type {@code Object}, and whose arity is the length
+ * of the argument list.
+ * <p>
+ * Specifically, execution proceeds as if by the following steps,
+ * although the methods are not guaranteed to be called if the JVM
+ * can predict their effects.
+ * <ul>
+ * <li>Determine the length of the argument array as {@code N}.
+ * For a null reference, {@code N=0}. </li>
+ * <li>Determine the general type {@code TN} of {@code N} arguments as
+ * as {@code TN=MethodType.genericMethodType(N)}.</li>
+ * <li>Force the original target method handle {@code MH0} to the
+ * required type, as {@code MH1 = MH0.asType(TN)}. </li>
+ * <li>Spread the array into {@code N} separate arguments {@code A0, ...}. </li>
+ * <li>Invoke the type-adjusted method handle on the unpacked arguments:
+ * MH1.invokeExact(A0, ...). </li>
+ * <li>Take the return value as an {@code Object} reference. </li>
+ * </ul>
+ * <p>
+ * Because of the action of the {@code asType} step, the following argument
+ * conversions are applied as necessary:
+ * <ul>
+ * <li>reference casting
+ * <li>unboxing
+ * <li>widening primitive conversions
+ * </ul>
+ * <p>
+ * The result returned by the call is boxed if it is a primitive,
+ * or forced to null if the return type is void.
+ * <p>
+ * This call is equivalent to the following code:
+ * <blockquote><pre>{@code
+ * MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0);
+ * Object result = invoker.invokeExact(this, arguments);
+ * }</pre></blockquote>
+ * <p>
+ * Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke},
+ * {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI.
+ * It can therefore be used as a bridge between native or reflective code and method handles.
+ *
+ * @param arguments the arguments to pass to the target
+ * @return the result returned by the target
+ * @throws ClassCastException if an argument cannot be converted by reference casting
+ * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
+ * @throws Throwable anything thrown by the target method invocation
+ * @see MethodHandles#spreadInvoker
+ */
+ public Object invokeWithArguments(Object... arguments) throws Throwable {
+ MethodHandle invoker = null;
+ synchronized (this) {
+ if (cachedSpreadInvoker == null) {
+ cachedSpreadInvoker = MethodHandles.spreadInvoker(this.type(), 0);
+ }
+
+ invoker = cachedSpreadInvoker;
+ }
+
+ return invoker.invoke(this, arguments);
+ }
+
+ /**
+ * Performs a variable arity invocation, passing the arguments in the given array
+ * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
+ * which mentions only the type {@code Object}, and whose arity is the length
+ * of the argument array.
+ * <p>
+ * This method is also equivalent to the following code:
+ * <blockquote><pre>{@code
+ * invokeWithArguments(arguments.toArray()
+ * }</pre></blockquote>
+ *
+ * @param arguments the arguments to pass to the target
+ * @return the result returned by the target
+ * @throws NullPointerException if {@code arguments} is a null reference
+ * @throws ClassCastException if an argument cannot be converted by reference casting
+ * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
+ * @throws Throwable anything thrown by the target method invocation
+ */
+ public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable {
+ return invokeWithArguments(arguments.toArray());
+ }
+
+ /**
+ * Produces an adapter method handle which adapts the type of the
+ * current method handle to a new type.
+ * The resulting method handle is guaranteed to report a type
+ * which is equal to the desired new type.
+ * <p>
+ * If the original type and new type are equal, returns {@code this}.
+ * <p>
+ * The new method handle, when invoked, will perform the following
+ * steps:
+ * <ul>
+ * <li>Convert the incoming argument list to match the original
+ * method handle's argument list.
+ * <li>Invoke the original method handle on the converted argument list.
+ * <li>Convert any result returned by the original method handle
+ * to the return type of new method handle.
+ * </ul>
+ * <p>
+ * This method provides the crucial behavioral difference between
+ * {@link #invokeExact invokeExact} and plain, inexact {@link #invoke invoke}.
+ * The two methods
+ * perform the same steps when the caller's type descriptor exactly m atches
+ * the callee's, but when the types differ, plain {@link #invoke invoke}
+ * also calls {@code asType} (or some internal equivalent) in order
+ * to match up the caller's and callee's types.
+ * <p>
+ * If the current method is a variable arity method handle
+ * argument list conversion may involve the conversion and collection
+ * of several arguments into an array, as
+ * {@linkplain #asVarargsCollector described elsewhere}.
+ * In every other case, all conversions are applied <em>pairwise</em>,
+ * which means that each argument or return value is converted to
+ * exactly one argument or return value (or no return value).
+ * The applied conversions are defined by consulting the
+ * the corresponding component types of the old and new
+ * method handle types.
+ * <p>
+ * Let <em>T0</em> and <em>T1</em> be corresponding new and old parameter types,
+ * or old and new return types. Specifically, for some valid index {@code i}, let
+ * <em>T0</em>{@code =newType.parameterType(i)} and <em>T1</em>{@code =this.type().parameterType(i)}.
+ * Or else, going the other way for return values, let
+ * <em>T0</em>{@code =this.type().returnType()} and <em>T1</em>{@code =newType.returnType()}.
+ * If the types are the same, the new method handle makes no change
+ * to the corresponding argument or return value (if any).
+ * Otherwise, one of the following conversions is applied
+ * if possible:
+ * <ul>
+ * <li>If <em>T0</em> and <em>T1</em> are references, then a cast to <em>T1</em> is applied.
+ * (The types do not need to be related in any particular way.
+ * This is because a dynamic value of null can convert to any reference type.)
+ * <li>If <em>T0</em> and <em>T1</em> are primitives, then a Java method invocation
+ * conversion (JLS 5.3) is applied, if one exists.
+ * (Specifically, <em>T0</em> must convert to <em>T1</em> by a widening primitive conversion.)
+ * <li>If <em>T0</em> is a primitive and <em>T1</em> a reference,
+ * a Java casting conversion (JLS 5.5) is applied if one exists.
+ * (Specifically, the value is boxed from <em>T0</em> to its wrapper class,
+ * which is then widened as needed to <em>T1</em>.)
+ * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
+ * conversion will be applied at runtime, possibly followed
+ * by a Java method invocation conversion (JLS 5.3)
+ * on the primitive value. (These are the primitive widening conversions.)
+ * <em>T0</em> must be a wrapper class or a supertype of one.
+ * (In the case where <em>T0</em> is Object, these are the conversions
+ * allowed by {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.)
+ * The unboxing conversion must have a possibility of success, which means that
+ * if <em>T0</em> is not itself a wrapper class, there must exist at least one
+ * wrapper class <em>TW</em> which is a subtype of <em>T0</em> and whose unboxed
+ * primitive value can be widened to <em>T1</em>.
+ * <li>If the return type <em>T1</em> is marked as void, any returned value is discarded
+ * <li>If the return type <em>T0</em> is void and <em>T1</em> a reference, a null value is introduced.
+ * <li>If the return type <em>T0</em> is void and <em>T1</em> a primitive,
+ * a zero value is introduced.
+ * </ul>
+ * (<em>Note:</em> Both <em>T0</em> and <em>T1</em> may be regarded as static types,
+ * because neither corresponds specifically to the <em>dynamic type</em> of any
+ * actual argument or return value.)
+ * <p>
+ * The method handle conversion cannot be made if any one of the required
+ * pairwise conversions cannot be made.
+ * <p>
+ * At runtime, the conversions applied to reference arguments
+ * or return values may require additional runtime checks which can fail.
+ * An unboxing operation may fail because the original reference is null,
+ * causing a {@link java.lang.NullPointerException NullPointerException}.
+ * An unboxing operation or a reference cast may also fail on a reference
+ * to an object of the wrong type,
+ * causing a {@link java.lang.ClassCastException ClassCastException}.
+ * Although an unboxing operation may accept several kinds of wrappers,
+ * if none are available, a {@code ClassCastException} will be thrown.
+ *
+ * @param newType the expected type of the new method handle
+ * @return a method handle which delegates to {@code this} after performing
+ * any necessary argument conversions, and arranges for any
+ * necessary return value conversions
+ * @throws NullPointerException if {@code newType} is a null reference
+ * @throws WrongMethodTypeException if the conversion cannot be made
+ * @see MethodHandles#explicitCastArguments
+ */
+ public MethodHandle asType(MethodType newType) {
+ // Fast path alternative to a heavyweight {@code asType} call.
+ // Return 'this' if the conversion will be a no-op.
+ if (newType == type) {
+ return this;
+ }
+
+ if (!type.isConvertibleTo(newType)) {
+ throw new WrongMethodTypeException("cannot convert " + this + " to " + newType);
+ }
+
+ MethodHandle mh = duplicate();
+ mh.nominalType = newType;
+ return mh;
+ }
+
+ /**
+ * Makes an <em>array-spreading</em> method handle, which accepts a trailing array argument
+ * and spreads its elements as positional arguments.
+ * The new method handle adapts, as its <i>target</i>,
+ * the current method handle. The type of the adapter will be
+ * the same as the type of the target, except that the final
+ * {@code arrayLength} parameters of the target's type are replaced
+ * by a single array parameter of type {@code arrayType}.
+ * <p>
+ * If the array element type differs from any of the corresponding
+ * argument types on the original target,
+ * the original target is adapted to take the array elements directly,
+ * as if by a call to {@link #asType asType}.
+ * <p>
+ * When called, the adapter replaces a trailing array argument
+ * by the array's elements, each as its own argument to the target.
+ * (The order of the arguments is preserved.)
+ * They are converted pairwise by casting and/or unboxing
+ * to the types of the trailing parameters of the target.
+ * Finally the target is called.
+ * What the target eventually returns is returned unchanged by the adapter.
+ * <p>
+ * Before calling the target, the adapter verifies that the array
+ * contains exactly enough elements to provide a correct argument count
+ * to the target method handle.
+ * (The array may also be null when zero elements are required.)
+ * <p>
+ * If, when the adapter is called, the supplied array argument does
+ * not have the correct number of elements, the adapter will throw
+ * an {@link IllegalArgumentException} instead of invoking the target.
+ * <p>
+ * Here are some simple examples of array-spreading method handles:
+ * <blockquote><pre>{@code
+MethodHandle equals = publicLookup()
+ .findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
+assert( (boolean) equals.invokeExact("me", (Object)"me"));
+assert(!(boolean) equals.invokeExact("me", (Object)"thee"));
+// spread both arguments from a 2-array:
+MethodHandle eq2 = equals.asSpreader(Object[].class, 2);
+assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" }));
+assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
+// try to spread from anything but a 2-array:
+for (int n = 0; n <= 10; n++) {
+ Object[] badArityArgs = (n == 2 ? null : new Object[n]);
+ try { assert((boolean) eq2.invokeExact(badArityArgs) && false); }
+ catch (IllegalArgumentException ex) { } // OK
+}
+// spread both arguments from a String array:
+MethodHandle eq2s = equals.asSpreader(String[].class, 2);
+assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" }));
+assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" }));
+// spread second arguments from a 1-array:
+MethodHandle eq1 = equals.asSpreader(Object[].class, 1);
+assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" }));
+assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" }));
+// spread no arguments from a 0-array or null:
+MethodHandle eq0 = equals.asSpreader(Object[].class, 0);
+assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0]));
+assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null));
+// asSpreader and asCollector are approximate inverses:
+for (int n = 0; n <= 2; n++) {
+ for (Class<?> a : new Class<?>[]{Object[].class, String[].class, CharSequence[].class}) {
+ MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n);
+ assert( (boolean) equals2.invokeWithArguments("me", "me"));
+ assert(!(boolean) equals2.invokeWithArguments("me", "thee"));
+ }
+}
+MethodHandle caToString = publicLookup()
+ .findStatic(Arrays.class, "toString", methodType(String.class, char[].class));
+assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray()));
+MethodHandle caString3 = caToString.asCollector(char[].class, 3);
+assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C'));
+MethodHandle caToString2 = caString3.asSpreader(char[].class, 2);
+assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
+ * }</pre></blockquote>
+ * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments
+ * @param arrayLength the number of arguments to spread from an incoming array argument
+ * @return a new method handle which spreads its final array argument,
+ * before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type,
+ * or if target does not have at least
+ * {@code arrayLength} parameter types,
+ * or if {@code arrayLength} is negative,
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ * @throws WrongMethodTypeException if the implied {@code asType} call fails
+ * @see #asCollector
+ */
+ public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) {
+ MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength);
+
+ final int targetParamCount = postSpreadType.parameterCount();
+ MethodType dropArrayArgs = postSpreadType.dropParameterTypes(
+ (targetParamCount - arrayLength), targetParamCount);
+ MethodType adapterType = dropArrayArgs.appendParameterTypes(arrayType);
+
+ return new Transformers.Spreader(this, adapterType, arrayLength);
+ }
+
+ /**
+ * See if {@code asSpreader} can be validly called with the given arguments.
+ * Return the type of the method handle call after spreading but before conversions.
+ */
+ private MethodType asSpreaderChecks(Class<?> arrayType, int arrayLength) {
+ spreadArrayChecks(arrayType, arrayLength);
+ int nargs = type().parameterCount();
+ if (nargs < arrayLength || arrayLength < 0)
+ throw newIllegalArgumentException("bad spread array length");
+ Class<?> arrayElement = arrayType.getComponentType();
+ MethodType mtype = type();
+ boolean match = true, fail = false;
+ for (int i = nargs - arrayLength; i < nargs; i++) {
+ Class<?> ptype = mtype.parameterType(i);
+ if (ptype != arrayElement) {
+ match = false;
+ if (!MethodType.canConvert(arrayElement, ptype)) {
+ fail = true;
+ break;
+ }
+ }
+ }
+ if (match) return mtype;
+ MethodType needType = mtype.asSpreaderType(arrayType, arrayLength);
+ if (!fail) return needType;
+ // elicit an error:
+ this.asType(needType);
+ throw newInternalError("should not return", null);
+ }
+
+ private void spreadArrayChecks(Class<?> arrayType, int arrayLength) {
+ Class<?> arrayElement = arrayType.getComponentType();
+ if (arrayElement == null)
+ throw newIllegalArgumentException("not an array type", arrayType);
+ if ((arrayLength & 0x7F) != arrayLength) {
+ if ((arrayLength & 0xFF) != arrayLength)
+ throw newIllegalArgumentException("array length is not legal", arrayLength);
+ assert(arrayLength >= 128);
+ if (arrayElement == long.class ||
+ arrayElement == double.class)
+ throw newIllegalArgumentException("array length is not legal for long[] or double[]", arrayLength);
+ }
+ }
+
+ /**
+ * Makes an <em>array-collecting</em> method handle, which accepts a given number of trailing
+ * positional arguments and collects them into an array argument.
+ * The new method handle adapts, as its <i>target</i>,
+ * the current method handle. The type of the adapter will be
+ * the same as the type of the target, except that a single trailing
+ * parameter (usually of type {@code arrayType}) is replaced by
+ * {@code arrayLength} parameters whose type is element type of {@code arrayType}.
+ * <p>
+ * If the array type differs from the final argument type on the original target,
+ * the original target is adapted to take the array type directly,
+ * as if by a call to {@link #asType asType}.
+ * <p>
+ * When called, the adapter replaces its trailing {@code arrayLength}
+ * arguments by a single new array of type {@code arrayType}, whose elements
+ * comprise (in order) the replaced arguments.
+ * Finally the target is called.
+ * What the target eventually returns is returned unchanged by the adapter.
+ * <p>
+ * (The array may also be a shared constant when {@code arrayLength} is zero.)
+ * <p>
+ * (<em>Note:</em> The {@code arrayType} is often identical to the last
+ * parameter type of the original target.
+ * It is an explicit argument for symmetry with {@code asSpreader}, and also
+ * to allow the target to use a simple {@code Object} as its last parameter type.)
+ * <p>
+ * In order to create a collecting adapter which is not restricted to a particular
+ * number of collected arguments, use {@link #asVarargsCollector asVarargsCollector} instead.
+ * <p>
+ * Here are some examples of array-collecting method handles:
+ * <blockquote><pre>{@code
+MethodHandle deepToString = publicLookup()
+ .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+assertEquals("[won]", (String) deepToString.invokeExact(new Object[]{"won"}));
+MethodHandle ts1 = deepToString.asCollector(Object[].class, 1);
+assertEquals(methodType(String.class, Object.class), ts1.type());
+//assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"})); //FAIL
+assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"}));
+// arrayType can be a subtype of Object[]
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals(methodType(String.class, String.class, String.class), ts2.type());
+assertEquals("[two, too]", (String) ts2.invokeExact("two", "too"));
+MethodHandle ts0 = deepToString.asCollector(Object[].class, 0);
+assertEquals("[]", (String) ts0.invokeExact());
+// collectors can be nested, Lisp-style
+MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2);
+assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D")));
+// arrayType can be any primitive array type
+MethodHandle bytesToString = publicLookup()
+ .findStatic(Arrays.class, "toString", methodType(String.class, byte[].class))
+ .asCollector(byte[].class, 3);
+assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3));
+MethodHandle longsToString = publicLookup()
+ .findStatic(Arrays.class, "toString", methodType(String.class, long[].class))
+ .asCollector(long[].class, 1);
+assertEquals("[123]", (String) longsToString.invokeExact((long)123));
+ * }</pre></blockquote>
+ * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
+ * @param arrayLength the number of arguments to collect into a new array argument
+ * @return a new method handle which collects some trailing argument
+ * into an array, before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type
+ * or {@code arrayType} is not assignable to this method handle's trailing parameter type,
+ * or {@code arrayLength} is not a legal array size,
+ * or the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ * @throws WrongMethodTypeException if the implied {@code asType} call fails
+ * @see #asSpreader
+ * @see #asVarargsCollector
+ */
+ public MethodHandle asCollector(Class<?> arrayType, int arrayLength) {
+ asCollectorChecks(arrayType, arrayLength);
+
+ return new Transformers.Collector(this, arrayType, arrayLength);
+ }
+
+ /**
+ * See if {@code asCollector} can be validly called with the given arguments.
+ * Return false if the last parameter is not an exact match to arrayType.
+ */
+ /*non-public*/ boolean asCollectorChecks(Class<?> arrayType, int arrayLength) {
+ spreadArrayChecks(arrayType, arrayLength);
+ int nargs = type().parameterCount();
+ if (nargs != 0) {
+ Class<?> lastParam = type().parameterType(nargs-1);
+ if (lastParam == arrayType) return true;
+ if (lastParam.isAssignableFrom(arrayType)) return false;
+ }
+ throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType);
+ }
+
+ /**
+ * Makes a <em>variable arity</em> adapter which is able to accept
+ * any number of trailing positional arguments and collect them
+ * into an array argument.
+ * <p>
+ * The type and behavior of the adapter will be the same as
+ * the type and behavior of the target, except that certain
+ * {@code invoke} and {@code asType} requests can lead to
+ * trailing positional arguments being collected into target's
+ * trailing parameter.
+ * Also, the last parameter type of the adapter will be
+ * {@code arrayType}, even if the target has a different
+ * last parameter type.
+ * <p>
+ * This transformation may return {@code this} if the method handle is
+ * already of variable arity and its trailing parameter type
+ * is identical to {@code arrayType}.
+ * <p>
+ * When called with {@link #invokeExact invokeExact}, the adapter invokes
+ * the target with no argument changes.
+ * (<em>Note:</em> This behavior is different from a
+ * {@linkplain #asCollector fixed arity collector},
+ * since it accepts a whole array of indeterminate length,
+ * rather than a fixed number of arguments.)
+ * <p>
+ * When called with plain, inexact {@link #invoke invoke}, if the caller
+ * type is the same as the adapter, the adapter invokes the target as with
+ * {@code invokeExact}.
+ * (This is the normal behavior for {@code invoke} when types match.)
+ * <p>
+ * Otherwise, if the caller and adapter arity are the same, and the
+ * trailing parameter type of the caller is a reference type identical to
+ * or assignable to the trailing parameter type of the adapter,
+ * the arguments and return values are converted pairwise,
+ * as if by {@link #asType asType} on a fixed arity
+ * method handle.
+ * <p>
+ * Otherwise, the arities differ, or the adapter's trailing parameter
+ * type is not assignable from the corresponding caller type.
+ * In this case, the adapter replaces all trailing arguments from
+ * the original trailing argument position onward, by
+ * a new array of type {@code arrayType}, whose elements
+ * comprise (in order) the replaced arguments.
+ * <p>
+ * The caller type must provides as least enough arguments,
+ * and of the correct type, to satisfy the target's requirement for
+ * positional arguments before the trailing array argument.
+ * Thus, the caller must supply, at a minimum, {@code N-1} arguments,
+ * where {@code N} is the arity of the target.
+ * Also, there must exist conversions from the incoming arguments
+ * to the target's arguments.
+ * As with other uses of plain {@code invoke}, if these basic
+ * requirements are not fulfilled, a {@code WrongMethodTypeException}
+ * may be thrown.
+ * <p>
+ * In all cases, what the target eventually returns is returned unchanged by the adapter.
+ * <p>
+ * In the final case, it is exactly as if the target method handle were
+ * temporarily adapted with a {@linkplain #asCollector fixed arity collector}
+ * to the arity required by the caller type.
+ * (As with {@code asCollector}, if the array length is zero,
+ * a shared constant may be used instead of a new array.
+ * If the implied call to {@code asCollector} would throw
+ * an {@code IllegalArgumentException} or {@code WrongMethodTypeException},
+ * the call to the variable arity adapter must throw
+ * {@code WrongMethodTypeException}.)
+ * <p>
+ * The behavior of {@link #asType asType} is also specialized for
+ * variable arity adapters, to maintain the invariant that
+ * plain, inexact {@code invoke} is always equivalent to an {@code asType}
+ * call to adjust the target type, followed by {@code invokeExact}.
+ * Therefore, a variable arity adapter responds
+ * to an {@code asType} request by building a fixed arity collector,
+ * if and only if the adapter and requested type differ either
+ * in arity or trailing argument type.
+ * The resulting fixed arity collector has its type further adjusted
+ * (if necessary) to the requested type by pairwise conversion,
+ * as if by another application of {@code asType}.
+ * <p>
+ * When a method handle is obtained by executing an {@code ldc} instruction
+ * of a {@code CONSTANT_MethodHandle} constant, and the target method is marked
+ * as a variable arity method (with the modifier bit {@code 0x0080}),
+ * the method handle will accept multiple arities, as if the method handle
+ * constant were created by means of a call to {@code asVarargsCollector}.
+ * <p>
+ * In order to create a collecting adapter which collects a predetermined
+ * number of arguments, and whose type reflects this predetermined number,
+ * use {@link #asCollector asCollector} instead.
+ * <p>
+ * No method handle transformations produce new method handles with
+ * variable arity, unless they are documented as doing so.
+ * Therefore, besides {@code asVarargsCollector},
+ * all methods in {@code MethodHandle} and {@code MethodHandles}
+ * will return a method handle with fixed arity,
+ * except in the cases where they are specified to return their original
+ * operand (e.g., {@code asType} of the method handle's own type).
+ * <p>
+ * Calling {@code asVarargsCollector} on a method handle which is already
+ * of variable arity will produce a method handle with the same type and behavior.
+ * It may (or may not) return the original variable arity method handle.
+ * <p>
+ * Here is an example, of a list-making variable arity method handle:
+ * <blockquote><pre>{@code
+MethodHandle deepToString = publicLookup()
+ .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class);
+assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"}));
+assertEquals("[won]", (String) ts1.invoke( new Object[]{"won"}));
+assertEquals("[won]", (String) ts1.invoke( "won" ));
+assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"}));
+// findStatic of Arrays.asList(...) produces a variable arity method handle:
+MethodHandle asList = publicLookup()
+ .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class));
+assertEquals(methodType(List.class, Object[].class), asList.type());
+assert(asList.isVarargsCollector());
+assertEquals("[]", asList.invoke().toString());
+assertEquals("[1]", asList.invoke(1).toString());
+assertEquals("[two, too]", asList.invoke("two", "too").toString());
+String[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asList.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString());
+List ls = (List) asList.invoke((Object)argv);
+assertEquals(1, ls.size());
+assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
+ * }</pre></blockquote>
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * These rules are designed as a dynamically-typed variation
+ * of the Java rules for variable arity methods.
+ * In both cases, callers to a variable arity method or method handle
+ * can either pass zero or more positional arguments, or else pass
+ * pre-collected arrays of any length. Users should be aware of the
+ * special role of the final argument, and of the effect of a
+ * type match on that final argument, which determines whether
+ * or not a single trailing argument is interpreted as a whole
+ * array or a single element of an array to be collected.
+ * Note that the dynamic type of the trailing argument has no
+ * effect on this decision, only a comparison between the symbolic
+ * type descriptor of the call site and the type descriptor of the method handle.)
+ *
+ * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
+ * @return a new method handle which can collect any number of trailing arguments
+ * into an array, before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type
+ * or {@code arrayType} is not assignable to this method handle's trailing parameter type
+ * @see #asCollector
+ * @see #isVarargsCollector
+ * @see #asFixedArity
+ */
+ public MethodHandle asVarargsCollector(Class<?> arrayType) {
+ arrayType.getClass(); // explicit NPE
+ boolean lastMatch = asCollectorChecks(arrayType, 0);
+ if (isVarargsCollector() && lastMatch)
+ return this;
- public MethodType type() { return null; }
+ return new Transformers.VarargsCollector(this);
+ }
- public final Object invokeExact(Object... args) throws Throwable { return null; }
+ /**
+ * Determines if this method handle
+ * supports {@linkplain #asVarargsCollector variable arity} calls.
+ * Such method handles arise from the following sources:
+ * <ul>
+ * <li>a call to {@linkplain #asVarargsCollector asVarargsCollector}
+ * <li>a call to a {@linkplain java.lang.invoke.MethodHandles.Lookup lookup method}
+ * which resolves to a variable arity Java method or constructor
+ * <li>an {@code ldc} instruction of a {@code CONSTANT_MethodHandle}
+ * which resolves to a variable arity Java method or constructor
+ * </ul>
+ * @return true if this method handle accepts more than one arity of plain, inexact {@code invoke} calls
+ * @see #asVarargsCollector
+ * @see #asFixedArity
+ */
+ public boolean isVarargsCollector() {
+ return false;
+ }
- public final Object invoke(Object... args) throws Throwable { return null; }
+ /**
+ * Makes a <em>fixed arity</em> method handle which is otherwise
+ * equivalent to the current method handle.
+ * <p>
+ * If the current method handle is not of
+ * {@linkplain #asVarargsCollector variable arity},
+ * the current method handle is returned.
+ * This is true even if the current method handle
+ * could not be a valid input to {@code asVarargsCollector}.
+ * <p>
+ * Otherwise, the resulting fixed-arity method handle has the same
+ * type and behavior of the current method handle,
+ * except that {@link #isVarargsCollector isVarargsCollector}
+ * will be false.
+ * The fixed-arity method handle may (or may not) be the
+ * a previous argument to {@code asVarargsCollector}.
+ * <p>
+ * Here is an example, of a list-making variable arity method handle:
+ * <blockquote><pre>{@code
+MethodHandle asListVar = publicLookup()
+ .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class))
+ .asVarargsCollector(Object[].class);
+MethodHandle asListFix = asListVar.asFixedArity();
+assertEquals("[1]", asListVar.invoke(1).toString());
+Exception caught = null;
+try { asListFix.invoke((Object)1); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof ClassCastException);
+assertEquals("[two, too]", asListVar.invoke("two", "too").toString());
+try { asListFix.invoke("two", "too"); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof WrongMethodTypeException);
+Object[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString());
+assertEquals(1, ((List) asListVar.invoke((Object)argv)).size());
+assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
+ * }</pre></blockquote>
+ *
+ * @return a new method handle which accepts only a fixed number of arguments
+ * @see #asVarargsCollector
+ * @see #isVarargsCollector
+ */
+ public MethodHandle asFixedArity() {
+ // Android-changed: implementation specific.
+ MethodHandle mh = this;
+ if (mh.isVarargsCollector()) {
+ mh = ((Transformers.VarargsCollector) mh).asFixedArity();
+ }
+ assert(!mh.isVarargsCollector());
+ return mh;
+ }
- public Object invokeWithArguments(Object... arguments) throws Throwable { return null; }
+ /**
+ * Binds a value {@code x} to the first argument of a method handle, without invoking it.
+ * The new method handle adapts, as its <i>target</i>,
+ * the current method handle by binding it to the given argument.
+ * The type of the bound handle will be
+ * the same as the type of the target, except that a single leading
+ * reference parameter will be omitted.
+ * <p>
+ * When called, the bound handle inserts the given value {@code x}
+ * as a new leading argument to the target. The other arguments are
+ * also passed unchanged.
+ * What the target eventually returns is returned unchanged by the bound handle.
+ * <p>
+ * The reference {@code x} must be convertible to the first parameter
+ * type of the target.
+ * <p>
+ * (<em>Note:</em> Because method handles are immutable, the target method handle
+ * retains its original type and behavior.)
+ * @param x the value to bind to the first argument of the target
+ * @return a new method handle which prepends the given value to the incoming
+ * argument list, before calling the original method handle
+ * @throws IllegalArgumentException if the target does not have a
+ * leading parameter type that is a reference type
+ * @throws ClassCastException if {@code x} cannot be converted
+ * to the leading parameter type of the target
+ * @see MethodHandles#insertArguments
+ */
+ public MethodHandle bindTo(Object x) {
+ x = type.leadingReferenceParameter().cast(x); // throw CCE if needed
- public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable { return null; }
+ return new Transformers.BindTo(this, x);
+ }
- public MethodHandle asType(MethodType newType) { return null; }
+ /**
+ * Returns a string representation of the method handle,
+ * starting with the string {@code "MethodHandle"} and
+ * ending with the string representation of the method handle's type.
+ * In other words, this method returns a string equal to the value of:
+ * <blockquote><pre>{@code
+ * "MethodHandle" + type().toString()
+ * }</pre></blockquote>
+ * <p>
+ * (<em>Note:</em> Future releases of this API may add further information
+ * to the string representation.
+ * Therefore, the present syntax should not be parsed by applications.)
+ *
+ * @return a string representation of the method handle
+ */
+ @Override
+ public String toString() {
+ // Android-changed: Removed debugging support.
+ return "MethodHandle"+type;
+ }
- public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { return null; }
+ /** @hide */
+ public int getHandleKind() {
+ return handleKind;
+ }
- public MethodHandle asVarargsCollector(Class<?> arrayType) { return null; }
+ /** @hide */
+ protected void transform(EmulatedStackFrame arguments) throws Throwable {
+ throw new AssertionError("MethodHandle.transform should never be called.");
+ }
- public boolean isVarargsCollector() { return false; }
+ /**
+ * Creates a copy of this method handle, copying all relevant data.
+ *
+ * @hide
+ */
+ protected MethodHandle duplicate() {
+ try {
+ return (MethodHandle) this.clone();
+ } catch (CloneNotSupportedException cnse) {
+ throw new AssertionError("Subclass of Transformer is not cloneable");
+ }
+ }
- public MethodHandle asFixedArity() { return null; }
- public MethodHandle bindTo(Object x) { return null; }
+ /**
+ * This is the entry point for all transform calls, and dispatches to the protected
+ * transform method. This layer of indirection exists purely for convenience, because
+ * we can invoke-direct on a fixed ArtMethod for all transform variants.
+ *
+ * NOTE: If this extra layer of indirection proves to be a problem, we can get rid
+ * of this layer of indirection at the cost of some additional ugliness.
+ */
+ private void transformInternal(EmulatedStackFrame arguments) throws Throwable {
+ transform(arguments);
+ }
+ // Android-changed: Removed implementation details :
+ //
+ // String standardString();
+ // String debugString();
+ //
+ //// Implementation methods.
+ //// Sub-classes can override these default implementations.
+ //// All these methods assume arguments are already validated.
+ //
+ // Other transforms to do: convert, explicitCast, permute, drop, filter, fold, GWT, catch
+ //
+ // BoundMethodHandle bindArgumentL(int pos, Object value);
+ // /*non-public*/ MethodHandle setVarargs(MemberName member);
+ // /*non-public*/ MethodHandle viewAsType(MethodType newType, boolean strict);
+ // /*non-public*/ boolean viewAsTypeChecks(MethodType newType, boolean strict);
+ //
+ // Decoding
+ //
+ // /*non-public*/ LambdaForm internalForm();
+ // /*non-public*/ MemberName internalMemberName();
+ // /*non-public*/ Class<?> internalCallerClass();
+ // /*non-public*/ MethodHandleImpl.Intrinsic intrinsicName();
+ // /*non-public*/ MethodHandle withInternalMemberName(MemberName member, boolean isInvokeSpecial);
+ // /*non-public*/ boolean isInvokeSpecial();
+ // /*non-public*/ Object internalValues();
+ // /*non-public*/ Object internalProperties();
+ //
+ //// Method handle implementation methods.
+ //// Sub-classes can override these default implementations.
+ //// All these methods assume arguments are already validated.
+ //
+ // /*non-public*/ abstract MethodHandle copyWith(MethodType mt, LambdaForm lf);
+ // abstract BoundMethodHandle rebind();
+ // /*non-public*/ void updateForm(LambdaForm newForm);
+ // /*non-public*/ void customize();
+ // private static final long FORM_OFFSET;
}
diff --git a/java/lang/invoke/MethodHandles.java b/java/lang/invoke/MethodHandles.java
index f27ad988..a1b861d2 100644
--- a/java/lang/invoke/MethodHandles.java
+++ b/java/lang/invoke/MethodHandles.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,129 +25,3461 @@
package java.lang.invoke;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
+import java.lang.reflect.*;
+import java.nio.ByteOrder;
import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import dalvik.system.VMStack;
+import sun.invoke.util.VerifyAccess;
+import sun.invoke.util.Wrapper;
+import static java.lang.invoke.MethodHandleStatics.*;
+
+/**
+ * This class consists exclusively of static methods that operate on or return
+ * method handles. They fall into several categories:
+ * <ul>
+ * <li>Lookup methods which help create method handles for methods and fields.
+ * <li>Combinator methods, which combine or transform pre-existing method handles into new ones.
+ * <li>Other factory methods to create method handles that emulate other common JVM operations or control flow patterns.
+ * </ul>
+ * <p>
+ * @author John Rose, JSR 292 EG
+ * @since 1.7
+ */
public class MethodHandles {
- public static Lookup lookup() { return null; }
+ private MethodHandles() { } // do not instantiate
+
+ // BEGIN Android-added: unsupported() helper function.
+ // TODO(b/65872996): Remove when complete.
+ private static void unsupported(String msg) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(msg);
+ }
+ // END Android-added: unsupported() helper function.
+
+ // Android-changed: We do not use MemberName / MethodHandleImpl.
+ //
+ // private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory();
+ // static { MethodHandleImpl.initStatics(); }
+ // See IMPL_LOOKUP below.
+
+ //// Method handle creation from ordinary methods.
- public static Lookup publicLookup() { return null; }
+ /**
+ * Returns a {@link Lookup lookup object} with
+ * full capabilities to emulate all supported bytecode behaviors of the caller.
+ * These capabilities include <a href="MethodHandles.Lookup.html#privacc">private access</a> to the caller.
+ * Factory methods on the lookup object can create
+ * <a href="MethodHandleInfo.html#directmh">direct method handles</a>
+ * for any member that the caller has access to via bytecodes,
+ * including protected and private fields and methods.
+ * This lookup object is a <em>capability</em> which may be delegated to trusted agents.
+ * Do not store it in place where untrusted code can access it.
+ * <p>
+ * This method is caller sensitive, which means that it may return different
+ * values to different callers.
+ * <p>
+ * For any given caller class {@code C}, the lookup object returned by this call
+ * has equivalent capabilities to any lookup object
+ * supplied by the JVM to the bootstrap method of an
+ * <a href="package-summary.html#indyinsn">invokedynamic instruction</a>
+ * executing in the same caller class {@code C}.
+ * @return a lookup object for the caller of this method, with private access
+ */
+ // Android-changed: Remove caller sensitive.
+ // @CallerSensitive
+ public static Lookup lookup() {
+ // Android-changed: Do not use Reflection.getCallerClass().
+ return new Lookup(VMStack.getStackClass1());
+ }
+
+ /**
+ * Returns a {@link Lookup lookup object} which is trusted minimally.
+ * It can only be used to create method handles to
+ * publicly accessible fields and methods.
+ * <p>
+ * As a matter of pure convention, the {@linkplain Lookup#lookupClass lookup class}
+ * of this lookup object will be {@link java.lang.Object}.
+ *
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * The lookup class can be changed to any other class {@code C} using an expression of the form
+ * {@link Lookup#in publicLookup().in(C.class)}.
+ * Since all classes have equal access to public names,
+ * such a change would confer no new access rights.
+ * A public lookup object is always subject to
+ * <a href="MethodHandles.Lookup.html#secmgr">security manager checks</a>.
+ * Also, it cannot access
+ * <a href="MethodHandles.Lookup.html#callsens">caller sensitive methods</a>.
+ * @return a lookup object which is trusted minimally
+ */
+ public static Lookup publicLookup() {
+ return Lookup.PUBLIC_LOOKUP;
+ }
+ /**
+ * Performs an unchecked "crack" of a
+ * <a href="MethodHandleInfo.html#directmh">direct method handle</a>.
+ * The result is as if the user had obtained a lookup object capable enough
+ * to crack the target method handle, called
+ * {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect}
+ * on the target to obtain its symbolic reference, and then called
+ * {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs}
+ * to resolve the symbolic reference to a member.
+ * <p>
+ * If there is a security manager, its {@code checkPermission} method
+ * is called with a {@code ReflectPermission("suppressAccessChecks")} permission.
+ * @param <T> the desired type of the result, either {@link Member} or a subtype
+ * @param target a direct method handle to crack into symbolic reference components
+ * @param expected a class object representing the desired result type {@code T}
+ * @return a reference to the method, constructor, or field object
+ * @exception SecurityException if the caller is not privileged to call {@code setAccessible}
+ * @exception NullPointerException if either argument is {@code null}
+ * @exception IllegalArgumentException if the target is not a direct method handle
+ * @exception ClassCastException if the member is not of the expected type
+ * @since 1.8
+ */
public static <T extends Member> T
- reflectAs(Class<T> expected, MethodHandle target) { return null; }
+ reflectAs(Class<T> expected, MethodHandle target) {
+ MethodHandleImpl directTarget = getMethodHandleImpl(target);
+ // Given that this is specified to be an "unchecked" crack, we can directly allocate
+ // a member from the underlying ArtField / Method and bypass all associated access checks.
+ return expected.cast(directTarget.getMemberInternal());
+ }
+ /**
+ * A <em>lookup object</em> is a factory for creating method handles,
+ * when the creation requires access checking.
+ * Method handles do not perform
+ * access checks when they are called, but rather when they are created.
+ * Therefore, method handle access
+ * restrictions must be enforced when a method handle is created.
+ * The caller class against which those restrictions are enforced
+ * is known as the {@linkplain #lookupClass lookup class}.
+ * <p>
+ * A lookup class which needs to create method handles will call
+ * {@link #lookup MethodHandles.lookup} to create a factory for itself.
+ * When the {@code Lookup} factory object is created, the identity of the lookup class is
+ * determined, and securely stored in the {@code Lookup} object.
+ * The lookup class (or its delegates) may then use factory methods
+ * on the {@code Lookup} object to create method handles for access-checked members.
+ * This includes all methods, constructors, and fields which are allowed to the lookup class,
+ * even private ones.
+ *
+ * <h1><a name="lookups"></a>Lookup Factory Methods</h1>
+ * The factory methods on a {@code Lookup} object correspond to all major
+ * use cases for methods, constructors, and fields.
+ * Each method handle created by a factory method is the functional
+ * equivalent of a particular <em>bytecode behavior</em>.
+ * (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.)
+ * Here is a summary of the correspondence between these factory methods and
+ * the behavior the resulting method handles:
+ * <table border=1 cellpadding=5 summary="lookup method behaviors">
+ * <tr>
+ * <th><a name="equiv"></a>lookup expression</th>
+ * <th>member</th>
+ * <th>bytecode behavior</th>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findGetter lookup.findGetter(C.class,"f",FT.class)}</td>
+ * <td>{@code FT f;}</td><td>{@code (T) this.f;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticGetter lookup.findStaticGetter(C.class,"f",FT.class)}</td>
+ * <td>{@code static}<br>{@code FT f;}</td><td>{@code (T) C.f;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findSetter lookup.findSetter(C.class,"f",FT.class)}</td>
+ * <td>{@code FT f;}</td><td>{@code this.f = x;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticSetter lookup.findStaticSetter(C.class,"f",FT.class)}</td>
+ * <td>{@code static}<br>{@code FT f;}</td><td>{@code C.f = arg;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findVirtual lookup.findVirtual(C.class,"m",MT)}</td>
+ * <td>{@code T m(A*);}</td><td>{@code (T) this.m(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStatic lookup.findStatic(C.class,"m",MT)}</td>
+ * <td>{@code static}<br>{@code T m(A*);}</td><td>{@code (T) C.m(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findSpecial lookup.findSpecial(C.class,"m",MT,this.class)}</td>
+ * <td>{@code T m(A*);}</td><td>{@code (T) super.m(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findConstructor lookup.findConstructor(C.class,MT)}</td>
+ * <td>{@code C(A*);}</td><td>{@code new C(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectGetter lookup.unreflectGetter(aField)}</td>
+ * <td>({@code static})?<br>{@code FT f;}</td><td>{@code (FT) aField.get(thisOrNull);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter lookup.unreflectSetter(aField)}</td>
+ * <td>({@code static})?<br>{@code FT f;}</td><td>{@code aField.set(thisOrNull, arg);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
+ * <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectConstructor lookup.unreflectConstructor(aConstructor)}</td>
+ * <td>{@code C(A*);}</td><td>{@code (C) aConstructor.newInstance(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
+ * <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
+ * </tr>
+ * </table>
+ *
+ * Here, the type {@code C} is the class or interface being searched for a member,
+ * documented as a parameter named {@code refc} in the lookup methods.
+ * The method type {@code MT} is composed from the return type {@code T}
+ * and the sequence of argument types {@code A*}.
+ * The constructor also has a sequence of argument types {@code A*} and
+ * is deemed to return the newly-created object of type {@code C}.
+ * Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}.
+ * The formal parameter {@code this} stands for the self-reference of type {@code C};
+ * if it is present, it is always the leading argument to the method handle invocation.
+ * (In the case of some {@code protected} members, {@code this} may be
+ * restricted in type to the lookup class; see below.)
+ * The name {@code arg} stands for all the other method handle arguments.
+ * In the code examples for the Core Reflection API, the name {@code thisOrNull}
+ * stands for a null reference if the accessed method or field is static,
+ * and {@code this} otherwise.
+ * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand
+ * for reflective objects corresponding to the given members.
+ * <p>
+ * In cases where the given member is of variable arity (i.e., a method or constructor)
+ * the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}.
+ * In all other cases, the returned method handle will be of fixed arity.
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * The equivalence between looked-up method handles and underlying
+ * class members and bytecode behaviors
+ * can break down in a few ways:
+ * <ul style="font-size:smaller;">
+ * <li>If {@code C} is not symbolically accessible from the lookup class's loader,
+ * the lookup can still succeed, even when there is no equivalent
+ * Java expression or bytecoded constant.
+ * <li>Likewise, if {@code T} or {@code MT}
+ * is not symbolically accessible from the lookup class's loader,
+ * the lookup can still succeed.
+ * For example, lookups for {@code MethodHandle.invokeExact} and
+ * {@code MethodHandle.invoke} will always succeed, regardless of requested type.
+ * <li>If there is a security manager installed, it can forbid the lookup
+ * on various grounds (<a href="MethodHandles.Lookup.html#secmgr">see below</a>).
+ * By contrast, the {@code ldc} instruction on a {@code CONSTANT_MethodHandle}
+ * constant is not subject to security manager checks.
+ * <li>If the looked-up method has a
+ * <a href="MethodHandle.html#maxarity">very large arity</a>,
+ * the method handle creation may fail, due to the method handle
+ * type having too many parameters.
+ * </ul>
+ *
+ * <h1><a name="access"></a>Access checking</h1>
+ * Access checks are applied in the factory methods of {@code Lookup},
+ * when a method handle is created.
+ * This is a key difference from the Core Reflection API, since
+ * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+ * performs access checking against every caller, on every call.
+ * <p>
+ * All access checks start from a {@code Lookup} object, which
+ * compares its recorded lookup class against all requests to
+ * create method handles.
+ * A single {@code Lookup} object can be used to create any number
+ * of access-checked method handles, all checked against a single
+ * lookup class.
+ * <p>
+ * A {@code Lookup} object can be shared with other trusted code,
+ * such as a metaobject protocol.
+ * A shared {@code Lookup} object delegates the capability
+ * to create method handles on private members of the lookup class.
+ * Even if privileged code uses the {@code Lookup} object,
+ * the access checking is confined to the privileges of the
+ * original lookup class.
+ * <p>
+ * A lookup can fail, because
+ * the containing class is not accessible to the lookup class, or
+ * because the desired class member is missing, or because the
+ * desired class member is not accessible to the lookup class, or
+ * because the lookup object is not trusted enough to access the member.
+ * In any of these cases, a {@code ReflectiveOperationException} will be
+ * thrown from the attempted lookup. The exact class will be one of
+ * the following:
+ * <ul>
+ * <li>NoSuchMethodException &mdash; if a method is requested but does not exist
+ * <li>NoSuchFieldException &mdash; if a field is requested but does not exist
+ * <li>IllegalAccessException &mdash; if the member exists but an access check fails
+ * </ul>
+ * <p>
+ * In general, the conditions under which a method handle may be
+ * looked up for a method {@code M} are no more restrictive than the conditions
+ * under which the lookup class could have compiled, verified, and resolved a call to {@code M}.
+ * Where the JVM would raise exceptions like {@code NoSuchMethodError},
+ * a method handle lookup will generally raise a corresponding
+ * checked exception, such as {@code NoSuchMethodException}.
+ * And the effect of invoking the method handle resulting from the lookup
+ * is <a href="MethodHandles.Lookup.html#equiv">exactly equivalent</a>
+ * to executing the compiled, verified, and resolved call to {@code M}.
+ * The same point is true of fields and constructors.
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * Access checks only apply to named and reflected methods,
+ * constructors, and fields.
+ * Other method handle creation methods, such as
+ * {@link MethodHandle#asType MethodHandle.asType},
+ * do not require any access checks, and are used
+ * independently of any {@code Lookup} object.
+ * <p>
+ * If the desired member is {@code protected}, the usual JVM rules apply,
+ * including the requirement that the lookup class must be either be in the
+ * same package as the desired member, or must inherit that member.
+ * (See the Java Virtual Machine Specification, sections 4.9.2, 5.4.3.5, and 6.4.)
+ * In addition, if the desired member is a non-static field or method
+ * in a different package, the resulting method handle may only be applied
+ * to objects of the lookup class or one of its subclasses.
+ * This requirement is enforced by narrowing the type of the leading
+ * {@code this} parameter from {@code C}
+ * (which will necessarily be a superclass of the lookup class)
+ * to the lookup class itself.
+ * <p>
+ * The JVM imposes a similar requirement on {@code invokespecial} instruction,
+ * that the receiver argument must match both the resolved method <em>and</em>
+ * the current class. Again, this requirement is enforced by narrowing the
+ * type of the leading parameter to the resulting method handle.
+ * (See the Java Virtual Machine Specification, section 4.10.1.9.)
+ * <p>
+ * The JVM represents constructors and static initializer blocks as internal methods
+ * with special names ({@code "<init>"} and {@code "<clinit>"}).
+ * The internal syntax of invocation instructions allows them to refer to such internal
+ * methods as if they were normal methods, but the JVM bytecode verifier rejects them.
+ * A lookup of such an internal method will produce a {@code NoSuchMethodException}.
+ * <p>
+ * In some cases, access between nested classes is obtained by the Java compiler by creating
+ * an wrapper method to access a private method of another class
+ * in the same top-level declaration.
+ * For example, a nested class {@code C.D}
+ * can access private members within other related classes such as
+ * {@code C}, {@code C.D.E}, or {@code C.B},
+ * but the Java compiler may need to generate wrapper methods in
+ * those related classes. In such cases, a {@code Lookup} object on
+ * {@code C.E} would be unable to those private members.
+ * A workaround for this limitation is the {@link Lookup#in Lookup.in} method,
+ * which can transform a lookup on {@code C.E} into one on any of those other
+ * classes, without special elevation of privilege.
+ * <p>
+ * The accesses permitted to a given lookup object may be limited,
+ * according to its set of {@link #lookupModes lookupModes},
+ * to a subset of members normally accessible to the lookup class.
+ * For example, the {@link #publicLookup publicLookup}
+ * method produces a lookup object which is only allowed to access
+ * public members in public classes.
+ * The caller sensitive method {@link #lookup lookup}
+ * produces a lookup object with full capabilities relative to
+ * its caller class, to emulate all supported bytecode behaviors.
+ * Also, the {@link Lookup#in Lookup.in} method may produce a lookup object
+ * with fewer access modes than the original lookup object.
+ *
+ * <p style="font-size:smaller;">
+ * <a name="privacc"></a>
+ * <em>Discussion of private access:</em>
+ * We say that a lookup has <em>private access</em>
+ * if its {@linkplain #lookupModes lookup modes}
+ * include the possibility of accessing {@code private} members.
+ * As documented in the relevant methods elsewhere,
+ * only lookups with private access possess the following capabilities:
+ * <ul style="font-size:smaller;">
+ * <li>access private fields, methods, and constructors of the lookup class
+ * <li>create method handles which invoke <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a> methods,
+ * such as {@code Class.forName}
+ * <li>create method handles which {@link Lookup#findSpecial emulate invokespecial} instructions
+ * <li>avoid <a href="MethodHandles.Lookup.html#secmgr">package access checks</a>
+ * for classes accessible to the lookup class
+ * <li>create {@link Lookup#in delegated lookup objects} which have private access to other classes
+ * within the same package member
+ * </ul>
+ * <p style="font-size:smaller;">
+ * Each of these permissions is a consequence of the fact that a lookup object
+ * with private access can be securely traced back to an originating class,
+ * whose <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> and Java language access permissions
+ * can be reliably determined and emulated by method handles.
+ *
+ * <h1><a name="secmgr"></a>Security manager interactions</h1>
+ * Although bytecode instructions can only refer to classes in
+ * a related class loader, this API can search for methods in any
+ * class, as long as a reference to its {@code Class} object is
+ * available. Such cross-loader references are also possible with the
+ * Core Reflection API, and are impossible to bytecode instructions
+ * such as {@code invokestatic} or {@code getfield}.
+ * There is a {@linkplain java.lang.SecurityManager security manager API}
+ * to allow applications to check such cross-loader references.
+ * These checks apply to both the {@code MethodHandles.Lookup} API
+ * and the Core Reflection API
+ * (as found on {@link java.lang.Class Class}).
+ * <p>
+ * If a security manager is present, member lookups are subject to
+ * additional checks.
+ * From one to three calls are made to the security manager.
+ * Any of these calls can refuse access by throwing a
+ * {@link java.lang.SecurityException SecurityException}.
+ * Define {@code smgr} as the security manager,
+ * {@code lookc} as the lookup class of the current lookup object,
+ * {@code refc} as the containing class in which the member
+ * is being sought, and {@code defc} as the class in which the
+ * member is actually defined.
+ * The value {@code lookc} is defined as <em>not present</em>
+ * if the current lookup object does not have
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>.
+ * The calls are made according to the following rules:
+ * <ul>
+ * <li><b>Step 1:</b>
+ * If {@code lookc} is not present, or if its class loader is not
+ * the same as or an ancestor of the class loader of {@code refc},
+ * then {@link SecurityManager#checkPackageAccess
+ * smgr.checkPackageAccess(refcPkg)} is called,
+ * where {@code refcPkg} is the package of {@code refc}.
+ * <li><b>Step 2:</b>
+ * If the retrieved member is not public and
+ * {@code lookc} is not present, then
+ * {@link SecurityManager#checkPermission smgr.checkPermission}
+ * with {@code RuntimePermission("accessDeclaredMembers")} is called.
+ * <li><b>Step 3:</b>
+ * If the retrieved member is not public,
+ * and if {@code lookc} is not present,
+ * and if {@code defc} and {@code refc} are different,
+ * then {@link SecurityManager#checkPackageAccess
+ * smgr.checkPackageAccess(defcPkg)} is called,
+ * where {@code defcPkg} is the package of {@code defc}.
+ * </ul>
+ * Security checks are performed after other access checks have passed.
+ * Therefore, the above rules presuppose a member that is public,
+ * or else that is being accessed from a lookup class that has
+ * rights to access the member.
+ *
+ * <h1><a name="callsens"></a>Caller sensitive methods</h1>
+ * A small number of Java methods have a special property called caller sensitivity.
+ * A <em>caller-sensitive</em> method can behave differently depending on the
+ * identity of its immediate caller.
+ * <p>
+ * If a method handle for a caller-sensitive method is requested,
+ * the general rules for <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> apply,
+ * but they take account of the lookup class in a special way.
+ * The resulting method handle behaves as if it were called
+ * from an instruction contained in the lookup class,
+ * so that the caller-sensitive method detects the lookup class.
+ * (By contrast, the invoker of the method handle is disregarded.)
+ * Thus, in the case of caller-sensitive methods,
+ * different lookup classes may give rise to
+ * differently behaving method handles.
+ * <p>
+ * In cases where the lookup object is
+ * {@link #publicLookup publicLookup()},
+ * or some other lookup object without
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>,
+ * the lookup class is disregarded.
+ * In such cases, no caller-sensitive method handle can be created,
+ * access is forbidden, and the lookup fails with an
+ * {@code IllegalAccessException}.
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * For example, the caller-sensitive method
+ * {@link java.lang.Class#forName(String) Class.forName(x)}
+ * can return varying classes or throw varying exceptions,
+ * depending on the class loader of the class that calls it.
+ * A public lookup of {@code Class.forName} will fail, because
+ * there is no reasonable way to determine its bytecode behavior.
+ * <p style="font-size:smaller;">
+ * If an application caches method handles for broad sharing,
+ * it should use {@code publicLookup()} to create them.
+ * If there is a lookup of {@code Class.forName}, it will fail,
+ * and the application must take appropriate action in that case.
+ * It may be that a later lookup, perhaps during the invocation of a
+ * bootstrap method, can incorporate the specific identity
+ * of the caller, making the method accessible.
+ * <p style="font-size:smaller;">
+ * The function {@code MethodHandles.lookup} is caller sensitive
+ * so that there can be a secure foundation for lookups.
+ * Nearly all other methods in the JSR 292 API rely on lookup
+ * objects to check access requests.
+ */
+ // Android-changed: Change link targets from MethodHandles#[public]Lookup to
+ // #[public]Lookup to work around complaints from javadoc.
public static final
class Lookup {
- public static final int PUBLIC = 0;
+ /** The class on behalf of whom the lookup is being performed. */
+ /* @NonNull */ private final Class<?> lookupClass;
+
+ /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */
+ private final int allowedModes;
+
+ /** A single-bit mask representing {@code public} access,
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value, {@code 0x01}, happens to be the same as the value of the
+ * {@code public} {@linkplain java.lang.reflect.Modifier#PUBLIC modifier bit}.
+ */
+ public static final int PUBLIC = Modifier.PUBLIC;
+
+ /** A single-bit mask representing {@code private} access,
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value, {@code 0x02}, happens to be the same as the value of the
+ * {@code private} {@linkplain java.lang.reflect.Modifier#PRIVATE modifier bit}.
+ */
+ public static final int PRIVATE = Modifier.PRIVATE;
+
+ /** A single-bit mask representing {@code protected} access,
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value, {@code 0x04}, happens to be the same as the value of the
+ * {@code protected} {@linkplain java.lang.reflect.Modifier#PROTECTED modifier bit}.
+ */
+ public static final int PROTECTED = Modifier.PROTECTED;
+
+ /** A single-bit mask representing {@code package} access (default access),
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value is {@code 0x08}, which does not correspond meaningfully to
+ * any particular {@linkplain java.lang.reflect.Modifier modifier bit}.
+ */
+ public static final int PACKAGE = Modifier.STATIC;
+
+ private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE);
+
+ // Android-note: Android has no notion of a trusted lookup. If required, such lookups
+ // are performed by the runtime. As a result, we always use lookupClass, which will always
+ // be non-null in our implementation.
+ //
+ // private static final int TRUSTED = -1;
+
+ private static int fixmods(int mods) {
+ mods &= (ALL_MODES - PACKAGE);
+ return (mods != 0) ? mods : PACKAGE;
+ }
+
+ /** Tells which class is performing the lookup. It is this class against
+ * which checks are performed for visibility and access permissions.
+ * <p>
+ * The class implies a maximum level of access permission,
+ * but the permissions may be additionally limited by the bitmask
+ * {@link #lookupModes lookupModes}, which controls whether non-public members
+ * can be accessed.
+ * @return the lookup class, on behalf of which this lookup object finds members
+ */
+ public Class<?> lookupClass() {
+ return lookupClass;
+ }
+
+ /** Tells which access-protection classes of members this lookup object can produce.
+ * The result is a bit-mask of the bits
+ * {@linkplain #PUBLIC PUBLIC (0x01)},
+ * {@linkplain #PRIVATE PRIVATE (0x02)},
+ * {@linkplain #PROTECTED PROTECTED (0x04)},
+ * and {@linkplain #PACKAGE PACKAGE (0x08)}.
+ * <p>
+ * A freshly-created lookup object
+ * on the {@linkplain java.lang.invoke.MethodHandles#lookup() caller's class}
+ * has all possible bits set, since the caller class can access all its own members.
+ * A lookup object on a new lookup class
+ * {@linkplain java.lang.invoke.MethodHandles.Lookup#in created from a previous lookup object}
+ * may have some mode bits set to zero.
+ * The purpose of this is to restrict access via the new lookup object,
+ * so that it can access only names which can be reached by the original
+ * lookup object, and also by the new lookup class.
+ * @return the lookup modes, which limit the kinds of access performed by this lookup object
+ */
+ public int lookupModes() {
+ return allowedModes & ALL_MODES;
+ }
+
+ /** Embody the current class (the lookupClass) as a lookup class
+ * for method handle creation.
+ * Must be called by from a method in this package,
+ * which in turn is called by a method not in this package.
+ */
+ Lookup(Class<?> lookupClass) {
+ this(lookupClass, ALL_MODES);
+ // make sure we haven't accidentally picked up a privileged class:
+ checkUnprivilegedlookupClass(lookupClass, ALL_MODES);
+ }
+
+ private Lookup(Class<?> lookupClass, int allowedModes) {
+ this.lookupClass = lookupClass;
+ this.allowedModes = allowedModes;
+ }
+
+ /**
+ * Creates a lookup on the specified new lookup class.
+ * The resulting object will report the specified
+ * class as its own {@link #lookupClass lookupClass}.
+ * <p>
+ * However, the resulting {@code Lookup} object is guaranteed
+ * to have no more access capabilities than the original.
+ * In particular, access capabilities can be lost as follows:<ul>
+ * <li>If the new lookup class differs from the old one,
+ * protected members will not be accessible by virtue of inheritance.
+ * (Protected members may continue to be accessible because of package sharing.)
+ * <li>If the new lookup class is in a different package
+ * than the old one, protected and default (package) members will not be accessible.
+ * <li>If the new lookup class is not within the same package member
+ * as the old one, private members will not be accessible.
+ * <li>If the new lookup class is not accessible to the old lookup class,
+ * then no members, not even public members, will be accessible.
+ * (In all other cases, public members will continue to be accessible.)
+ * </ul>
+ *
+ * @param requestedLookupClass the desired lookup class for the new lookup object
+ * @return a lookup object which reports the desired lookup class
+ * @throws NullPointerException if the argument is null
+ */
+ public Lookup in(Class<?> requestedLookupClass) {
+ requestedLookupClass.getClass(); // null check
+ // Android-changed: There's no notion of a trusted lookup.
+ // if (allowedModes == TRUSTED) // IMPL_LOOKUP can make any lookup at all
+ // return new Lookup(requestedLookupClass, ALL_MODES);
- public static final int PRIVATE = 0;
+ if (requestedLookupClass == this.lookupClass)
+ return this; // keep same capabilities
+ int newModes = (allowedModes & (ALL_MODES & ~PROTECTED));
+ if ((newModes & PACKAGE) != 0
+ && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) {
+ newModes &= ~(PACKAGE|PRIVATE);
+ }
+ // Allow nestmate lookups to be created without special privilege:
+ if ((newModes & PRIVATE) != 0
+ && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) {
+ newModes &= ~PRIVATE;
+ }
+ if ((newModes & PUBLIC) != 0
+ && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, allowedModes)) {
+ // The requested class it not accessible from the lookup class.
+ // No permissions.
+ newModes = 0;
+ }
+ checkUnprivilegedlookupClass(requestedLookupClass, newModes);
+ return new Lookup(requestedLookupClass, newModes);
+ }
- public static final int PROTECTED = 0;
+ // Make sure outer class is initialized first.
+ //
+ // Android-changed: Removed unnecessary reference to IMPL_NAMES.
+ // static { IMPL_NAMES.getClass(); }
- public static final int PACKAGE = 0;
+ /** Version of lookup which is trusted minimally.
+ * It can only be used to create method handles to
+ * publicly accessible members.
+ */
+ static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, PUBLIC);
- public Class<?> lookupClass() { return null; }
+ /** Package-private version of lookup which is trusted. */
+ static final Lookup IMPL_LOOKUP = new Lookup(Object.class, ALL_MODES);
- public int lookupModes() { return 0; }
+ private static void checkUnprivilegedlookupClass(Class<?> lookupClass, int allowedModes) {
+ String name = lookupClass.getName();
+ if (name.startsWith("java.lang.invoke."))
+ throw newIllegalArgumentException("illegal lookupClass: "+lookupClass);
- public Lookup in(Class<?> requestedLookupClass) { return null; }
+ // For caller-sensitive MethodHandles.lookup()
+ // disallow lookup more restricted packages
+ //
+ // Android-changed: The bootstrap classloader isn't null.
+ if (allowedModes == ALL_MODES &&
+ lookupClass.getClassLoader() == Object.class.getClassLoader()) {
+ if (name.startsWith("java.") ||
+ (name.startsWith("sun.")
+ && !name.startsWith("sun.invoke.")
+ && !name.equals("sun.reflect.ReflectionFactory"))) {
+ throw newIllegalArgumentException("illegal lookupClass: " + lookupClass);
+ }
+ }
+ }
+ /**
+ * Displays the name of the class from which lookups are to be made.
+ * (The name is the one reported by {@link java.lang.Class#getName() Class.getName}.)
+ * If there are restrictions on the access permitted to this lookup,
+ * this is indicated by adding a suffix to the class name, consisting
+ * of a slash and a keyword. The keyword represents the strongest
+ * allowed access, and is chosen as follows:
+ * <ul>
+ * <li>If no access is allowed, the suffix is "/noaccess".
+ * <li>If only public access is allowed, the suffix is "/public".
+ * <li>If only public and package access are allowed, the suffix is "/package".
+ * <li>If only public, package, and private access are allowed, the suffix is "/private".
+ * </ul>
+ * If none of the above cases apply, it is the case that full
+ * access (public, package, private, and protected) is allowed.
+ * In this case, no suffix is added.
+ * This is true only of an object obtained originally from
+ * {@link java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}.
+ * Objects created by {@link java.lang.invoke.MethodHandles.Lookup#in Lookup.in}
+ * always have restricted access, and will display a suffix.
+ * <p>
+ * (It may seem strange that protected access should be
+ * stronger than private access. Viewed independently from
+ * package access, protected access is the first to be lost,
+ * because it requires a direct subclass relationship between
+ * caller and callee.)
+ * @see #in
+ */
+ @Override
+ public String toString() {
+ String cname = lookupClass.getName();
+ switch (allowedModes) {
+ case 0: // no privileges
+ return cname + "/noaccess";
+ case PUBLIC:
+ return cname + "/public";
+ case PUBLIC|PACKAGE:
+ return cname + "/package";
+ case ALL_MODES & ~PROTECTED:
+ return cname + "/private";
+ case ALL_MODES:
+ return cname;
+ // Android-changed: No support for TRUSTED callers.
+ // case TRUSTED:
+ // return "/trusted"; // internal only; not exported
+ default: // Should not happen, but it's a bitfield...
+ cname = cname + "/" + Integer.toHexString(allowedModes);
+ assert(false) : cname;
+ return cname;
+ }
+ }
+
+ /**
+ * Produces a method handle for a static method.
+ * The type of the method handle will be that of the method.
+ * (Since static methods do not take receivers, there is no
+ * additional receiver argument inserted into the method handle type,
+ * as there would be with {@link #findVirtual findVirtual} or {@link #findSpecial findSpecial}.)
+ * The method and all its argument types must be accessible to the lookup object.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If the returned method handle is invoked, the method's class will
+ * be initialized, if it has not already been initialized.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_asList = publicLookup().findStatic(Arrays.class,
+ "asList", methodType(List.class, Object[].class));
+assertEquals("[x, y]", MH_asList.invoke("x", "y").toString());
+ * }</pre></blockquote>
+ * @param refc the class from which the method is accessed
+ * @param name the name of the method
+ * @param type the type of the method
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails,
+ * or if the method is not {@code static},
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
public
- MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ Method method = refc.getDeclaredMethod(name, type.ptypes());
+ final int modifiers = method.getModifiers();
+ if (!Modifier.isStatic(modifiers)) {
+ throw new IllegalAccessException("Method" + method + " is not static");
+ }
+ checkReturnType(method, type);
+ checkAccess(refc, method.getDeclaringClass(), modifiers, method.getName());
+ return createMethodHandle(method, MethodHandle.INVOKE_STATIC, type);
+ }
+
+ private MethodHandle findVirtualForMH(String name, MethodType type) {
+ // these names require special lookups because of the implicit MethodType argument
+ if ("invoke".equals(name))
+ return invoker(type);
+ if ("invokeExact".equals(name))
+ return exactInvoker(type);
+ return null;
+ }
+
+ private static MethodHandle createMethodHandle(Method method, int handleKind,
+ MethodType methodType) {
+ MethodHandle mh = new MethodHandleImpl(method.getArtMethod(), handleKind, methodType);
+ if (method.isVarArgs()) {
+ return new Transformers.VarargsCollector(mh);
+ } else {
+ return mh;
+ }
+ }
+
+ /**
+ * Produces a method handle for a virtual method.
+ * The type of the method handle will be that of the method,
+ * with the receiver type (usually {@code refc}) prepended.
+ * The method and all its argument types must be accessible to the lookup object.
+ * <p>
+ * When called, the handle will treat the first argument as a receiver
+ * and dispatch on the receiver's type to determine which method
+ * implementation to enter.
+ * (The dispatching action is identical with that performed by an
+ * {@code invokevirtual} or {@code invokeinterface} instruction.)
+ * <p>
+ * The first argument will be of type {@code refc} if the lookup
+ * class has full privileges to access the member. Otherwise
+ * the member must be {@code protected} and the first argument
+ * will be restricted in type to the lookup class.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * Because of the general <a href="MethodHandles.Lookup.html#equiv">equivalence</a> between {@code invokevirtual}
+ * instructions and method handles produced by {@code findVirtual},
+ * if the class is {@code MethodHandle} and the name string is
+ * {@code invokeExact} or {@code invoke}, the resulting
+ * method handle is equivalent to one produced by
+ * {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker} or
+ * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}
+ * with the same {@code type} argument.
+ *
+ * <b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_concat = publicLookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class,
+ "hashCode", methodType(int.class));
+MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class,
+ "hashCode", methodType(int.class));
+assertEquals("xy", (String) MH_concat.invokeExact("x", "y"));
+assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy"));
+assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy"));
+// interface method:
+MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class,
+ "subSequence", methodType(CharSequence.class, int.class, int.class));
+assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString());
+// constructor "internal method" must be accessed differently:
+MethodType MT_newString = methodType(void.class); //()V for new String()
+try { assertEquals("impossible", lookup()
+ .findVirtual(String.class, "<init>", MT_newString));
+ } catch (NoSuchMethodException ex) { } // OK
+MethodHandle MH_newString = publicLookup()
+ .findConstructor(String.class, MT_newString);
+assertEquals("", (String) MH_newString.invokeExact());
+ * }</pre></blockquote>
+ *
+ * @param refc the class or interface from which the method is accessed
+ * @param name the name of the method
+ * @param type the type of the method, with the receiver argument omitted
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails,
+ * or if the method is {@code static}
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ // Special case : when we're looking up a virtual method on the MethodHandles class
+ // itself, we can return one of our specialized invokers.
+ if (refc == MethodHandle.class) {
+ MethodHandle mh = findVirtualForMH(name, type);
+ if (mh != null) {
+ return mh;
+ }
+ }
+ // BEGIN Android-changed: Added VarHandle case here.
+ // Implementation to follow. TODO(b/65872996)
+ if (refc == VarHandle.class) {
+ unsupported("MethodHandles.findVirtual with refc == VarHandle.class");
+ return null;
+ }
+ // END Android-changed: Added VarHandle handling here.
+
+ Method method = refc.getInstanceMethod(name, type.ptypes());
+ if (method == null) {
+ // This is pretty ugly and a consequence of the MethodHandles API. We have to throw
+ // an IAE and not an NSME if the method exists but is static (even though the RI's
+ // IAE has a message that says "no such method"). We confine the ugliness and
+ // slowness to the failure case, and allow getInstanceMethod to remain fairly
+ // general.
+ try {
+ Method m = refc.getDeclaredMethod(name, type.ptypes());
+ if (Modifier.isStatic(m.getModifiers())) {
+ throw new IllegalAccessException("Method" + m + " is static");
+ }
+ } catch (NoSuchMethodException ignored) {
+ }
+
+ throw new NoSuchMethodException(name + " " + Arrays.toString(type.ptypes()));
+ }
+ checkReturnType(method, type);
+
+ // We have a valid method, perform access checks.
+ checkAccess(refc, method.getDeclaringClass(), method.getModifiers(), method.getName());
+
+ // Insert the leading reference parameter.
+ MethodType handleType = type.insertParameterTypes(0, refc);
+ return createMethodHandle(method, MethodHandle.INVOKE_VIRTUAL, handleType);
+ }
+
+ /**
+ * Produces a method handle which creates an object and initializes it, using
+ * the constructor of the specified type.
+ * The parameter types of the method handle will be those of the constructor,
+ * while the return type will be a reference to the constructor's class.
+ * The constructor and all its argument types must be accessible to the lookup object.
+ * <p>
+ * The requested type must have a return type of {@code void}.
+ * (This is consistent with the JVM's treatment of constructor type descriptors.)
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If the returned method handle is invoked, the constructor's class will
+ * be initialized, if it has not already been initialized.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_newArrayList = publicLookup().findConstructor(
+ ArrayList.class, methodType(void.class, Collection.class));
+Collection orig = Arrays.asList("x", "y");
+Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig);
+assert(orig != copy);
+assertEquals(orig, copy);
+// a variable-arity constructor:
+MethodHandle MH_newProcessBuilder = publicLookup().findConstructor(
+ ProcessBuilder.class, methodType(void.class, String[].class));
+ProcessBuilder pb = (ProcessBuilder)
+ MH_newProcessBuilder.invoke("x", "y", "z");
+assertEquals("[x, y, z]", pb.command().toString());
+ * }</pre></blockquote>
+ * @param refc the class or interface from which the method is accessed
+ * @param type the type of the method, with the receiver argument omitted, and a void return type
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the constructor does not exist
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ if (refc.isArray()) {
+ throw new NoSuchMethodException("no constructor for array class: " + refc.getName());
+ }
+ // The queried |type| is (PT1,PT2,..)V
+ Constructor constructor = refc.getDeclaredConstructor(type.ptypes());
+ if (constructor == null) {
+ throw new NoSuchMethodException(
+ "No constructor for " + constructor.getDeclaringClass() + " matching " + type);
+ }
+ checkAccess(refc, constructor.getDeclaringClass(), constructor.getModifiers(),
+ constructor.getName());
+
+ return createMethodHandleForConstructor(constructor);
+ }
+
+ private MethodHandle createMethodHandleForConstructor(Constructor constructor) {
+ Class<?> refc = constructor.getDeclaringClass();
+ MethodType constructorType =
+ MethodType.methodType(refc, constructor.getParameterTypes());
+ MethodHandle mh;
+ if (refc == String.class) {
+ // String constructors have optimized StringFactory methods
+ // that matches returned type. These factory methods combine the
+ // memory allocation and initialization calls for String objects.
+ mh = new MethodHandleImpl(constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT,
+ constructorType);
+ } else {
+ // Constructors for all other classes use a Construct transformer to perform
+ // their memory allocation and call to <init>.
+ MethodType initType = initMethodType(constructorType);
+ MethodHandle initHandle = new MethodHandleImpl(
+ constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT, initType);
+ mh = new Transformers.Construct(initHandle, constructorType);
+ }
- public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ if (constructor.isVarArgs()) {
+ mh = new Transformers.VarargsCollector(mh);
+ }
+ return mh;
+ }
- public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ private static MethodType initMethodType(MethodType constructorType) {
+ // Returns a MethodType appropriate for class <init>
+ // methods. Constructor MethodTypes have the form
+ // (PT1,PT2,...)C and class <init> MethodTypes have the
+ // form (C,PT1,PT2,...)V.
+ assert constructorType.rtype() != void.class;
+ // Insert constructorType C as the first parameter type in
+ // the MethodType for <init>.
+ Class<?> [] initPtypes = new Class<?> [constructorType.ptypes().length + 1];
+ initPtypes[0] = constructorType.rtype();
+ System.arraycopy(constructorType.ptypes(), 0, initPtypes, 1,
+ constructorType.ptypes().length);
+
+ // Set the return type for the <init> MethodType to be void.
+ return MethodType.methodType(void.class, initPtypes);
+ }
+
+ /**
+ * Produces an early-bound method handle for a virtual method.
+ * It will bypass checks for overriding methods on the receiver,
+ * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
+ * instruction from within the explicitly specified {@code specialCaller}.
+ * The type of the method handle will be that of the method,
+ * with a suitably restricted receiver type prepended.
+ * (The receiver type will be {@code specialCaller} or a subtype.)
+ * The method and all its argument types must be accessible
+ * to the lookup object.
+ * <p>
+ * Before method resolution,
+ * if the explicitly specified caller class is not identical with the
+ * lookup class, or if this lookup object does not have
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>
+ * privileges, the access fails.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p style="font-size:smaller;">
+ * <em>(Note: JVM internal methods named {@code "<init>"} are not visible to this API,
+ * even though the {@code invokespecial} instruction can refer to them
+ * in special circumstances. Use {@link #findConstructor findConstructor}
+ * to access instance initialization methods in a safe manner.)</em>
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+static class Listie extends ArrayList {
+ public String toString() { return "[wee Listie]"; }
+ static Lookup lookup() { return MethodHandles.lookup(); }
+}
+...
+// no access to constructor via invokeSpecial:
+MethodHandle MH_newListie = Listie.lookup()
+ .findConstructor(Listie.class, methodType(void.class));
+Listie l = (Listie) MH_newListie.invokeExact();
+try { assertEquals("impossible", Listie.lookup().findSpecial(
+ Listie.class, "<init>", methodType(void.class), Listie.class));
+ } catch (NoSuchMethodException ex) { } // OK
+// access to super and self methods via invokeSpecial:
+MethodHandle MH_super = Listie.lookup().findSpecial(
+ ArrayList.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_this = Listie.lookup().findSpecial(
+ Listie.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_duper = Listie.lookup().findSpecial(
+ Object.class, "toString" , methodType(String.class), Listie.class);
+assertEquals("[]", (String) MH_super.invokeExact(l));
+assertEquals(""+l, (String) MH_this.invokeExact(l));
+assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method
+try { assertEquals("inaccessible", Listie.lookup().findSpecial(
+ String.class, "toString", methodType(String.class), Listie.class));
+ } catch (IllegalAccessException ex) { } // OK
+Listie subl = new Listie() { public String toString() { return "[subclass]"; } };
+assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method
+ * }</pre></blockquote>
+ *
+ * @param refc the class or interface from which the method is accessed
+ * @param name the name of the method (which must not be "&lt;init&gt;")
+ * @param type the type of the method, with the receiver argument omitted
+ * @param specialCaller the proposed calling class to perform the {@code invokespecial}
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
public MethodHandle findSpecial(Class<?> refc, String name, MethodType type,
- Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { return null; }
+ Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
+ if (specialCaller == null) {
+ throw new NullPointerException("specialCaller == null");
+ }
+
+ if (type == null) {
+ throw new NullPointerException("type == null");
+ }
+
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+
+ if (refc == null) {
+ throw new NullPointerException("ref == null");
+ }
+
+ // Make sure that the special caller is identical to the lookup class or that we have
+ // private access.
+ checkSpecialCaller(specialCaller);
+
+ // Even though constructors are invoked using a "special" invoke, handles to them can't
+ // be created using findSpecial. Callers must use findConstructor instead. Similarly,
+ // there is no path for calling static class initializers.
+ if (name.startsWith("<")) {
+ throw new NoSuchMethodException(name + " is not a valid method name.");
+ }
+
+ Method method = refc.getDeclaredMethod(name, type.ptypes());
+ checkReturnType(method, type);
+ return findSpecial(method, type, refc, specialCaller);
+ }
+
+ private MethodHandle findSpecial(Method method, MethodType type,
+ Class<?> refc, Class<?> specialCaller)
+ throws IllegalAccessException {
+ if (Modifier.isStatic(method.getModifiers())) {
+ throw new IllegalAccessException("expected a non-static method:" + method);
+ }
+
+ if (Modifier.isPrivate(method.getModifiers())) {
+ // Since this is a private method, we'll need to also make sure that the
+ // lookup class is the same as the refering class. We've already checked that
+ // the specialCaller is the same as the special lookup class, both of these must
+ // be the same as the declaring class(*) in order to access the private method.
+ //
+ // (*) Well, this isn't true for nested classes but OpenJDK doesn't support those
+ // either.
+ if (refc != lookupClass()) {
+ throw new IllegalAccessException("no private access for invokespecial : "
+ + refc + ", from" + this);
+ }
- public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ // This is a private method, so there's nothing special to do.
+ MethodType handleType = type.insertParameterTypes(0, refc);
+ return createMethodHandle(method, MethodHandle.INVOKE_DIRECT, handleType);
+ }
- public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ // This is a public, protected or package-private method, which means we're expecting
+ // invoke-super semantics. We'll have to restrict the receiver type appropriately on the
+ // handle once we check that there really is a "super" relationship between them.
+ if (!method.getDeclaringClass().isAssignableFrom(specialCaller)) {
+ throw new IllegalAccessException(refc + "is not assignable from " + specialCaller);
+ }
- public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ // Note that we restrict the receiver to "specialCaller" instances.
+ MethodType handleType = type.insertParameterTypes(0, specialCaller);
+ return createMethodHandle(method, MethodHandle.INVOKE_SUPER, handleType);
+ }
- public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ /**
+ * Produces a method handle giving read access to a non-static field.
+ * The type of the method handle will have a return type of the field's
+ * value type.
+ * The method handle's single argument will be the instance containing
+ * the field.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can load values from the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.IGET);
+ }
- public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ private MethodHandle findAccessor(Class<?> refc, String name, Class<?> type, int kind)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = findFieldOfType(refc, name, type);
+ return findAccessor(field, refc, type, kind, true /* performAccessChecks */);
+ }
- public MethodHandle unreflect(Method m) throws IllegalAccessException { return null; }
+ private MethodHandle findAccessor(Field field, Class<?> refc, Class<?> type, int kind,
+ boolean performAccessChecks)
+ throws IllegalAccessException {
+ final boolean isSetterKind = kind == MethodHandle.IPUT || kind == MethodHandle.SPUT;
+ final boolean isStaticKind = kind == MethodHandle.SGET || kind == MethodHandle.SPUT;
+ commonFieldChecks(field, refc, type, isStaticKind, performAccessChecks);
+ if (performAccessChecks) {
+ final int modifiers = field.getModifiers();
+ if (isSetterKind && Modifier.isFinal(modifiers)) {
+ throw new IllegalAccessException("Field " + field + " is final");
+ }
+ }
- public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { return null; }
+ final MethodType methodType;
+ switch (kind) {
+ case MethodHandle.SGET:
+ methodType = MethodType.methodType(type);
+ break;
+ case MethodHandle.SPUT:
+ methodType = MethodType.methodType(void.class, type);
+ break;
+ case MethodHandle.IGET:
+ methodType = MethodType.methodType(type, refc);
+ break;
+ case MethodHandle.IPUT:
+ methodType = MethodType.methodType(void.class, refc, type);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid kind " + kind);
+ }
+ return new MethodHandleImpl(field.getArtField(), kind, methodType);
+ }
- public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException { return null; }
+ /**
+ * Produces a method handle giving write access to a non-static field.
+ * The type of the method handle will have a void return type.
+ * The method handle will take two arguments, the instance containing
+ * the field, and the value to be stored.
+ * The second argument will be of the field's value type.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can store values into the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.IPUT);
+ }
- public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { return null; }
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method.
+ /**
+ * Produces a VarHandle giving access to a non-static field {@code name}
+ * of type {@code type} declared in a class of type {@code recv}.
+ * The VarHandle's variable type is {@code type} and it has one
+ * coordinate type, {@code recv}.
+ * <p>
+ * Access checking is performed immediately on behalf of the lookup
+ * class.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the field is declared {@code final}, then the write, atomic
+ * update, numeric atomic update, and bitwise atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double} then numeric atomic update
+ * access modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the field is declared {@code volatile} then the returned VarHandle
+ * will override access to the field (effectively ignore the
+ * {@code volatile} declaration) in accordance to its specified
+ * access modes.
+ * <p>
+ * If the field type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param recv the receiver class, of type {@code R}, that declares the
+ * non-static field
+ * @param name the field's name
+ * @param type the field's type, of type {@code T}
+ * @return a VarHandle giving access to non-static fields.
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ * @since 9
+ * @hide
+ */
+ public VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ final Field field = findFieldOfType(recv, name, type);
+ final boolean isStatic = false;
+ final boolean performAccessChecks = true;
+ commonFieldChecks(field, recv, type, isStatic, performAccessChecks);
+ return FieldVarHandle.create(field);
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory method.
- public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { return null; }
+ // BEGIN Android-added: Common field resolution and access check methods.
+ private Field findFieldOfType(final Class<?> refc, String name, Class<?> type)
+ throws NoSuchFieldException {
+ Field field = null;
- public MethodHandleInfo revealDirect(MethodHandle target) { return null; }
+ // Search refc and super classes for the field.
+ for (Class<?> cls = refc; cls != null; cls = cls.getSuperclass()) {
+ try {
+ field = cls.getDeclaredField(name);
+ break;
+ } catch (NoSuchFieldException e) {
+ }
+ }
+
+ if (field == null) {
+ // Force failure citing refc.
+ field = refc.getDeclaredField(name);
+ }
+
+ final Class<?> fieldType = field.getType();
+ if (fieldType != type) {
+ throw new NoSuchFieldException(name);
+ }
+ return field;
+ }
+
+ private void commonFieldChecks(Field field, Class<?> refc, Class<?> type,
+ boolean isStatic, boolean performAccessChecks)
+ throws IllegalAccessException {
+ final int modifiers = field.getModifiers();
+ if (performAccessChecks) {
+ checkAccess(refc, field.getDeclaringClass(), modifiers, field.getName());
+ }
+ if (Modifier.isStatic(modifiers) != isStatic) {
+ String reason = "Field " + field + " is " +
+ (isStatic ? "not " : "") + "static";
+ throw new IllegalAccessException(reason);
+ }
+ }
+ // END Android-added: Common field resolution and access check methods.
+
+ /**
+ * Produces a method handle giving read access to a static field.
+ * The type of the method handle will have a return type of the field's
+ * value type.
+ * The method handle will take no arguments.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can load values from the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.SGET);
+ }
+
+ /**
+ * Produces a method handle giving write access to a static field.
+ * The type of the method handle will have a void return type.
+ * The method handle will take a single
+ * argument, of the field's value type, the value to be stored.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can store values into the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.SPUT);
+ }
+
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method.
+ /**
+ * Produces a VarHandle giving access to a static field {@code name} of
+ * type {@code type} declared in a class of type {@code decl}.
+ * The VarHandle's variable type is {@code type} and it has no
+ * coordinate types.
+ * <p>
+ * Access checking is performed immediately on behalf of the lookup
+ * class.
+ * <p>
+ * If the returned VarHandle is operated on, the declaring class will be
+ * initialized, if it has not already been initialized.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the field is declared {@code final}, then the write, atomic
+ * update, numeric atomic update, and bitwise atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double}, then numeric atomic update
+ * access modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the field is declared {@code volatile} then the returned VarHandle
+ * will override access to the field (effectively ignore the
+ * {@code volatile} declaration) in accordance to its specified
+ * access modes.
+ * <p>
+ * If the field type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param decl the class that declares the static field
+ * @param name the field's name
+ * @param type the field's type, of type {@code T}
+ * @return a VarHandle giving access to a static field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ * @since 9
+ * @hide
+ */
+ public VarHandle findStaticVarHandle(Class<?> decl, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ final Field field = findFieldOfType(decl, name, type);
+ final boolean isStatic = true;
+ final boolean performAccessChecks = true;
+ commonFieldChecks(field, decl, type, isStatic, performAccessChecks);
+ return FieldVarHandle.create(field);
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory method.
+
+ /**
+ * Produces an early-bound method handle for a non-static method.
+ * The receiver must have a supertype {@code defc} in which a method
+ * of the given name and type is accessible to the lookup class.
+ * The method and all its argument types must be accessible to the lookup object.
+ * The type of the method handle will be that of the method,
+ * without any insertion of an additional receiver parameter.
+ * The given receiver will be bound into the method handle,
+ * so that every call to the method handle will invoke the
+ * requested method on the given receiver.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set
+ * <em>and</em> the trailing array argument is not the only argument.
+ * (If the trailing array argument is the only argument,
+ * the given receiver value will be bound to it.)
+ * <p>
+ * This is equivalent to the following code:
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle mh0 = lookup().findVirtual(defc, name, type);
+MethodHandle mh1 = mh0.bindTo(receiver);
+MethodType mt1 = mh1.type();
+if (mh0.isVarargsCollector())
+ mh1 = mh1.asVarargsCollector(mt1.parameterType(mt1.parameterCount()-1));
+return mh1;
+ * }</pre></blockquote>
+ * where {@code defc} is either {@code receiver.getClass()} or a super
+ * type of that class, in which the requested method is accessible
+ * to the lookup class.
+ * (Note that {@code bindTo} does not preserve variable arity.)
+ * @param receiver the object from which the method is accessed
+ * @param name the name of the method
+ * @param type the type of the method, with the receiver argument omitted
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ * @see MethodHandle#bindTo
+ * @see #findVirtual
+ */
+ public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ MethodHandle handle = findVirtual(receiver.getClass(), name, type);
+ MethodHandle adapter = handle.bindTo(receiver);
+ MethodType adapterType = adapter.type();
+ if (handle.isVarargsCollector()) {
+ adapter = adapter.asVarargsCollector(
+ adapterType.parameterType(adapterType.parameterCount() - 1));
+ }
+
+ return adapter;
+ }
+
+ /**
+ * Makes a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
+ * to <i>m</i>, if the lookup class has permission.
+ * If <i>m</i> is non-static, the receiver argument is treated as an initial argument.
+ * If <i>m</i> is virtual, overriding is respected on every call.
+ * Unlike the Core Reflection API, exceptions are <em>not</em> wrapped.
+ * The type of the method handle will be that of the method,
+ * with the receiver type prepended (but only if it is non-static).
+ * If the method's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * If <i>m</i> is not public, do not share the resulting handle with untrusted parties.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If <i>m</i> is static, and
+ * if the returned method handle is invoked, the method's class will
+ * be initialized, if it has not already been initialized.
+ * @param m the reflected method
+ * @return a method handle which can invoke the reflected method
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflect(Method m) throws IllegalAccessException {
+ if (m == null) {
+ throw new NullPointerException("m == null");
+ }
+
+ MethodType methodType = MethodType.methodType(m.getReturnType(),
+ m.getParameterTypes());
+
+ // We should only perform access checks if setAccessible hasn't been called yet.
+ if (!m.isAccessible()) {
+ checkAccess(m.getDeclaringClass(), m.getDeclaringClass(), m.getModifiers(),
+ m.getName());
+ }
+
+ if (Modifier.isStatic(m.getModifiers())) {
+ return createMethodHandle(m, MethodHandle.INVOKE_STATIC, methodType);
+ } else {
+ methodType = methodType.insertParameterTypes(0, m.getDeclaringClass());
+ return createMethodHandle(m, MethodHandle.INVOKE_VIRTUAL, methodType);
+ }
+ }
+
+ /**
+ * Produces a method handle for a reflected method.
+ * It will bypass checks for overriding methods on the receiver,
+ * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
+ * instruction from within the explicitly specified {@code specialCaller}.
+ * The type of the method handle will be that of the method,
+ * with a suitably restricted receiver type prepended.
+ * (The receiver type will be {@code specialCaller} or a subtype.)
+ * If the method's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class,
+ * as if {@code invokespecial} instruction were being linked.
+ * <p>
+ * Before method resolution,
+ * if the explicitly specified caller class is not identical with the
+ * lookup class, or if this lookup object does not have
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>
+ * privileges, the access fails.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * @param m the reflected method
+ * @param specialCaller the class nominally calling the method
+ * @return a method handle which can invoke the reflected method
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException {
+ if (m == null) {
+ throw new NullPointerException("m == null");
+ }
+
+ if (specialCaller == null) {
+ throw new NullPointerException("specialCaller == null");
+ }
+
+ if (!m.isAccessible()) {
+ checkSpecialCaller(specialCaller);
+ }
+
+ final MethodType methodType = MethodType.methodType(m.getReturnType(),
+ m.getParameterTypes());
+ return findSpecial(m, methodType, m.getDeclaringClass() /* refc */, specialCaller);
+ }
+
+ /**
+ * Produces a method handle for a reflected constructor.
+ * The type of the method handle will be that of the constructor,
+ * with the return type changed to the declaring class.
+ * The method handle will perform a {@code newInstance} operation,
+ * creating a new instance of the constructor's class on the
+ * arguments passed to the method handle.
+ * <p>
+ * If the constructor's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If the returned method handle is invoked, the constructor's class will
+ * be initialized, if it has not already been initialized.
+ * @param c the reflected constructor
+ * @return a method handle which can invoke the reflected constructor
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException {
+ if (c == null) {
+ throw new NullPointerException("c == null");
+ }
+
+ if (!c.isAccessible()) {
+ checkAccess(c.getDeclaringClass(), c.getDeclaringClass(), c.getModifiers(),
+ c.getName());
+ }
+
+ return createMethodHandleForConstructor(c);
+ }
+
+ /**
+ * Produces a method handle giving read access to a reflected field.
+ * The type of the method handle will have a return type of the field's
+ * value type.
+ * If the field is static, the method handle will take no arguments.
+ * Otherwise, its single argument will be the instance containing
+ * the field.
+ * If the field's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the field is static, and
+ * if the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param f the reflected field
+ * @return a method handle which can load values from the reflected field
+ * @throws IllegalAccessException if access checking fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflectGetter(Field f) throws IllegalAccessException {
+ return findAccessor(f, f.getDeclaringClass(), f.getType(),
+ Modifier.isStatic(f.getModifiers()) ? MethodHandle.SGET : MethodHandle.IGET,
+ !f.isAccessible() /* performAccessChecks */);
+ }
+
+ /**
+ * Produces a method handle giving write access to a reflected field.
+ * The type of the method handle will have a void return type.
+ * If the field is static, the method handle will take a single
+ * argument, of the field's value type, the value to be stored.
+ * Otherwise, the two arguments will be the instance containing
+ * the field, and the value to be stored.
+ * If the field's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the field is static, and
+ * if the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param f the reflected field
+ * @return a method handle which can store values into the reflected field
+ * @throws IllegalAccessException if access checking fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
+ return findAccessor(f, f.getDeclaringClass(), f.getType(),
+ Modifier.isStatic(f.getModifiers()) ? MethodHandle.SPUT : MethodHandle.IPUT,
+ !f.isAccessible() /* performAccessChecks */);
+ }
+
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method.
+ /**
+ * Produces a VarHandle giving access to a reflected field {@code f}
+ * of type {@code T} declared in a class of type {@code R}.
+ * The VarHandle's variable type is {@code T}.
+ * If the field is non-static the VarHandle has one coordinate type,
+ * {@code R}. Otherwise, the field is static, and the VarHandle has no
+ * coordinate types.
+ * <p>
+ * Access checking is performed immediately on behalf of the lookup
+ * class, regardless of the value of the field's {@code accessible}
+ * flag.
+ * <p>
+ * If the field is static, and if the returned VarHandle is operated
+ * on, the field's declaring class will be initialized, if it has not
+ * already been initialized.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the field is declared {@code final}, then the write, atomic
+ * update, numeric atomic update, and bitwise atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double} then numeric atomic update
+ * access modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the field is declared {@code volatile} then the returned VarHandle
+ * will override access to the field (effectively ignore the
+ * {@code volatile} declaration) in accordance to its specified
+ * access modes.
+ * <p>
+ * If the field type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param f the reflected field, with a field of type {@code T}, and
+ * a declaring class of type {@code R}
+ * @return a VarHandle giving access to non-static fields or a static
+ * field
+ * @throws IllegalAccessException if access checking fails
+ * @throws NullPointerException if the argument is null
+ * @since 9
+ * @hide
+ */
+ public VarHandle unreflectVarHandle(Field f) throws IllegalAccessException {
+ final boolean isStatic = Modifier.isStatic(f.getModifiers());
+ final boolean performAccessChecks = true;
+ commonFieldChecks(f, f.getDeclaringClass(), f.getType(), isStatic, performAccessChecks);
+ return FieldVarHandle.create(f);
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory method.
+
+ /**
+ * Cracks a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
+ * created by this lookup object or a similar one.
+ * Security and access checks are performed to ensure that this lookup object
+ * is capable of reproducing the target method handle.
+ * This means that the cracking may fail if target is a direct method handle
+ * but was created by an unrelated lookup object.
+ * This can happen if the method handle is <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a>
+ * and was created by a lookup object for a different class.
+ * @param target a direct method handle to crack into symbolic reference components
+ * @return a symbolic reference which can be used to reconstruct this method handle from this lookup object
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails
+ * @exception NullPointerException if the target is {@code null}
+ * @see MethodHandleInfo
+ * @since 1.8
+ */
+ public MethodHandleInfo revealDirect(MethodHandle target) {
+ MethodHandleImpl directTarget = getMethodHandleImpl(target);
+ MethodHandleInfo info = directTarget.reveal();
+
+ try {
+ checkAccess(lookupClass(), info.getDeclaringClass(), info.getModifiers(),
+ info.getName());
+ } catch (IllegalAccessException exception) {
+ throw new IllegalArgumentException("Unable to access memeber.", exception);
+ }
+
+ return info;
+ }
+
+ private boolean hasPrivateAccess() {
+ return (allowedModes & PRIVATE) != 0;
+ }
+
+ /** Check public/protected/private bits on the symbolic reference class and its member. */
+ void checkAccess(Class<?> refc, Class<?> defc, int mods, String methName)
+ throws IllegalAccessException {
+ int allowedModes = this.allowedModes;
+
+ if (Modifier.isProtected(mods) &&
+ defc == Object.class &&
+ "clone".equals(methName) &&
+ refc.isArray()) {
+ // The JVM does this hack also.
+ // (See ClassVerifier::verify_invoke_instructions
+ // and LinkResolver::check_method_accessability.)
+ // Because the JVM does not allow separate methods on array types,
+ // there is no separate method for int[].clone.
+ // All arrays simply inherit Object.clone.
+ // But for access checking logic, we make Object.clone
+ // (normally protected) appear to be public.
+ // Later on, when the DirectMethodHandle is created,
+ // its leading argument will be restricted to the
+ // requested array type.
+ // N.B. The return type is not adjusted, because
+ // that is *not* the bytecode behavior.
+ mods ^= Modifier.PROTECTED | Modifier.PUBLIC;
+ }
+
+ if (Modifier.isProtected(mods) && Modifier.isConstructor(mods)) {
+ // cannot "new" a protected ctor in a different package
+ mods ^= Modifier.PROTECTED;
+ }
+
+ if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0)
+ return; // common case
+ int requestedModes = fixmods(mods); // adjust 0 => PACKAGE
+ if ((requestedModes & allowedModes) != 0) {
+ if (VerifyAccess.isMemberAccessible(refc, defc, mods, lookupClass(), allowedModes))
+ return;
+ } else {
+ // Protected members can also be checked as if they were package-private.
+ if ((requestedModes & PROTECTED) != 0 && (allowedModes & PACKAGE) != 0
+ && VerifyAccess.isSamePackage(defc, lookupClass()))
+ return;
+ }
+
+ throwMakeAccessException(accessFailedMessage(refc, defc, mods), this);
+ }
+
+ String accessFailedMessage(Class<?> refc, Class<?> defc, int mods) {
+ // check the class first:
+ boolean classOK = (Modifier.isPublic(defc.getModifiers()) &&
+ (defc == refc ||
+ Modifier.isPublic(refc.getModifiers())));
+ if (!classOK && (allowedModes & PACKAGE) != 0) {
+ classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), ALL_MODES) &&
+ (defc == refc ||
+ VerifyAccess.isClassAccessible(refc, lookupClass(), ALL_MODES)));
+ }
+ if (!classOK)
+ return "class is not public";
+ if (Modifier.isPublic(mods))
+ return "access to public member failed"; // (how?)
+ if (Modifier.isPrivate(mods))
+ return "member is private";
+ if (Modifier.isProtected(mods))
+ return "member is protected";
+ return "member is private to package";
+ }
+
+ // Android-changed: checkSpecialCaller assumes that ALLOW_NESTMATE_ACCESS = false,
+ // as in upstream OpenJDK.
+ //
+ // private static final boolean ALLOW_NESTMATE_ACCESS = false;
+
+ private void checkSpecialCaller(Class<?> specialCaller) throws IllegalAccessException {
+ // Android-changed: No support for TRUSTED lookups. Also construct the
+ // IllegalAccessException by hand because the upstream code implicitly assumes
+ // that the lookupClass == specialCaller.
+ //
+ // if (allowedModes == TRUSTED) return;
+ if (!hasPrivateAccess() || (specialCaller != lookupClass())) {
+ throw new IllegalAccessException("no private access for invokespecial : "
+ + specialCaller + ", from" + this);
+ }
+ }
+
+ private void throwMakeAccessException(String message, Object from) throws
+ IllegalAccessException{
+ message = message + ": "+ toString();
+ if (from != null) message += ", from " + from;
+ throw new IllegalAccessException(message);
+ }
+
+ private void checkReturnType(Method method, MethodType methodType)
+ throws NoSuchMethodException {
+ if (method.getReturnType() != methodType.rtype()) {
+ throw new NoSuchMethodException(method.getName() + methodType);
+ }
+ }
+ }
+
+ /**
+ * "Cracks" {@code target} to reveal the underlying {@code MethodHandleImpl}.
+ */
+ private static MethodHandleImpl getMethodHandleImpl(MethodHandle target) {
+ // Special case : We implement handles to constructors as transformers,
+ // so we must extract the underlying handle from the transformer.
+ if (target instanceof Transformers.Construct) {
+ target = ((Transformers.Construct) target).getConstructorHandle();
+ }
+
+ // Special case: Var-args methods are also implemented as Transformers,
+ // so we should get the underlying handle in that case as well.
+ if (target instanceof Transformers.VarargsCollector) {
+ target = target.asFixedArity();
+ }
+
+ if (target instanceof MethodHandleImpl) {
+ return (MethodHandleImpl) target;
+ }
+
+ throw new IllegalArgumentException(target + " is not a direct handle");
+ }
+
+ // BEGIN Android-added: method to check if a class is an array.
+ private static void checkClassIsArray(Class<?> c) {
+ if (!c.isArray()) {
+ throw new IllegalArgumentException("Not an array type: " + c);
+ }
+ }
+ private static void checkTypeIsViewable(Class<?> componentType) {
+ if (componentType == short.class ||
+ componentType == char.class ||
+ componentType == int.class ||
+ componentType == long.class ||
+ componentType == float.class ||
+ componentType == double.class) {
+ return;
+ }
+ throw new UnsupportedOperationException("Component type not supported: " + componentType);
}
+ // END Android-added: method to check if a class is an array.
+ /**
+ * Produces a method handle giving read access to elements of an array.
+ * The type of the method handle will have a return type of the array's
+ * element type. Its first argument will be the array type,
+ * and the second will be {@code int}.
+ * @param arrayClass an array type
+ * @return a method handle which can load values from the given array type
+ * @throws NullPointerException if the argument is null
+ * @throws IllegalArgumentException if arrayClass is not an array type
+ */
public static
- MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+ MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException {
+ checkClassIsArray(arrayClass);
+ final Class<?> componentType = arrayClass.getComponentType();
+ if (componentType.isPrimitive()) {
+ try {
+ return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
+ "arrayElementGetter",
+ MethodType.methodType(componentType, arrayClass, int.class));
+ } catch (NoSuchMethodException | IllegalAccessException exception) {
+ throw new AssertionError(exception);
+ }
+ }
+ return new Transformers.ReferenceArrayElementGetter(arrayClass);
+ }
+
+ /** @hide */ public static byte arrayElementGetter(byte[] array, int i) { return array[i]; }
+ /** @hide */ public static boolean arrayElementGetter(boolean[] array, int i) { return array[i]; }
+ /** @hide */ public static char arrayElementGetter(char[] array, int i) { return array[i]; }
+ /** @hide */ public static short arrayElementGetter(short[] array, int i) { return array[i]; }
+ /** @hide */ public static int arrayElementGetter(int[] array, int i) { return array[i]; }
+ /** @hide */ public static long arrayElementGetter(long[] array, int i) { return array[i]; }
+ /** @hide */ public static float arrayElementGetter(float[] array, int i) { return array[i]; }
+ /** @hide */ public static double arrayElementGetter(double[] array, int i) { return array[i]; }
+
+ /**
+ * Produces a method handle giving write access to elements of an array.
+ * The type of the method handle will have a void return type.
+ * Its last argument will be the array's element type.
+ * The first and second arguments will be the array type and int.
+ * @param arrayClass the class of an array
+ * @return a method handle which can store values into the array type
+ * @throws NullPointerException if the argument is null
+ * @throws IllegalArgumentException if arrayClass is not an array type
+ */
+ public static
+ MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException {
+ checkClassIsArray(arrayClass);
+ final Class<?> componentType = arrayClass.getComponentType();
+ if (componentType.isPrimitive()) {
+ try {
+ return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
+ "arrayElementSetter",
+ MethodType.methodType(void.class, arrayClass, int.class, componentType));
+ } catch (NoSuchMethodException | IllegalAccessException exception) {
+ throw new AssertionError(exception);
+ }
+ }
+
+ return new Transformers.ReferenceArrayElementSetter(arrayClass);
+ }
+
+ /** @hide */
+ public static void arrayElementSetter(byte[] array, int i, byte val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(boolean[] array, int i, boolean val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(char[] array, int i, char val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(short[] array, int i, short val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(int[] array, int i, int val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(long[] array, int i, long val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(float[] array, int i, float val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(double[] array, int i, double val) { array[i] = val; }
+
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory methods.
+ /**
+ * Produces a VarHandle giving access to elements of an array of type
+ * {@code arrayClass}. The VarHandle's variable type is the component type
+ * of {@code arrayClass} and the list of coordinate types is
+ * {@code (arrayClass, int)}, where the {@code int} coordinate type
+ * corresponds to an argument that is an index into an array.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the component type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double} then numeric atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the component type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param arrayClass the class of an array, of type {@code T[]}
+ * @return a VarHandle giving access to elements of an array
+ * @throws NullPointerException if the arrayClass is null
+ * @throws IllegalArgumentException if arrayClass is not an array type
+ * @since 9
+ * @hide
+ */
+ public static
+ VarHandle arrayElementVarHandle(Class<?> arrayClass) throws IllegalArgumentException {
+ checkClassIsArray(arrayClass);
+ return ArrayElementVarHandle.create(arrayClass);
+ }
+
+ /**
+ * Produces a VarHandle giving access to elements of a {@code byte[]} array
+ * viewed as if it were a different primitive array type, such as
+ * {@code int[]} or {@code long[]}.
+ * The VarHandle's variable type is the component type of
+ * {@code viewArrayClass} and the list of coordinate types is
+ * {@code (byte[], int)}, where the {@code int} coordinate type
+ * corresponds to an argument that is an index into a {@code byte[]} array.
+ * The returned VarHandle accesses bytes at an index in a {@code byte[]}
+ * array, composing bytes to or from a value of the component type of
+ * {@code viewArrayClass} according to the given endianness.
+ * <p>
+ * The supported component types (variables types) are {@code short},
+ * {@code char}, {@code int}, {@code long}, {@code float} and
+ * {@code double}.
+ * <p>
+ * Access of bytes at a given index will result in an
+ * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
+ * or greater than the {@code byte[]} array length minus the size (in bytes)
+ * of {@code T}.
+ * <p>
+ * Access of bytes at an index may be aligned or misaligned for {@code T},
+ * with respect to the underlying memory address, {@code A} say, associated
+ * with the array and index.
+ * If access is misaligned then access for anything other than the
+ * {@code get} and {@code set} access modes will result in an
+ * {@code IllegalStateException}. In such cases atomic access is only
+ * guaranteed with respect to the largest power of two that divides the GCD
+ * of {@code A} and the size (in bytes) of {@code T}.
+ * If access is aligned then following access modes are supported and are
+ * guaranteed to support atomic access:
+ * <ul>
+ * <li>read write access modes for all {@code T}, with the exception of
+ * access modes {@code get} and {@code set} for {@code long} and
+ * {@code double} on 32-bit platforms.
+ * <li>atomic update access modes for {@code int}, {@code long},
+ * {@code float} or {@code double}.
+ * (Future major platform releases of the JDK may support additional
+ * types for certain currently unsupported access modes.)
+ * <li>numeric atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * <li>bitwise atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * </ul>
+ * <p>
+ * Misaligned access, and therefore atomicity guarantees, may be determined
+ * for {@code byte[]} arrays without operating on a specific array. Given
+ * an {@code index}, {@code T} and it's corresponding boxed type,
+ * {@code T_BOX}, misalignment may be determined as follows:
+ * <pre>{@code
+ * int sizeOfT = T_BOX.BYTES; // size in bytes of T
+ * int misalignedAtZeroIndex = ByteBuffer.wrap(new byte[0]).
+ * alignmentOffset(0, sizeOfT);
+ * int misalignedAtIndex = (misalignedAtZeroIndex + index) % sizeOfT;
+ * boolean isMisaligned = misalignedAtIndex != 0;
+ * }</pre>
+ * <p>
+ * If the variable type is {@code float} or {@code double} then atomic
+ * update access modes compare values using their bitwise representation
+ * (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @param viewArrayClass the view array class, with a component type of
+ * type {@code T}
+ * @param byteOrder the endianness of the view array elements, as
+ * stored in the underlying {@code byte} array
+ * @return a VarHandle giving access to elements of a {@code byte[]} array
+ * viewed as if elements corresponding to the components type of the view
+ * array class
+ * @throws NullPointerException if viewArrayClass or byteOrder is null
+ * @throws IllegalArgumentException if viewArrayClass is not an array type
+ * @throws UnsupportedOperationException if the component type of
+ * viewArrayClass is not supported as a variable type
+ * @since 9
+ * @hide
+ */
public static
- MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+ VarHandle byteArrayViewVarHandle(Class<?> viewArrayClass,
+ ByteOrder byteOrder) throws IllegalArgumentException {
+ checkClassIsArray(viewArrayClass);
+ checkTypeIsViewable(viewArrayClass.getComponentType());
+ return ByteArrayViewVarHandle.create(viewArrayClass, byteOrder);
+ }
+
+ /**
+ * Produces a VarHandle giving access to elements of a {@code ByteBuffer}
+ * viewed as if it were an array of elements of a different primitive
+ * component type to that of {@code byte}, such as {@code int[]} or
+ * {@code long[]}.
+ * The VarHandle's variable type is the component type of
+ * {@code viewArrayClass} and the list of coordinate types is
+ * {@code (ByteBuffer, int)}, where the {@code int} coordinate type
+ * corresponds to an argument that is an index into a {@code byte[]} array.
+ * The returned VarHandle accesses bytes at an index in a
+ * {@code ByteBuffer}, composing bytes to or from a value of the component
+ * type of {@code viewArrayClass} according to the given endianness.
+ * <p>
+ * The supported component types (variables types) are {@code short},
+ * {@code char}, {@code int}, {@code long}, {@code float} and
+ * {@code double}.
+ * <p>
+ * Access will result in a {@code ReadOnlyBufferException} for anything
+ * other than the read access modes if the {@code ByteBuffer} is read-only.
+ * <p>
+ * Access of bytes at a given index will result in an
+ * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
+ * or greater than the {@code ByteBuffer} limit minus the size (in bytes) of
+ * {@code T}.
+ * <p>
+ * Access of bytes at an index may be aligned or misaligned for {@code T},
+ * with respect to the underlying memory address, {@code A} say, associated
+ * with the {@code ByteBuffer} and index.
+ * If access is misaligned then access for anything other than the
+ * {@code get} and {@code set} access modes will result in an
+ * {@code IllegalStateException}. In such cases atomic access is only
+ * guaranteed with respect to the largest power of two that divides the GCD
+ * of {@code A} and the size (in bytes) of {@code T}.
+ * If access is aligned then following access modes are supported and are
+ * guaranteed to support atomic access:
+ * <ul>
+ * <li>read write access modes for all {@code T}, with the exception of
+ * access modes {@code get} and {@code set} for {@code long} and
+ * {@code double} on 32-bit platforms.
+ * <li>atomic update access modes for {@code int}, {@code long},
+ * {@code float} or {@code double}.
+ * (Future major platform releases of the JDK may support additional
+ * types for certain currently unsupported access modes.)
+ * <li>numeric atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * <li>bitwise atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * </ul>
+ * <p>
+ * Misaligned access, and therefore atomicity guarantees, may be determined
+ * for a {@code ByteBuffer}, {@code bb} (direct or otherwise), an
+ * {@code index}, {@code T} and it's corresponding boxed type,
+ * {@code T_BOX}, as follows:
+ * <pre>{@code
+ * int sizeOfT = T_BOX.BYTES; // size in bytes of T
+ * ByteBuffer bb = ...
+ * int misalignedAtIndex = bb.alignmentOffset(index, sizeOfT);
+ * boolean isMisaligned = misalignedAtIndex != 0;
+ * }</pre>
+ * <p>
+ * If the variable type is {@code float} or {@code double} then atomic
+ * update access modes compare values using their bitwise representation
+ * (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @param viewArrayClass the view array class, with a component type of
+ * type {@code T}
+ * @param byteOrder the endianness of the view array elements, as
+ * stored in the underlying {@code ByteBuffer} (Note this overrides the
+ * endianness of a {@code ByteBuffer})
+ * @return a VarHandle giving access to elements of a {@code ByteBuffer}
+ * viewed as if elements corresponding to the components type of the view
+ * array class
+ * @throws NullPointerException if viewArrayClass or byteOrder is null
+ * @throws IllegalArgumentException if viewArrayClass is not an array type
+ * @throws UnsupportedOperationException if the component type of
+ * viewArrayClass is not supported as a variable type
+ * @since 9
+ * @hide
+ */
+ public static
+ VarHandle byteBufferViewVarHandle(Class<?> viewArrayClass,
+ ByteOrder byteOrder) throws IllegalArgumentException {
+ checkClassIsArray(viewArrayClass);
+ checkTypeIsViewable(viewArrayClass.getComponentType());
+ return ByteBufferViewVarHandle.create(viewArrayClass, byteOrder);
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory methods.
+
+ /// method handle invocation (reflective style)
+
+ /**
+ * Produces a method handle which will invoke any method handle of the
+ * given {@code type}, with a given number of trailing arguments replaced by
+ * a single trailing {@code Object[]} array.
+ * The resulting invoker will be a method handle with the following
+ * arguments:
+ * <ul>
+ * <li>a single {@code MethodHandle} target
+ * <li>zero or more leading values (counted by {@code leadingArgCount})
+ * <li>an {@code Object[]} array containing trailing arguments
+ * </ul>
+ * <p>
+ * The invoker will invoke its target like a call to {@link MethodHandle#invoke invoke} with
+ * the indicated {@code type}.
+ * That is, if the target is exactly of the given {@code type}, it will behave
+ * like {@code invokeExact}; otherwise it behave as if {@link MethodHandle#asType asType}
+ * is used to convert the target to the required {@code type}.
+ * <p>
+ * The type of the returned invoker will not be the given {@code type}, but rather
+ * will have all parameters except the first {@code leadingArgCount}
+ * replaced by a single array of type {@code Object[]}, which will be
+ * the final parameter.
+ * <p>
+ * Before invoking its target, the invoker will spread the final array, apply
+ * reference casts as necessary, and unbox and widen primitive arguments.
+ * If, when the invoker is called, the supplied array argument does
+ * not have the correct number of elements, the invoker will throw
+ * an {@link IllegalArgumentException} instead of invoking the target.
+ * <p>
+ * This method is equivalent to the following code (though it may be more efficient):
+ * <blockquote><pre>{@code
+MethodHandle invoker = MethodHandles.invoker(type);
+int spreadArgCount = type.parameterCount() - leadingArgCount;
+invoker = invoker.asSpreader(Object[].class, spreadArgCount);
+return invoker;
+ * }</pre></blockquote>
+ * This method throws no reflective or security exceptions.
+ * @param type the desired target type
+ * @param leadingArgCount number of fixed arguments, to be passed unchanged to the target
+ * @return a method handle suitable for invoking any method handle of the given type
+ * @throws NullPointerException if {@code type} is null
+ * @throws IllegalArgumentException if {@code leadingArgCount} is not in
+ * the range from 0 to {@code type.parameterCount()} inclusive,
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
+ static public
+ MethodHandle spreadInvoker(MethodType type, int leadingArgCount) {
+ if (leadingArgCount < 0 || leadingArgCount > type.parameterCount())
+ throw newIllegalArgumentException("bad argument count", leadingArgCount);
+
+ MethodHandle invoker = MethodHandles.invoker(type);
+ int spreadArgCount = type.parameterCount() - leadingArgCount;
+ invoker = invoker.asSpreader(Object[].class, spreadArgCount);
+ return invoker;
+ }
+
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke any method handle of the given type, as if by {@link MethodHandle#invokeExact invokeExact}.
+ * The resulting invoker will have a type which is
+ * exactly equal to the desired type, except that it will accept
+ * an additional leading argument of type {@code MethodHandle}.
+ * <p>
+ * This method is equivalent to the following code (though it may be more efficient):
+ * {@code publicLookup().findVirtual(MethodHandle.class, "invokeExact", type)}
+ *
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * Invoker method handles can be useful when working with variable method handles
+ * of unknown types.
+ * For example, to emulate an {@code invokeExact} call to a variable method
+ * handle {@code M}, extract its type {@code T},
+ * look up the invoker method {@code X} for {@code T},
+ * and call the invoker method, as {@code X.invoke(T, A...)}.
+ * (It would not work to call {@code X.invokeExact}, since the type {@code T}
+ * is unknown.)
+ * If spreading, collecting, or other argument transformations are required,
+ * they can be applied once to the invoker {@code X} and reused on many {@code M}
+ * method handle values, as long as they are compatible with the type of {@code X}.
+ * <p style="font-size:smaller;">
+ * <em>(Note: The invoker method is not available via the Core Reflection API.
+ * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+ * on the declared {@code invokeExact} or {@code invoke} method will raise an
+ * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
+ * <p>
+ * This method throws no reflective or security exceptions.
+ * @param type the desired target type
+ * @return a method handle suitable for invoking any method handle of the given type
+ * @throws IllegalArgumentException if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
+ static public
+ MethodHandle exactInvoker(MethodType type) {
+ return new Transformers.Invoker(type, true /* isExactInvoker */);
+ }
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke any method handle compatible with the given type, as if by {@link MethodHandle#invoke invoke}.
+ * The resulting invoker will have a type which is
+ * exactly equal to the desired type, except that it will accept
+ * an additional leading argument of type {@code MethodHandle}.
+ * <p>
+ * Before invoking its target, if the target differs from the expected type,
+ * the invoker will apply reference casts as
+ * necessary and box, unbox, or widen primitive values, as if by {@link MethodHandle#asType asType}.
+ * Similarly, the return value will be converted as necessary.
+ * If the target is a {@linkplain MethodHandle#asVarargsCollector variable arity method handle},
+ * the required arity conversion will be made, again as if by {@link MethodHandle#asType asType}.
+ * <p>
+ * This method is equivalent to the following code (though it may be more efficient):
+ * {@code publicLookup().findVirtual(MethodHandle.class, "invoke", type)}
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * A {@linkplain MethodType#genericMethodType general method type} is one which
+ * mentions only {@code Object} arguments and return values.
+ * An invoker for such a type is capable of calling any method handle
+ * of the same arity as the general type.
+ * <p style="font-size:smaller;">
+ * <em>(Note: The invoker method is not available via the Core Reflection API.
+ * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+ * on the declared {@code invokeExact} or {@code invoke} method will raise an
+ * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
+ * <p>
+ * This method throws no reflective or security exceptions.
+ * @param type the desired target type
+ * @return a method handle suitable for invoking any method handle convertible to the given type
+ * @throws IllegalArgumentException if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
static public
- MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { return null; }
+ MethodHandle invoker(MethodType type) {
+ return new Transformers.Invoker(type, false /* isExactInvoker */);
+ }
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke a signature-polymorphic access mode method on any VarHandle whose
+ * associated access mode type is compatible with the given type.
+ * The resulting invoker will have a type which is exactly equal to the
+ * desired given type, except that it will accept an additional leading
+ * argument of type {@code VarHandle}.
+ *
+ * @param accessMode the VarHandle access mode
+ * @param type the desired target type
+ * @return a method handle suitable for invoking an access mode method of
+ * any VarHandle whose access mode type is of the given type.
+ * @since 9
+ * @hide
+ */
static public
- MethodHandle exactInvoker(MethodType type) { return null; }
+ MethodHandle varHandleExactInvoker(VarHandle.AccessMode accessMode, MethodType type) {
+ unsupported("MethodHandles.varHandleExactInvoker()"); // TODO(b/65872996)
+ return null;
+ }
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke a signature-polymorphic access mode method on any VarHandle whose
+ * associated access mode type is compatible with the given type.
+ * The resulting invoker will have a type which is exactly equal to the
+ * desired given type, except that it will accept an additional leading
+ * argument of type {@code VarHandle}.
+ * <p>
+ * Before invoking its target, if the access mode type differs from the
+ * desired given type, the invoker will apply reference casts as necessary
+ * and box, unbox, or widen primitive values, as if by
+ * {@link MethodHandle#asType asType}. Similarly, the return value will be
+ * converted as necessary.
+ * <p>
+ * This method is equivalent to the following code (though it may be more
+ * efficient): {@code publicLookup().findVirtual(VarHandle.class, accessMode.name(), type)}
+ *
+ * @param accessMode the VarHandle access mode
+ * @param type the desired target type
+ * @return a method handle suitable for invoking an access mode method of
+ * any VarHandle whose access mode type is convertible to the given
+ * type.
+ * @since 9
+ * @hide
+ */
static public
- MethodHandle invoker(MethodType type) { return null; }
+ MethodHandle varHandleInvoker(VarHandle.AccessMode accessMode, MethodType type) {
+ unsupported("MethodHandles.varHandleInvoker()"); // TODO(b/65872996)
+ return null;
+ }
+
+ // Android-changed: Basic invokers are not supported.
+ //
+ // static /*non-public*/
+ // MethodHandle basicInvoker(MethodType type) {
+ // return type.invokers().basicInvoker();
+ // }
+
+ /// method handle modification (creation from other method handles)
+ /**
+ * Produces a method handle which adapts the type of the
+ * given method handle to a new type by pairwise argument and return type conversion.
+ * The original type and new type must have the same number of arguments.
+ * The resulting method handle is guaranteed to report a type
+ * which is equal to the desired new type.
+ * <p>
+ * If the original type and new type are equal, returns target.
+ * <p>
+ * The same conversions are allowed as for {@link MethodHandle#asType MethodHandle.asType},
+ * and some additional conversions are also applied if those conversions fail.
+ * Given types <em>T0</em>, <em>T1</em>, one of the following conversions is applied
+ * if possible, before or instead of any conversions done by {@code asType}:
+ * <ul>
+ * <li>If <em>T0</em> and <em>T1</em> are references, and <em>T1</em> is an interface type,
+ * then the value of type <em>T0</em> is passed as a <em>T1</em> without a cast.
+ * (This treatment of interfaces follows the usage of the bytecode verifier.)
+ * <li>If <em>T0</em> is boolean and <em>T1</em> is another primitive,
+ * the boolean is converted to a byte value, 1 for true, 0 for false.
+ * (This treatment follows the usage of the bytecode verifier.)
+ * <li>If <em>T1</em> is boolean and <em>T0</em> is another primitive,
+ * <em>T0</em> is converted to byte via Java casting conversion (JLS 5.5),
+ * and the low order bit of the result is tested, as if by {@code (x & 1) != 0}.
+ * <li>If <em>T0</em> and <em>T1</em> are primitives other than boolean,
+ * then a Java casting conversion (JLS 5.5) is applied.
+ * (Specifically, <em>T0</em> will convert to <em>T1</em> by
+ * widening and/or narrowing.)
+ * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
+ * conversion will be applied at runtime, possibly followed
+ * by a Java casting conversion (JLS 5.5) on the primitive value,
+ * possibly followed by a conversion from byte to boolean by testing
+ * the low-order bit.
+ * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive,
+ * and if the reference is null at runtime, a zero value is introduced.
+ * </ul>
+ * @param target the method handle to invoke after arguments are retyped
+ * @param newType the expected type of the new method handle
+ * @return a method handle which delegates to the target after performing
+ * any necessary argument conversions, and arranges for any
+ * necessary return value conversions
+ * @throws NullPointerException if either argument is null
+ * @throws WrongMethodTypeException if the conversion cannot be made
+ * @see MethodHandle#asType
+ */
public static
- MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { return null; }
+ MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) {
+ explicitCastArgumentsChecks(target, newType);
+ // use the asTypeCache when possible:
+ MethodType oldType = target.type();
+ if (oldType == newType) return target;
+ if (oldType.explicitCastEquivalentToAsType(newType)) {
+ return target.asFixedArity().asType(newType);
+ }
+ return new Transformers.ExplicitCastArguments(target, newType);
+ }
+
+ private static void explicitCastArgumentsChecks(MethodHandle target, MethodType newType) {
+ if (target.type().parameterCount() != newType.parameterCount()) {
+ throw new WrongMethodTypeException("cannot explicitly cast " + target + " to " + newType);
+ }
+ }
+
+ /**
+ * Produces a method handle which adapts the calling sequence of the
+ * given method handle to a new type, by reordering the arguments.
+ * The resulting method handle is guaranteed to report a type
+ * which is equal to the desired new type.
+ * <p>
+ * The given array controls the reordering.
+ * Call {@code #I} the number of incoming parameters (the value
+ * {@code newType.parameterCount()}, and call {@code #O} the number
+ * of outgoing parameters (the value {@code target.type().parameterCount()}).
+ * Then the length of the reordering array must be {@code #O},
+ * and each element must be a non-negative number less than {@code #I}.
+ * For every {@code N} less than {@code #O}, the {@code N}-th
+ * outgoing argument will be taken from the {@code I}-th incoming
+ * argument, where {@code I} is {@code reorder[N]}.
+ * <p>
+ * No argument or return value conversions are applied.
+ * The type of each incoming argument, as determined by {@code newType},
+ * must be identical to the type of the corresponding outgoing parameter
+ * or parameters in the target method handle.
+ * The return type of {@code newType} must be identical to the return
+ * type of the original target.
+ * <p>
+ * The reordering array need not specify an actual permutation.
+ * An incoming argument will be duplicated if its index appears
+ * more than once in the array, and an incoming argument will be dropped
+ * if its index does not appear in the array.
+ * As in the case of {@link #dropArguments(MethodHandle,int,List) dropArguments},
+ * incoming arguments which are not mentioned in the reordering array
+ * are may be any type, as determined only by {@code newType}.
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodType intfn1 = methodType(int.class, int.class);
+MethodType intfn2 = methodType(int.class, int.class, int.class);
+MethodHandle sub = ... (int x, int y) -> (x-y) ...;
+assert(sub.type().equals(intfn2));
+MethodHandle sub1 = permuteArguments(sub, intfn2, 0, 1);
+MethodHandle rsub = permuteArguments(sub, intfn2, 1, 0);
+assert((int)rsub.invokeExact(1, 100) == 99);
+MethodHandle add = ... (int x, int y) -> (x+y) ...;
+assert(add.type().equals(intfn2));
+MethodHandle twice = permuteArguments(add, intfn1, 0, 0);
+assert(twice.type().equals(intfn1));
+assert((int)twice.invokeExact(21) == 42);
+ * }</pre></blockquote>
+ * @param target the method handle to invoke after arguments are reordered
+ * @param newType the expected type of the new method handle
+ * @param reorder an index array which controls the reordering
+ * @return a method handle which delegates to the target after it
+ * drops unused arguments and moves and/or duplicates the other arguments
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if the index array length is not equal to
+ * the arity of the target, or if any index array element
+ * not a valid index for a parameter of {@code newType},
+ * or if two corresponding parameter types in
+ * {@code target.type()} and {@code newType} are not identical,
+ */
public static
- MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { return null; }
+ MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) {
+ reorder = reorder.clone(); // get a private copy
+ MethodType oldType = target.type();
+ permuteArgumentChecks(reorder, newType, oldType);
+
+ return new Transformers.PermuteArguments(newType, target, reorder);
+ }
+
+ // Android-changed: findFirstDupOrDrop is unused and removed.
+ // private static int findFirstDupOrDrop(int[] reorder, int newArity);
+
+ private static boolean permuteArgumentChecks(int[] reorder, MethodType newType, MethodType oldType) {
+ if (newType.returnType() != oldType.returnType())
+ throw newIllegalArgumentException("return types do not match",
+ oldType, newType);
+ if (reorder.length == oldType.parameterCount()) {
+ int limit = newType.parameterCount();
+ boolean bad = false;
+ for (int j = 0; j < reorder.length; j++) {
+ int i = reorder[j];
+ if (i < 0 || i >= limit) {
+ bad = true; break;
+ }
+ Class<?> src = newType.parameterType(i);
+ Class<?> dst = oldType.parameterType(j);
+ if (src != dst)
+ throw newIllegalArgumentException("parameter types do not match after reorder",
+ oldType, newType);
+ }
+ if (!bad) return true;
+ }
+ throw newIllegalArgumentException("bad reorder array: "+Arrays.toString(reorder));
+ }
+ /**
+ * Produces a method handle of the requested return type which returns the given
+ * constant value every time it is invoked.
+ * <p>
+ * Before the method handle is returned, the passed-in value is converted to the requested type.
+ * If the requested type is primitive, widening primitive conversions are attempted,
+ * else reference conversions are attempted.
+ * <p>The returned method handle is equivalent to {@code identity(type).bindTo(value)}.
+ * @param type the return type of the desired method handle
+ * @param value the value to return
+ * @return a method handle of the given return type and no arguments, which always returns the given value
+ * @throws NullPointerException if the {@code type} argument is null
+ * @throws ClassCastException if the value cannot be converted to the required return type
+ * @throws IllegalArgumentException if the given type is {@code void.class}
+ */
public static
- MethodHandle constant(Class<?> type, Object value) { return null; }
+ MethodHandle constant(Class<?> type, Object value) {
+ if (type.isPrimitive()) {
+ if (type == void.class)
+ throw newIllegalArgumentException("void type");
+ Wrapper w = Wrapper.forPrimitiveType(type);
+ value = w.convert(value, type);
+ }
+
+ return new Transformers.Constant(type, value);
+ }
+ /**
+ * Produces a method handle which returns its sole argument when invoked.
+ * @param type the type of the sole parameter and return value of the desired method handle
+ * @return a unary method handle which accepts and returns the given type
+ * @throws NullPointerException if the argument is null
+ * @throws IllegalArgumentException if the given type is {@code void.class}
+ */
public static
- MethodHandle identity(Class<?> type) { return null; }
+ MethodHandle identity(Class<?> type) {
+ if (type == null) {
+ throw new NullPointerException("type == null");
+ }
+ if (type.isPrimitive()) {
+ try {
+ return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class, "identity",
+ MethodType.methodType(type, type));
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ return new Transformers.ReferenceIdentity(type);
+ }
+
+ /** @hide */ public static byte identity(byte val) { return val; }
+ /** @hide */ public static boolean identity(boolean val) { return val; }
+ /** @hide */ public static char identity(char val) { return val; }
+ /** @hide */ public static short identity(short val) { return val; }
+ /** @hide */ public static int identity(int val) { return val; }
+ /** @hide */ public static long identity(long val) { return val; }
+ /** @hide */ public static float identity(float val) { return val; }
+ /** @hide */ public static double identity(double val) { return val; }
+
+ /**
+ * Provides a target method handle with one or more <em>bound arguments</em>
+ * in advance of the method handle's invocation.
+ * The formal parameters to the target corresponding to the bound
+ * arguments are called <em>bound parameters</em>.
+ * Returns a new method handle which saves away the bound arguments.
+ * When it is invoked, it receives arguments for any non-bound parameters,
+ * binds the saved arguments to their corresponding parameters,
+ * and calls the original target.
+ * <p>
+ * The type of the new method handle will drop the types for the bound
+ * parameters from the original target type, since the new method handle
+ * will no longer require those arguments to be supplied by its callers.
+ * <p>
+ * Each given argument object must match the corresponding bound parameter type.
+ * If a bound parameter type is a primitive, the argument object
+ * must be a wrapper, and will be unboxed to produce the primitive value.
+ * <p>
+ * The {@code pos} argument selects which parameters are to be bound.
+ * It may range between zero and <i>N-L</i> (inclusively),
+ * where <i>N</i> is the arity of the target method handle
+ * and <i>L</i> is the length of the values array.
+ * @param target the method handle to invoke after the argument is inserted
+ * @param pos where to insert the argument (zero for the first)
+ * @param values the series of arguments to insert
+ * @return a method handle which inserts an additional argument,
+ * before calling the original method handle
+ * @throws NullPointerException if the target or the {@code values} array is null
+ * @see MethodHandle#bindTo
+ */
public static
- MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { return null; }
+ MethodHandle insertArguments(MethodHandle target, int pos, Object... values) {
+ int insCount = values.length;
+ Class<?>[] ptypes = insertArgumentsChecks(target, insCount, pos);
+ if (insCount == 0) {
+ return target;
+ }
+
+ // Throw ClassCastExceptions early if we can't cast any of the provided values
+ // to the required type.
+ for (int i = 0; i < insCount; i++) {
+ final Class<?> ptype = ptypes[pos + i];
+ if (!ptype.isPrimitive()) {
+ ptypes[pos + i].cast(values[i]);
+ } else {
+ // Will throw a ClassCastException if something terrible happens.
+ values[i] = Wrapper.forPrimitiveType(ptype).convert(values[i], ptype);
+ }
+ }
+ return new Transformers.InsertArguments(target, pos, values);
+ }
+
+ // Android-changed: insertArgumentPrimitive is unused.
+ //
+ // private static BoundMethodHandle insertArgumentPrimitive(BoundMethodHandle result, int pos,
+ // Class<?> ptype, Object value) {
+ // Wrapper w = Wrapper.forPrimitiveType(ptype);
+ // // perform unboxing and/or primitive conversion
+ // value = w.convert(value, ptype);
+ // switch (w) {
+ // case INT: return result.bindArgumentI(pos, (int)value);
+ // case LONG: return result.bindArgumentJ(pos, (long)value);
+ // case FLOAT: return result.bindArgumentF(pos, (float)value);
+ // case DOUBLE: return result.bindArgumentD(pos, (double)value);
+ // default: return result.bindArgumentI(pos, ValueConversions.widenSubword(value));
+ // }
+ // }
+
+ private static Class<?>[] insertArgumentsChecks(MethodHandle target, int insCount, int pos) throws RuntimeException {
+ MethodType oldType = target.type();
+ int outargs = oldType.parameterCount();
+ int inargs = outargs - insCount;
+ if (inargs < 0)
+ throw newIllegalArgumentException("too many values to insert");
+ if (pos < 0 || pos > inargs)
+ throw newIllegalArgumentException("no argument type to append");
+ return oldType.ptypes();
+ }
+
+ /**
+ * Produces a method handle which will discard some dummy arguments
+ * before calling some other specified <i>target</i> method handle.
+ * The type of the new method handle will be the same as the target's type,
+ * except it will also include the dummy argument types,
+ * at some given position.
+ * <p>
+ * The {@code pos} argument may range between zero and <i>N</i>,
+ * where <i>N</i> is the arity of the target.
+ * If {@code pos} is zero, the dummy arguments will precede
+ * the target's real arguments; if {@code pos} is <i>N</i>
+ * they will come after.
+ * <p>
+ * <b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodType bigType = cat.type().insertParameterTypes(0, int.class, String.class);
+MethodHandle d0 = dropArguments(cat, 0, bigType.parameterList().subList(0,2));
+assertEquals(bigType, d0.type());
+assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z"));
+ * }</pre></blockquote>
+ * <p>
+ * This method is also equivalent to the following code:
+ * <blockquote><pre>
+ * {@link #dropArguments(MethodHandle,int,Class...) dropArguments}{@code (target, pos, valueTypes.toArray(new Class[0]))}
+ * </pre></blockquote>
+ * @param target the method handle to invoke after the arguments are dropped
+ * @param valueTypes the type(s) of the argument(s) to drop
+ * @param pos position of first argument to drop (zero for the leftmost)
+ * @return a method handle which drops arguments of the given types,
+ * before calling the original method handle
+ * @throws NullPointerException if the target is null,
+ * or if the {@code valueTypes} list or any of its elements is null
+ * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
+ * or if {@code pos} is negative or greater than the arity of the target,
+ * or if the new method handle's type would have too many parameters
+ */
public static
- MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { return null; }
+ MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
+ valueTypes = copyTypes(valueTypes);
+ MethodType oldType = target.type(); // get NPE
+ int dropped = dropArgumentChecks(oldType, pos, valueTypes);
+
+ MethodType newType = oldType.insertParameterTypes(pos, valueTypes);
+ if (dropped == 0) {
+ return target;
+ }
+
+ return new Transformers.DropArguments(newType, target, pos, valueTypes.size());
+ }
+
+ private static List<Class<?>> copyTypes(List<Class<?>> types) {
+ Object[] a = types.toArray();
+ return Arrays.asList(Arrays.copyOf(a, a.length, Class[].class));
+ }
+
+ private static int dropArgumentChecks(MethodType oldType, int pos, List<Class<?>> valueTypes) {
+ int dropped = valueTypes.size();
+ MethodType.checkSlotCount(dropped);
+ int outargs = oldType.parameterCount();
+ int inargs = outargs + dropped;
+ if (pos < 0 || pos > outargs)
+ throw newIllegalArgumentException("no argument type to remove"
+ + Arrays.asList(oldType, pos, valueTypes, inargs, outargs)
+ );
+ return dropped;
+ }
+ /**
+ * Produces a method handle which will discard some dummy arguments
+ * before calling some other specified <i>target</i> method handle.
+ * The type of the new method handle will be the same as the target's type,
+ * except it will also include the dummy argument types,
+ * at some given position.
+ * <p>
+ * The {@code pos} argument may range between zero and <i>N</i>,
+ * where <i>N</i> is the arity of the target.
+ * If {@code pos} is zero, the dummy arguments will precede
+ * the target's real arguments; if {@code pos} is <i>N</i>
+ * they will come after.
+ * <p>
+ * <b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle d0 = dropArguments(cat, 0, String.class);
+assertEquals("yz", (String) d0.invokeExact("x", "y", "z"));
+MethodHandle d1 = dropArguments(cat, 1, String.class);
+assertEquals("xz", (String) d1.invokeExact("x", "y", "z"));
+MethodHandle d2 = dropArguments(cat, 2, String.class);
+assertEquals("xy", (String) d2.invokeExact("x", "y", "z"));
+MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class);
+assertEquals("xz", (String) d12.invokeExact("x", 12, true, "z"));
+ * }</pre></blockquote>
+ * <p>
+ * This method is also equivalent to the following code:
+ * <blockquote><pre>
+ * {@link #dropArguments(MethodHandle,int,List) dropArguments}{@code (target, pos, Arrays.asList(valueTypes))}
+ * </pre></blockquote>
+ * @param target the method handle to invoke after the arguments are dropped
+ * @param valueTypes the type(s) of the argument(s) to drop
+ * @param pos position of first argument to drop (zero for the leftmost)
+ * @return a method handle which drops arguments of the given types,
+ * before calling the original method handle
+ * @throws NullPointerException if the target is null,
+ * or if the {@code valueTypes} array or any of its elements is null
+ * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
+ * or if {@code pos} is negative or greater than the arity of the target,
+ * or if the new method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
public static
- MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) { return null; }
+ MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
+ return dropArguments(target, pos, Arrays.asList(valueTypes));
+ }
+ /**
+ * Adapts a target method handle by pre-processing
+ * one or more of its arguments, each with its own unary filter function,
+ * and then calling the target with each pre-processed argument
+ * replaced by the result of its corresponding filter function.
+ * <p>
+ * The pre-processing is performed by one or more method handles,
+ * specified in the elements of the {@code filters} array.
+ * The first element of the filter array corresponds to the {@code pos}
+ * argument of the target, and so on in sequence.
+ * <p>
+ * Null arguments in the array are treated as identity functions,
+ * and the corresponding arguments left unchanged.
+ * (If there are no non-null elements in the array, the original target is returned.)
+ * Each filter is applied to the corresponding argument of the adapter.
+ * <p>
+ * If a filter {@code F} applies to the {@code N}th argument of
+ * the target, then {@code F} must be a method handle which
+ * takes exactly one argument. The type of {@code F}'s sole argument
+ * replaces the corresponding argument type of the target
+ * in the resulting adapted method handle.
+ * The return type of {@code F} must be identical to the corresponding
+ * parameter type of the target.
+ * <p>
+ * It is an error if there are elements of {@code filters}
+ * (null or not)
+ * which do not correspond to argument positions in the target.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+MethodHandle upcase = lookup().findVirtual(String.class,
+ "toUpperCase", methodType(String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle f0 = filterArguments(cat, 0, upcase);
+assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy
+MethodHandle f1 = filterArguments(cat, 1, upcase);
+assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY
+MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);
+assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * V target(P... p, A[i]... a[i], B... b);
+ * A[i] filter[i](V[i]);
+ * T adapter(P... p, V[i]... v[i], B... b) {
+ * return target(p..., f[i](v[i])..., b...);
+ * }
+ * }</pre></blockquote>
+ *
+ * @param target the method handle to invoke after arguments are filtered
+ * @param pos the position of the first argument to filter
+ * @param filters method handles to call initially on filtered arguments
+ * @return method handle which incorporates the specified argument filtering logic
+ * @throws NullPointerException if the target is null
+ * or if the {@code filters} array is null
+ * @throws IllegalArgumentException if a non-null element of {@code filters}
+ * does not match a corresponding argument type of target as described above,
+ * or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()},
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
public static
- MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { return null; }
+ MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) {
+ filterArgumentsCheckArity(target, pos, filters);
+
+ for (int i = 0; i < filters.length; ++i) {
+ filterArgumentChecks(target, i + pos, filters[i]);
+ }
+
+ return new Transformers.FilterArguments(target, pos, filters);
+ }
+
+ private static void filterArgumentsCheckArity(MethodHandle target, int pos, MethodHandle[] filters) {
+ MethodType targetType = target.type();
+ int maxPos = targetType.parameterCount();
+ if (pos + filters.length > maxPos)
+ throw newIllegalArgumentException("too many filters");
+ }
+
+ private static void filterArgumentChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
+ MethodType targetType = target.type();
+ MethodType filterType = filter.type();
+ if (filterType.parameterCount() != 1
+ || filterType.returnType() != targetType.parameterType(pos))
+ throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+ }
+
+ /**
+ * Adapts a target method handle by pre-processing
+ * a sub-sequence of its arguments with a filter (another method handle).
+ * The pre-processed arguments are replaced by the result (if any) of the
+ * filter function.
+ * The target is then called on the modified (usually shortened) argument list.
+ * <p>
+ * If the filter returns a value, the target must accept that value as
+ * its argument in position {@code pos}, preceded and/or followed by
+ * any arguments not passed to the filter.
+ * If the filter returns void, the target must accept all arguments
+ * not passed to the filter.
+ * No arguments are reordered, and a result returned from the filter
+ * replaces (in order) the whole subsequence of arguments originally
+ * passed to the adapter.
+ * <p>
+ * The argument types (if any) of the filter
+ * replace zero or one argument types of the target, at position {@code pos},
+ * in the resulting adapted method handle.
+ * The return type of the filter (if any) must be identical to the
+ * argument type of the target at position {@code pos}, and that target argument
+ * is supplied by the return value of the filter.
+ * <p>
+ * In all cases, {@code pos} must be greater than or equal to zero, and
+ * {@code pos} must also be less than or equal to the target's arity.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle deepToString = publicLookup()
+ .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+
+MethodHandle ts1 = deepToString.asCollector(String[].class, 1);
+assertEquals("[strange]", (String) ts1.invokeExact("strange"));
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals("[up, down]", (String) ts2.invokeExact("up", "down"));
+
+MethodHandle ts3 = deepToString.asCollector(String[].class, 3);
+MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2);
+assertEquals("[top, [up, down], strange]",
+ (String) ts3_ts2.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1);
+assertEquals("[top, [up, down], [strange]]",
+ (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3);
+assertEquals("[top, [[up, down, strange], charm], bottom]",
+ (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom"));
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * T target(A...,V,C...);
+ * V filter(B...);
+ * T adapter(A... a,B... b,C... c) {
+ * V v = filter(b...);
+ * return target(a...,v,c...);
+ * }
+ * // and if the filter has no arguments:
+ * T target2(A...,V,C...);
+ * V filter2();
+ * T adapter2(A... a,C... c) {
+ * V v = filter2();
+ * return target2(a...,v,c...);
+ * }
+ * // and if the filter has a void return:
+ * T target3(A...,C...);
+ * void filter3(B...);
+ * void adapter3(A... a,B... b,C... c) {
+ * filter3(b...);
+ * return target3(a...,c...);
+ * }
+ * }</pre></blockquote>
+ * <p>
+ * A collection adapter {@code collectArguments(mh, 0, coll)} is equivalent to
+ * one which first "folds" the affected arguments, and then drops them, in separate
+ * steps as follows:
+ * <blockquote><pre>{@code
+ * mh = MethodHandles.dropArguments(mh, 1, coll.type().parameterList()); //step 2
+ * mh = MethodHandles.foldArguments(mh, coll); //step 1
+ * }</pre></blockquote>
+ * If the target method handle consumes no arguments besides than the result
+ * (if any) of the filter {@code coll}, then {@code collectArguments(mh, 0, coll)}
+ * is equivalent to {@code filterReturnValue(coll, mh)}.
+ * If the filter method handle {@code coll} consumes one argument and produces
+ * a non-void result, then {@code collectArguments(mh, N, coll)}
+ * is equivalent to {@code filterArguments(mh, N, coll)}.
+ * Other equivalences are possible but would require argument permutation.
+ *
+ * @param target the method handle to invoke after filtering the subsequence of arguments
+ * @param pos the position of the first adapter argument to pass to the filter,
+ * and/or the target argument which receives the result of the filter
+ * @param filter method handle to call on the subsequence of arguments
+ * @return method handle which incorporates the specified argument subsequence filtering logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if the return type of {@code filter}
+ * is non-void and is not the same as the {@code pos} argument of the target,
+ * or if {@code pos} is not between 0 and the target's arity, inclusive,
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ * @see MethodHandles#foldArguments
+ * @see MethodHandles#filterArguments
+ * @see MethodHandles#filterReturnValue
+ */
public static
- MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { return null; }
+ MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) {
+ MethodType newType = collectArgumentsChecks(target, pos, filter);
+ return new Transformers.CollectArguments(target, filter, pos, newType);
+ }
+
+ private static MethodType collectArgumentsChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
+ MethodType targetType = target.type();
+ MethodType filterType = filter.type();
+ Class<?> rtype = filterType.returnType();
+ List<Class<?>> filterArgs = filterType.parameterList();
+ if (rtype == void.class) {
+ return targetType.insertParameterTypes(pos, filterArgs);
+ }
+ if (rtype != targetType.parameterType(pos)) {
+ throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+ }
+ return targetType.dropParameterTypes(pos, pos+1).insertParameterTypes(pos, filterArgs);
+ }
+ /**
+ * Adapts a target method handle by post-processing
+ * its return value (if any) with a filter (another method handle).
+ * The result of the filter is returned from the adapter.
+ * <p>
+ * If the target returns a value, the filter must accept that value as
+ * its only argument.
+ * If the target returns void, the filter must accept no arguments.
+ * <p>
+ * The return type of the filter
+ * replaces the return type of the target
+ * in the resulting adapted method handle.
+ * The argument type of the filter (if any) must be identical to the
+ * return type of the target.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+MethodHandle length = lookup().findVirtual(String.class,
+ "length", methodType(int.class));
+System.out.println((String) cat.invokeExact("x", "y")); // xy
+MethodHandle f0 = filterReturnValue(cat, length);
+System.out.println((int) f0.invokeExact("x", "y")); // 2
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * V target(A...);
+ * T filter(V);
+ * T adapter(A... a) {
+ * V v = target(a...);
+ * return filter(v);
+ * }
+ * // and if the target has a void return:
+ * void target2(A...);
+ * T filter2();
+ * T adapter2(A... a) {
+ * target2(a...);
+ * return filter2();
+ * }
+ * // and if the filter has a void return:
+ * V target3(A...);
+ * void filter3(V);
+ * void adapter3(A... a) {
+ * V v = target3(a...);
+ * filter3(v);
+ * }
+ * }</pre></blockquote>
+ * @param target the method handle to invoke before filtering the return value
+ * @param filter method handle to call on the return value
+ * @return method handle which incorporates the specified return value filtering logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if the argument list of {@code filter}
+ * does not match the return type of target as described above
+ */
public static
- MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { return null; }
+ MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) {
+ MethodType targetType = target.type();
+ MethodType filterType = filter.type();
+ filterReturnValueChecks(targetType, filterType);
+ return new Transformers.FilterReturnValue(target, filter);
+ }
+
+ private static void filterReturnValueChecks(MethodType targetType, MethodType filterType) throws RuntimeException {
+ Class<?> rtype = targetType.returnType();
+ int filterValues = filterType.parameterCount();
+ if (filterValues == 0
+ ? (rtype != void.class)
+ : (rtype != filterType.parameterType(0) || filterValues != 1))
+ throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+ }
+
+ /**
+ * Adapts a target method handle by pre-processing
+ * some of its arguments, and then calling the target with
+ * the result of the pre-processing, inserted into the original
+ * sequence of arguments.
+ * <p>
+ * The pre-processing is performed by {@code combiner}, a second method handle.
+ * Of the arguments passed to the adapter, the first {@code N} arguments
+ * are copied to the combiner, which is then called.
+ * (Here, {@code N} is defined as the parameter count of the combiner.)
+ * After this, control passes to the target, with any result
+ * from the combiner inserted before the original {@code N} incoming
+ * arguments.
+ * <p>
+ * If the combiner returns a value, the first parameter type of the target
+ * must be identical with the return type of the combiner, and the next
+ * {@code N} parameter types of the target must exactly match the parameters
+ * of the combiner.
+ * <p>
+ * If the combiner has a void return, no result will be inserted,
+ * and the first {@code N} parameter types of the target
+ * must exactly match the parameters of the combiner.
+ * <p>
+ * The resulting adapter is the same type as the target, except that the
+ * first parameter type is dropped,
+ * if it corresponds to the result of the combiner.
+ * <p>
+ * (Note that {@link #dropArguments(MethodHandle,int,List) dropArguments} can be used to remove any arguments
+ * that either the combiner or the target does not wish to receive.
+ * If some of the incoming arguments are destined only for the combiner,
+ * consider using {@link MethodHandle#asCollector asCollector} instead, since those
+ * arguments will not need to be live on the stack on entry to the
+ * target.)
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
+ "println", methodType(void.class, String.class))
+ .bindTo(System.out);
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
+MethodHandle catTrace = foldArguments(cat, trace);
+// also prints "boo":
+assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * // there are N arguments in A...
+ * T target(V, A[N]..., B...);
+ * V combiner(A...);
+ * T adapter(A... a, B... b) {
+ * V v = combiner(a...);
+ * return target(v, a..., b...);
+ * }
+ * // and if the combiner has a void return:
+ * T target2(A[N]..., B...);
+ * void combiner2(A...);
+ * T adapter2(A... a, B... b) {
+ * combiner2(a...);
+ * return target2(a..., b...);
+ * }
+ * }</pre></blockquote>
+ * @param target the method handle to invoke after arguments are combined
+ * @param combiner method handle to call initially on the incoming arguments
+ * @return method handle which incorporates the specified argument folding logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if {@code combiner}'s return type
+ * is non-void and not the same as the first argument type of
+ * the target, or if the initial {@code N} argument types
+ * of the target
+ * (skipping one matching the {@code combiner}'s return type)
+ * are not identical with the argument types of {@code combiner}
+ */
public static
- MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { return null; }
+ MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) {
+ int foldPos = 0;
+ MethodType targetType = target.type();
+ MethodType combinerType = combiner.type();
+ Class<?> rtype = foldArgumentChecks(foldPos, targetType, combinerType);
+
+ return new Transformers.FoldArguments(target, combiner);
+ }
+
+ private static Class<?> foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) {
+ int foldArgs = combinerType.parameterCount();
+ Class<?> rtype = combinerType.returnType();
+ int foldVals = rtype == void.class ? 0 : 1;
+ int afterInsertPos = foldPos + foldVals;
+ boolean ok = (targetType.parameterCount() >= afterInsertPos + foldArgs);
+ if (ok && !(combinerType.parameterList()
+ .equals(targetType.parameterList().subList(afterInsertPos,
+ afterInsertPos + foldArgs))))
+ ok = false;
+ if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(0))
+ ok = false;
+ if (!ok)
+ throw misMatchedTypes("target and combiner types", targetType, combinerType);
+ return rtype;
+ }
+ /**
+ * Makes a method handle which adapts a target method handle,
+ * by guarding it with a test, a boolean-valued method handle.
+ * If the guard fails, a fallback handle is called instead.
+ * All three method handles must have the same corresponding
+ * argument and return types, except that the return type
+ * of the test must be boolean, and the test is allowed
+ * to have fewer arguments than the other two method handles.
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * boolean test(A...);
+ * T target(A...,B...);
+ * T fallback(A...,B...);
+ * T adapter(A... a,B... b) {
+ * if (test(a...))
+ * return target(a..., b...);
+ * else
+ * return fallback(a..., b...);
+ * }
+ * }</pre></blockquote>
+ * Note that the test arguments ({@code a...} in the pseudocode) cannot
+ * be modified by execution of the test, and so are passed unchanged
+ * from the caller to the target or fallback as appropriate.
+ * @param test method handle used for test, must return boolean
+ * @param target method handle to call if test passes
+ * @param fallback method handle to call if test fails
+ * @return method handle which incorporates the specified if/then/else logic
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if {@code test} does not return boolean,
+ * or if all three method types do not match (with the return
+ * type of {@code test} changed to match that of the target).
+ */
public static
MethodHandle guardWithTest(MethodHandle test,
MethodHandle target,
- MethodHandle fallback) { return null; }
+ MethodHandle fallback) {
+ MethodType gtype = test.type();
+ MethodType ttype = target.type();
+ MethodType ftype = fallback.type();
+ if (!ttype.equals(ftype))
+ throw misMatchedTypes("target and fallback types", ttype, ftype);
+ if (gtype.returnType() != boolean.class)
+ throw newIllegalArgumentException("guard type is not a predicate "+gtype);
+ List<Class<?>> targs = ttype.parameterList();
+ List<Class<?>> gargs = gtype.parameterList();
+ if (!targs.equals(gargs)) {
+ int gpc = gargs.size(), tpc = targs.size();
+ if (gpc >= tpc || !targs.subList(0, gpc).equals(gargs))
+ throw misMatchedTypes("target and test types", ttype, gtype);
+ test = dropArguments(test, gpc, targs.subList(gpc, tpc));
+ gtype = test.type();
+ }
+ return new Transformers.GuardWithTest(test, target, fallback);
+ }
+
+ static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) {
+ return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2);
+ }
+
+ /**
+ * Makes a method handle which adapts a target method handle,
+ * by running it inside an exception handler.
+ * If the target returns normally, the adapter returns that value.
+ * If an exception matching the specified type is thrown, the fallback
+ * handle is called instead on the exception, plus the original arguments.
+ * <p>
+ * The target and handler must have the same corresponding
+ * argument and return types, except that handler may omit trailing arguments
+ * (similarly to the predicate in {@link #guardWithTest guardWithTest}).
+ * Also, the handler must have an extra leading parameter of {@code exType} or a supertype.
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * T target(A..., B...);
+ * T handler(ExType, A...);
+ * T adapter(A... a, B... b) {
+ * try {
+ * return target(a..., b...);
+ * } catch (ExType ex) {
+ * return handler(ex, a...);
+ * }
+ * }
+ * }</pre></blockquote>
+ * Note that the saved arguments ({@code a...} in the pseudocode) cannot
+ * be modified by execution of the target, and so are passed unchanged
+ * from the caller to the handler, if the handler is invoked.
+ * <p>
+ * The target and handler must return the same type, even if the handler
+ * always throws. (This might happen, for instance, because the handler
+ * is simulating a {@code finally} clause).
+ * To create such a throwing handler, compose the handler creation logic
+ * with {@link #throwException throwException},
+ * in order to create a method handle of the correct return type.
+ * @param target method handle to call
+ * @param exType the type of exception which the handler will catch
+ * @param handler method handle to call if a matching exception is thrown
+ * @return method handle which incorporates the specified try/catch logic
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if {@code handler} does not accept
+ * the given exception type, or if the method handle types do
+ * not match in their return types and their
+ * corresponding parameters
+ */
public static
MethodHandle catchException(MethodHandle target,
Class<? extends Throwable> exType,
- MethodHandle handler) { return null; }
+ MethodHandle handler) {
+ MethodType ttype = target.type();
+ MethodType htype = handler.type();
+ if (htype.parameterCount() < 1 ||
+ !htype.parameterType(0).isAssignableFrom(exType))
+ throw newIllegalArgumentException("handler does not accept exception type "+exType);
+ if (htype.returnType() != ttype.returnType())
+ throw misMatchedTypes("target and handler return types", ttype, htype);
+ List<Class<?>> targs = ttype.parameterList();
+ List<Class<?>> hargs = htype.parameterList();
+ hargs = hargs.subList(1, hargs.size()); // omit leading parameter from handler
+ if (!targs.equals(hargs)) {
+ int hpc = hargs.size(), tpc = targs.size();
+ if (hpc >= tpc || !targs.subList(0, hpc).equals(hargs))
+ throw misMatchedTypes("target and handler types", ttype, htype);
+ }
+
+ return new Transformers.CatchException(target, handler, exType);
+ }
+ /**
+ * Produces a method handle which will throw exceptions of the given {@code exType}.
+ * The method handle will accept a single argument of {@code exType},
+ * and immediately throw it as an exception.
+ * The method type will nominally specify a return of {@code returnType}.
+ * The return type may be anything convenient: It doesn't matter to the
+ * method handle's behavior, since it will never return normally.
+ * @param returnType the return type of the desired method handle
+ * @param exType the parameter type of the desired method handle
+ * @return method handle which can throw the given exceptions
+ * @throws NullPointerException if either argument is null
+ */
public static
- MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { return null; }
+ MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) {
+ if (!Throwable.class.isAssignableFrom(exType))
+ throw new ClassCastException(exType.getName());
+
+ return new Transformers.AlwaysThrow(returnType, exType);
+ }
}
diff --git a/java/lang/invoke/MethodType.java b/java/lang/invoke/MethodType.java
index 4cb5c226..bfa7ccd5 100644
--- a/java/lang/invoke/MethodType.java
+++ b/java/lang/invoke/MethodType.java
@@ -25,78 +25,1227 @@
package java.lang.invoke;
+import sun.invoke.util.Wrapper;
+import java.lang.ref.WeakReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import sun.invoke.util.BytecodeDescriptor;
+import static java.lang.invoke.MethodHandleStatics.*;
+/**
+ * A method type represents the arguments and return type accepted and
+ * returned by a method handle, or the arguments and return type passed
+ * and expected by a method handle caller. Method types must be properly
+ * matched between a method handle and all its callers,
+ * and the JVM's operations enforce this matching at, specifically
+ * during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact}
+ * and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution
+ * of {@code invokedynamic} instructions.
+ * <p>
+ * The structure is a return type accompanied by any number of parameter types.
+ * The types (primitive, {@code void}, and reference) are represented by {@link Class} objects.
+ * (For ease of exposition, we treat {@code void} as if it were a type.
+ * In fact, it denotes the absence of a return type.)
+ * <p>
+ * All instances of {@code MethodType} are immutable.
+ * Two instances are completely interchangeable if they compare equal.
+ * Equality depends on pairwise correspondence of the return and parameter types and on nothing else.
+ * <p>
+ * This type can be created only by factory methods.
+ * All factory methods may cache values, though caching is not guaranteed.
+ * Some factory methods are static, while others are virtual methods which
+ * modify precursor method types, e.g., by changing a selected parameter.
+ * <p>
+ * Factory methods which operate on groups of parameter types
+ * are systematically presented in two versions, so that both Java arrays and
+ * Java lists can be used to work with groups of parameter types.
+ * The query methods {@code parameterArray} and {@code parameterList}
+ * also provide a choice between arrays and lists.
+ * <p>
+ * {@code MethodType} objects are sometimes derived from bytecode instructions
+ * such as {@code invokedynamic}, specifically from the type descriptor strings associated
+ * with the instructions in a class file's constant pool.
+ * <p>
+ * Like classes and strings, method types can also be represented directly
+ * in a class file's constant pool as constants.
+ * A method type may be loaded by an {@code ldc} instruction which refers
+ * to a suitable {@code CONSTANT_MethodType} constant pool entry.
+ * The entry refers to a {@code CONSTANT_Utf8} spelling for the descriptor string.
+ * (For full details on method type constants,
+ * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
+ * <p>
+ * When the JVM materializes a {@code MethodType} from a descriptor string,
+ * all classes named in the descriptor must be accessible, and will be loaded.
+ * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.)
+ * This loading may occur at any time before the {@code MethodType} object is first derived.
+ * @author John Rose, JSR 292 EG
+ */
public final
class MethodType implements java.io.Serializable {
+ private static final long serialVersionUID = 292L; // {rtype, {ptype...}}
+
+ // The rtype and ptypes fields define the structural identity of the method type:
+ private final Class<?> rtype;
+ private final Class<?>[] ptypes;
+
+ // The remaining fields are caches of various sorts:
+ private @Stable MethodTypeForm form; // erased form, plus cached data about primitives
+ private @Stable MethodType wrapAlt; // alternative wrapped/unwrapped version
+ // Android-changed: Remove adapter cache. We're not dynamically generating any
+ // adapters at this point.
+ // private @Stable Invokers invokers; // cache of handy higher-order adapters
+ private @Stable String methodDescriptor; // cache for toMethodDescriptorString
+
+ /**
+ * Check the given parameters for validity and store them into the final fields.
+ */
+ private MethodType(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
+ checkRtype(rtype);
+ checkPtypes(ptypes);
+ this.rtype = rtype;
+ // defensively copy the array passed in by the user
+ this.ptypes = trusted ? ptypes : Arrays.copyOf(ptypes, ptypes.length);
+ }
+
+ /**
+ * Construct a temporary unchecked instance of MethodType for use only as a key to the intern table.
+ * Does not check the given parameters for validity, and must be discarded after it is used as a searching key.
+ * The parameters are reversed for this constructor, so that is is not accidentally used.
+ */
+ private MethodType(Class<?>[] ptypes, Class<?> rtype) {
+ this.rtype = rtype;
+ this.ptypes = ptypes;
+ }
+
+ /*trusted*/ MethodTypeForm form() { return form; }
+ /*trusted*/ /** @hide */ public Class<?> rtype() { return rtype; }
+ /*trusted*/ /** @hide */ public Class<?>[] ptypes() { return ptypes; }
+ // Android-changed: Removed method setForm. It's unused in the JDK and there's no
+ // good reason to allow the form to be set externally.
+ //
+ // void setForm(MethodTypeForm f) { form = f; }
+
+ /** This number, mandated by the JVM spec as 255,
+ * is the maximum number of <em>slots</em>
+ * that any Java method can receive in its argument list.
+ * It limits both JVM signatures and method type objects.
+ * The longest possible invocation will look like
+ * {@code staticMethod(arg1, arg2, ..., arg255)} or
+ * {@code x.virtualMethod(arg1, arg2, ..., arg254)}.
+ */
+ /*non-public*/ static final int MAX_JVM_ARITY = 255; // this is mandated by the JVM spec.
+
+ /** This number is the maximum arity of a method handle, 254.
+ * It is derived from the absolute JVM-imposed arity by subtracting one,
+ * which is the slot occupied by the method handle itself at the
+ * beginning of the argument list used to invoke the method handle.
+ * The longest possible invocation will look like
+ * {@code mh.invoke(arg1, arg2, ..., arg254)}.
+ */
+ // Issue: Should we allow MH.invokeWithArguments to go to the full 255?
+ /*non-public*/ static final int MAX_MH_ARITY = MAX_JVM_ARITY-1; // deduct one for mh receiver
+
+ /** This number is the maximum arity of a method handle invoker, 253.
+ * It is derived from the absolute JVM-imposed arity by subtracting two,
+ * which are the slots occupied by invoke method handle, and the
+ * target method handle, which are both at the beginning of the argument
+ * list used to invoke the target method handle.
+ * The longest possible invocation will look like
+ * {@code invokermh.invoke(targetmh, arg1, arg2, ..., arg253)}.
+ */
+ /*non-public*/ static final int MAX_MH_INVOKER_ARITY = MAX_MH_ARITY-1; // deduct one more for invoker
+
+ private static void checkRtype(Class<?> rtype) {
+ Objects.requireNonNull(rtype);
+ }
+ private static void checkPtype(Class<?> ptype) {
+ Objects.requireNonNull(ptype);
+ if (ptype == void.class)
+ throw newIllegalArgumentException("parameter type cannot be void");
+ }
+ /** Return number of extra slots (count of long/double args). */
+ private static int checkPtypes(Class<?>[] ptypes) {
+ int slots = 0;
+ for (Class<?> ptype : ptypes) {
+ checkPtype(ptype);
+ if (ptype == double.class || ptype == long.class) {
+ slots++;
+ }
+ }
+ checkSlotCount(ptypes.length + slots);
+ return slots;
+ }
+ static void checkSlotCount(int count) {
+ assert((MAX_JVM_ARITY & (MAX_JVM_ARITY+1)) == 0);
+ // MAX_JVM_ARITY must be power of 2 minus 1 for following code trick to work:
+ if ((count & MAX_JVM_ARITY) != count)
+ throw newIllegalArgumentException("bad parameter count "+count);
+ }
+ private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) {
+ if (num instanceof Integer) num = "bad index: "+num;
+ return new IndexOutOfBoundsException(num.toString());
+ }
+
+ static final ConcurrentWeakInternSet<MethodType> internTable = new ConcurrentWeakInternSet<>();
+
+ static final Class<?>[] NO_PTYPES = {};
+
+ /**
+ * Finds or creates an instance of the given method type.
+ * @param rtype the return type
+ * @param ptypes the parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
+ * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
+ */
public static
MethodType methodType(Class<?> rtype, Class<?>[] ptypes) {
- return null;
+ return makeImpl(rtype, ptypes, false);
}
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param rtype the return type
+ * @param ptypes the parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
+ * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
+ */
public static
MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) {
- return null;
+ boolean notrust = false; // random List impl. could return evil ptypes array
+ return makeImpl(rtype, listToArray(ptypes), notrust);
}
+ private static Class<?>[] listToArray(List<Class<?>> ptypes) {
+ // sanity check the size before the toArray call, since size might be huge
+ checkSlotCount(ptypes.size());
+ return ptypes.toArray(NO_PTYPES);
+ }
+
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The leading parameter type is prepended to the remaining array.
+ * @param rtype the return type
+ * @param ptype0 the first parameter type
+ * @param ptypes the remaining parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is null
+ * @throws IllegalArgumentException if {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is {@code void.class}
+ */
public static
- MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) { return null; }
+ MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) {
+ Class<?>[] ptypes1 = new Class<?>[1+ptypes.length];
+ ptypes1[0] = ptype0;
+ System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length);
+ return makeImpl(rtype, ptypes1, true);
+ }
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The resulting method has no parameter types.
+ * @param rtype the return type
+ * @return a method type with the given return value
+ * @throws NullPointerException if {@code rtype} is null
+ */
public static
- MethodType methodType(Class<?> rtype) { return null; }
+ MethodType methodType(Class<?> rtype) {
+ return makeImpl(rtype, NO_PTYPES, true);
+ }
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The resulting method has the single given parameter type.
+ * @param rtype the return type
+ * @param ptype0 the parameter type
+ * @return a method type with the given return value and parameter type
+ * @throws NullPointerException if {@code rtype} or {@code ptype0} is null
+ * @throws IllegalArgumentException if {@code ptype0} is {@code void.class}
+ */
public static
- MethodType methodType(Class<?> rtype, Class<?> ptype0) { return null; }
+ MethodType methodType(Class<?> rtype, Class<?> ptype0) {
+ return makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
+ }
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The resulting method has the same parameter types as {@code ptypes},
+ * and the specified return type.
+ * @param rtype the return type
+ * @param ptypes the method type which supplies the parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptypes} is null
+ */
public static
- MethodType methodType(Class<?> rtype, MethodType ptypes) { return null; }
+ MethodType methodType(Class<?> rtype, MethodType ptypes) {
+ return makeImpl(rtype, ptypes.ptypes, true);
+ }
+
+ /**
+ * Sole factory method to find or create an interned method type.
+ * @param rtype desired return type
+ * @param ptypes desired parameter types
+ * @param trusted whether the ptypes can be used without cloning
+ * @return the unique method type of the desired structure
+ */
+ /*trusted*/ static
+ MethodType makeImpl(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
+ MethodType mt = internTable.get(new MethodType(ptypes, rtype));
+ if (mt != null)
+ return mt;
+ if (ptypes.length == 0) {
+ ptypes = NO_PTYPES; trusted = true;
+ }
+ mt = new MethodType(rtype, ptypes, trusted);
+ // promote the object to the Real Thing, and reprobe
+ mt.form = MethodTypeForm.findForm(mt);
+ return internTable.add(mt);
+ }
+ private static final MethodType[] objectOnlyTypes = new MethodType[20];
+ /**
+ * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All parameters and the return type will be {@code Object},
+ * except the final array parameter if any, which will be {@code Object[]}.
+ * @param objectArgCount number of parameters (excluding the final array parameter if any)
+ * @param finalArray whether there will be a trailing array parameter, of type {@code Object[]}
+ * @return a generally applicable method type, for all calls of the given fixed argument count and a collected array of further arguments
+ * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 (or 254, if {@code finalArray} is true)
+ * @see #genericMethodType(int)
+ */
public static
- MethodType genericMethodType(int objectArgCount, boolean finalArray) { return null; }
+ MethodType genericMethodType(int objectArgCount, boolean finalArray) {
+ MethodType mt;
+ checkSlotCount(objectArgCount);
+ int ivarargs = (!finalArray ? 0 : 1);
+ int ootIndex = objectArgCount*2 + ivarargs;
+ if (ootIndex < objectOnlyTypes.length) {
+ mt = objectOnlyTypes[ootIndex];
+ if (mt != null) return mt;
+ }
+ Class<?>[] ptypes = new Class<?>[objectArgCount + ivarargs];
+ Arrays.fill(ptypes, Object.class);
+ if (ivarargs != 0) ptypes[objectArgCount] = Object[].class;
+ mt = makeImpl(Object.class, ptypes, true);
+ if (ootIndex < objectOnlyTypes.length) {
+ objectOnlyTypes[ootIndex] = mt; // cache it here also!
+ }
+ return mt;
+ }
+ /**
+ * Finds or creates a method type whose components are all {@code Object}.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All parameters and the return type will be Object.
+ * @param objectArgCount number of parameters
+ * @return a generally applicable method type, for all calls of the given argument count
+ * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255
+ * @see #genericMethodType(int, boolean)
+ */
public static
- MethodType genericMethodType(int objectArgCount) { return null; }
+ MethodType genericMethodType(int objectArgCount) {
+ return genericMethodType(objectArgCount, false);
+ }
+
+ /**
+ * Finds or creates a method type with a single different parameter type.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param num the index (zero-based) of the parameter type to change
+ * @param nptype a new parameter type to replace the old one with
+ * @return the same type, except with the selected parameter changed
+ * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
+ * @throws IllegalArgumentException if {@code nptype} is {@code void.class}
+ * @throws NullPointerException if {@code nptype} is null
+ */
+ public MethodType changeParameterType(int num, Class<?> nptype) {
+ if (parameterType(num) == nptype) return this;
+ checkPtype(nptype);
+ Class<?>[] nptypes = ptypes.clone();
+ nptypes[num] = nptype;
+ return makeImpl(rtype, nptypes, true);
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param num the position (zero-based) of the inserted parameter type(s)
+ * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+ * @return the same type, except with the selected parameter(s) inserted
+ * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) {
+ int len = ptypes.length;
+ if (num < 0 || num > len)
+ throw newIndexOutOfBoundsException(num);
+ int ins = checkPtypes(ptypesToInsert);
+ checkSlotCount(parameterSlotCount() + ptypesToInsert.length + ins);
+ int ilen = ptypesToInsert.length;
+ if (ilen == 0) return this;
+ Class<?>[] nptypes = Arrays.copyOfRange(ptypes, 0, len+ilen);
+ System.arraycopy(nptypes, num, nptypes, num+ilen, len-num);
+ System.arraycopy(ptypesToInsert, 0, nptypes, num, ilen);
+ return makeImpl(rtype, nptypes, true);
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
+ * @return the same type, except with the selected parameter(s) appended
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType appendParameterTypes(Class<?>... ptypesToInsert) {
+ return insertParameterTypes(parameterCount(), ptypesToInsert);
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param num the position (zero-based) of the inserted parameter type(s)
+ * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+ * @return the same type, except with the selected parameter(s) inserted
+ * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) {
+ return insertParameterTypes(num, listToArray(ptypesToInsert));
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
+ * @return the same type, except with the selected parameter(s) appended
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) {
+ return insertParameterTypes(parameterCount(), ptypesToInsert);
+ }
+
+ /**
+ * Finds or creates a method type with modified parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param start the position (zero-based) of the first replaced parameter type(s)
+ * @param end the position (zero-based) after the last replaced parameter type(s)
+ * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+ * @return the same type, except with the selected parameter(s) replaced
+ * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
+ * or if {@code end} is negative or greater than {@code parameterCount()}
+ * or if {@code start} is greater than {@code end}
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ /*non-public*/ MethodType replaceParameterTypes(int start, int end, Class<?>... ptypesToInsert) {
+ if (start == end)
+ return insertParameterTypes(start, ptypesToInsert);
+ int len = ptypes.length;
+ if (!(0 <= start && start <= end && end <= len))
+ throw newIndexOutOfBoundsException("start="+start+" end="+end);
+ int ilen = ptypesToInsert.length;
+ if (ilen == 0)
+ return dropParameterTypes(start, end);
+ return dropParameterTypes(start, end).insertParameterTypes(start, ptypesToInsert);
+ }
+
+ /** Replace the last arrayLength parameter types with the component type of arrayType.
+ * @param arrayType any array type
+ * @param arrayLength the number of parameter types to change
+ * @return the resulting type
+ */
+ /*non-public*/ MethodType asSpreaderType(Class<?> arrayType, int arrayLength) {
+ assert(parameterCount() >= arrayLength);
+ int spreadPos = ptypes.length - arrayLength;
+ if (arrayLength == 0) return this; // nothing to change
+ if (arrayType == Object[].class) {
+ if (isGeneric()) return this; // nothing to change
+ if (spreadPos == 0) {
+ // no leading arguments to preserve; go generic
+ MethodType res = genericMethodType(arrayLength);
+ if (rtype != Object.class) {
+ res = res.changeReturnType(rtype);
+ }
+ return res;
+ }
+ }
+ Class<?> elemType = arrayType.getComponentType();
+ assert(elemType != null);
+ for (int i = spreadPos; i < ptypes.length; i++) {
+ if (ptypes[i] != elemType) {
+ Class<?>[] fixedPtypes = ptypes.clone();
+ Arrays.fill(fixedPtypes, i, ptypes.length, elemType);
+ return methodType(rtype, fixedPtypes);
+ }
+ }
+ return this; // arguments check out; no change
+ }
+
+ /** Return the leading parameter type, which must exist and be a reference.
+ * @return the leading parameter type, after error checks
+ */
+ /*non-public*/ Class<?> leadingReferenceParameter() {
+ Class<?> ptype;
+ if (ptypes.length == 0 ||
+ (ptype = ptypes[0]).isPrimitive())
+ throw newIllegalArgumentException("no leading reference parameter");
+ return ptype;
+ }
+
+ /** Delete the last parameter type and replace it with arrayLength copies of the component type of arrayType.
+ * @param arrayType any array type
+ * @param arrayLength the number of parameter types to insert
+ * @return the resulting type
+ */
+ /*non-public*/ MethodType asCollectorType(Class<?> arrayType, int arrayLength) {
+ assert(parameterCount() >= 1);
+ assert(lastParameterType().isAssignableFrom(arrayType));
+ MethodType res;
+ if (arrayType == Object[].class) {
+ res = genericMethodType(arrayLength);
+ if (rtype != Object.class) {
+ res = res.changeReturnType(rtype);
+ }
+ } else {
+ Class<?> elemType = arrayType.getComponentType();
+ assert(elemType != null);
+ res = methodType(rtype, Collections.nCopies(arrayLength, elemType));
+ }
+ if (ptypes.length == 1) {
+ return res;
+ } else {
+ return res.insertParameterTypes(0, parameterList().subList(0, ptypes.length-1));
+ }
+ }
+
+ /**
+ * Finds or creates a method type with some parameter types omitted.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param start the index (zero-based) of the first parameter type to remove
+ * @param end the index (greater than {@code start}) of the first parameter type after not to remove
+ * @return the same type, except with the selected parameter(s) removed
+ * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
+ * or if {@code end} is negative or greater than {@code parameterCount()}
+ * or if {@code start} is greater than {@code end}
+ */
+ public MethodType dropParameterTypes(int start, int end) {
+ int len = ptypes.length;
+ if (!(0 <= start && start <= end && end <= len))
+ throw newIndexOutOfBoundsException("start="+start+" end="+end);
+ if (start == end) return this;
+ Class<?>[] nptypes;
+ if (start == 0) {
+ if (end == len) {
+ // drop all parameters
+ nptypes = NO_PTYPES;
+ } else {
+ // drop initial parameter(s)
+ nptypes = Arrays.copyOfRange(ptypes, end, len);
+ }
+ } else {
+ if (end == len) {
+ // drop trailing parameter(s)
+ nptypes = Arrays.copyOfRange(ptypes, 0, start);
+ } else {
+ int tail = len - end;
+ nptypes = Arrays.copyOfRange(ptypes, 0, start + tail);
+ System.arraycopy(ptypes, end, nptypes, start, tail);
+ }
+ }
+ return makeImpl(rtype, nptypes, true);
+ }
+
+ /**
+ * Finds or creates a method type with a different return type.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param nrtype a return parameter type to replace the old one with
+ * @return the same type, except with the return type change
+ * @throws NullPointerException if {@code nrtype} is null
+ */
+ public MethodType changeReturnType(Class<?> nrtype) {
+ if (returnType() == nrtype) return this;
+ return makeImpl(nrtype, ptypes, true);
+ }
+
+ /**
+ * Reports if this type contains a primitive argument or return value.
+ * The return type {@code void} counts as a primitive.
+ * @return true if any of the types are primitives
+ */
+ public boolean hasPrimitives() {
+ return form.hasPrimitives();
+ }
+
+ /**
+ * Reports if this type contains a wrapper argument or return value.
+ * Wrappers are types which box primitive values, such as {@link Integer}.
+ * The reference type {@code java.lang.Void} counts as a wrapper,
+ * if it occurs as a return type.
+ * @return true if any of the types are wrappers
+ */
+ public boolean hasWrappers() {
+ return unwrap() != this;
+ }
+
+ /**
+ * Erases all reference types to {@code Object}.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All primitive types (including {@code void}) will remain unchanged.
+ * @return a version of the original type with all reference types replaced
+ */
+ public MethodType erase() {
+ return form.erasedType();
+ }
+
+ /**
+ * Erases all reference types to {@code Object}, and all subword types to {@code int}.
+ * This is the reduced type polymorphism used by private methods
+ * such as {@link MethodHandle#invokeBasic invokeBasic}.
+ * @return a version of the original type with all reference and subword types replaced
+ */
+ /*non-public*/ MethodType basicType() {
+ return form.basicType();
+ }
+
+ /**
+ * @return a version of the original type with MethodHandle prepended as the first argument
+ */
+ /*non-public*/ MethodType invokerType() {
+ return insertParameterTypes(0, MethodHandle.class);
+ }
- public MethodType changeParameterType(int num, Class<?> nptype) { return null; }
+ /**
+ * Converts all types, both reference and primitive, to {@code Object}.
+ * Convenience method for {@link #genericMethodType(int) genericMethodType}.
+ * The expression {@code type.wrap().erase()} produces the same value
+ * as {@code type.generic()}.
+ * @return a version of the original type with all types replaced
+ */
+ public MethodType generic() {
+ return genericMethodType(parameterCount());
+ }
- public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) { return null; }
+ /*non-public*/ boolean isGeneric() {
+ return this == erase() && !hasPrimitives();
+ }
- public MethodType appendParameterTypes(Class<?>... ptypesToInsert) { return null; }
+ /**
+ * Converts all primitive types to their corresponding wrapper types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All reference types (including wrapper types) will remain unchanged.
+ * A {@code void} return type is changed to the type {@code java.lang.Void}.
+ * The expression {@code type.wrap().erase()} produces the same value
+ * as {@code type.generic()}.
+ * @return a version of the original type with all primitive types replaced
+ */
+ public MethodType wrap() {
+ return hasPrimitives() ? wrapWithPrims(this) : this;
+ }
- public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) { return null; }
+ /**
+ * Converts all wrapper types to their corresponding primitive types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All primitive types (including {@code void}) will remain unchanged.
+ * A return type of {@code java.lang.Void} is changed to {@code void}.
+ * @return a version of the original type with all wrapper types replaced
+ */
+ public MethodType unwrap() {
+ MethodType noprims = !hasPrimitives() ? this : wrapWithPrims(this);
+ return unwrapWithNoPrims(noprims);
+ }
- public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) { return null; }
+ private static MethodType wrapWithPrims(MethodType pt) {
+ assert(pt.hasPrimitives());
+ MethodType wt = pt.wrapAlt;
+ if (wt == null) {
+ // fill in lazily
+ wt = MethodTypeForm.canonicalize(pt, MethodTypeForm.WRAP, MethodTypeForm.WRAP);
+ assert(wt != null);
+ pt.wrapAlt = wt;
+ }
+ return wt;
+ }
- public MethodType dropParameterTypes(int start, int end) { return null; }
+ private static MethodType unwrapWithNoPrims(MethodType wt) {
+ assert(!wt.hasPrimitives());
+ MethodType uwt = wt.wrapAlt;
+ if (uwt == null) {
+ // fill in lazily
+ uwt = MethodTypeForm.canonicalize(wt, MethodTypeForm.UNWRAP, MethodTypeForm.UNWRAP);
+ if (uwt == null)
+ uwt = wt; // type has no wrappers or prims at all
+ wt.wrapAlt = uwt;
+ }
+ return uwt;
+ }
- public MethodType changeReturnType(Class<?> nrtype) { return null; }
+ /**
+ * Returns the parameter type at the specified index, within this method type.
+ * @param num the index (zero-based) of the desired parameter type
+ * @return the selected parameter type
+ * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
+ */
+ public Class<?> parameterType(int num) {
+ return ptypes[num];
+ }
+ /**
+ * Returns the number of parameter types in this method type.
+ * @return the number of parameter types
+ */
+ public int parameterCount() {
+ return ptypes.length;
+ }
+ /**
+ * Returns the return type of this method type.
+ * @return the return type
+ */
+ public Class<?> returnType() {
+ return rtype;
+ }
- public boolean hasPrimitives() { return false; }
+ /**
+ * Presents the parameter types as a list (a convenience method).
+ * The list will be immutable.
+ * @return the parameter types (as an immutable list)
+ */
+ public List<Class<?>> parameterList() {
+ return Collections.unmodifiableList(Arrays.asList(ptypes.clone()));
+ }
- public boolean hasWrappers() { return false; }
+ /*non-public*/ Class<?> lastParameterType() {
+ int len = ptypes.length;
+ return len == 0 ? void.class : ptypes[len-1];
+ }
- public MethodType erase() { return null; }
+ /**
+ * Presents the parameter types as an array (a convenience method).
+ * Changes to the array will not result in changes to the type.
+ * @return the parameter types (as a fresh copy if necessary)
+ */
+ public Class<?>[] parameterArray() {
+ return ptypes.clone();
+ }
- public MethodType generic() { return null; }
+ /**
+ * Compares the specified object with this type for equality.
+ * That is, it returns <tt>true</tt> if and only if the specified object
+ * is also a method type with exactly the same parameters and return type.
+ * @param x object to compare
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object x) {
+ return this == x || x instanceof MethodType && equals((MethodType)x);
+ }
- public MethodType wrap() { return null; }
+ private boolean equals(MethodType that) {
+ return this.rtype == that.rtype
+ && Arrays.equals(this.ptypes, that.ptypes);
+ }
- public MethodType unwrap() { return null; }
+ /**
+ * Returns the hash code value for this method type.
+ * It is defined to be the same as the hashcode of a List
+ * whose elements are the return type followed by the
+ * parameter types.
+ * @return the hash code value for this method type
+ * @see Object#hashCode()
+ * @see #equals(Object)
+ * @see List#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int hashCode = 31 + rtype.hashCode();
+ for (Class<?> ptype : ptypes)
+ hashCode = 31*hashCode + ptype.hashCode();
+ return hashCode;
+ }
- public Class<?> parameterType(int num) { return null; }
+ /**
+ * Returns a string representation of the method type,
+ * of the form {@code "(PT0,PT1...)RT"}.
+ * The string representation of a method type is a
+ * parenthesis enclosed, comma separated list of type names,
+ * followed immediately by the return type.
+ * <p>
+ * Each type is represented by its
+ * {@link java.lang.Class#getSimpleName simple name}.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ for (int i = 0; i < ptypes.length; i++) {
+ if (i > 0) sb.append(",");
+ sb.append(ptypes[i].getSimpleName());
+ }
+ sb.append(")");
+ sb.append(rtype.getSimpleName());
+ return sb.toString();
+ }
- public int parameterCount() { return 0; }
+ /** True if the old return type can always be viewed (w/o casting) under new return type,
+ * and the new parameters can be viewed (w/o casting) under the old parameter types.
+ */
+ // Android-changed: Removed implementation details.
+ // boolean isViewableAs(MethodType newType, boolean keepInterfaces);
+ // boolean parametersAreViewableAs(MethodType newType, boolean keepInterfaces);
+ /*non-public*/
+ boolean isConvertibleTo(MethodType newType) {
+ MethodTypeForm oldForm = this.form();
+ MethodTypeForm newForm = newType.form();
+ if (oldForm == newForm)
+ // same parameter count, same primitive/object mix
+ return true;
+ if (!canConvert(returnType(), newType.returnType()))
+ return false;
+ Class<?>[] srcTypes = newType.ptypes;
+ Class<?>[] dstTypes = ptypes;
+ if (srcTypes == dstTypes)
+ return true;
+ int argc;
+ if ((argc = srcTypes.length) != dstTypes.length)
+ return false;
+ if (argc <= 1) {
+ if (argc == 1 && !canConvert(srcTypes[0], dstTypes[0]))
+ return false;
+ return true;
+ }
+ if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) ||
+ (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) {
+ // Somewhat complicated test to avoid a loop of 2 or more trips.
+ // If either type has only Object parameters, we know we can convert.
+ assert(canConvertParameters(srcTypes, dstTypes));
+ return true;
+ }
+ return canConvertParameters(srcTypes, dstTypes);
+ }
+
+ /** Returns true if MHs.explicitCastArguments produces the same result as MH.asType.
+ * If the type conversion is impossible for either, the result should be false.
+ */
+ /*non-public*/
+ boolean explicitCastEquivalentToAsType(MethodType newType) {
+ if (this == newType) return true;
+ if (!explicitCastEquivalentToAsType(rtype, newType.rtype)) {
+ return false;
+ }
+ Class<?>[] srcTypes = newType.ptypes;
+ Class<?>[] dstTypes = ptypes;
+ if (dstTypes == srcTypes) {
+ return true;
+ }
+ assert(dstTypes.length == srcTypes.length);
+ for (int i = 0; i < dstTypes.length; i++) {
+ if (!explicitCastEquivalentToAsType(srcTypes[i], dstTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Reports true if the src can be converted to the dst, by both asType and MHs.eCE,
+ * and with the same effect.
+ * MHs.eCA has the following "upgrades" to MH.asType:
+ * 1. interfaces are unchecked (that is, treated as if aliased to Object)
+ * Therefore, {@code Object->CharSequence} is possible in both cases but has different semantics
+ * 2a. the full matrix of primitive-to-primitive conversions is supported
+ * Narrowing like {@code long->byte} and basic-typing like {@code boolean->int}
+ * are not supported by asType, but anything supported by asType is equivalent
+ * with MHs.eCE.
+ * 2b. conversion of void->primitive means explicit cast has to insert zero/false/null.
+ * 3a. unboxing conversions can be followed by the full matrix of primitive conversions
+ * 3b. unboxing of null is permitted (creates a zero primitive value)
+ * Other than interfaces, reference-to-reference conversions are the same.
+ * Boxing primitives to references is the same for both operators.
+ */
+ private static boolean explicitCastEquivalentToAsType(Class<?> src, Class<?> dst) {
+ if (src == dst || dst == Object.class || dst == void.class) {
+ return true;
+ } else if (src.isPrimitive() && src != void.class) {
+ // Could be a prim/prim conversion, where casting is a strict superset.
+ // Or a boxing conversion, which is always to an exact wrapper class.
+ return canConvert(src, dst);
+ } else if (dst.isPrimitive()) {
+ // Unboxing behavior is different between MHs.eCA & MH.asType (see 3b).
+ return false;
+ } else {
+ // R->R always works, but we have to avoid a check-cast to an interface.
+ return !dst.isInterface() || dst.isAssignableFrom(src);
+ }
+ }
- public Class<?> returnType() { return null; }
+ private boolean canConvertParameters(Class<?>[] srcTypes, Class<?>[] dstTypes) {
+ for (int i = 0; i < srcTypes.length; i++) {
+ if (!canConvert(srcTypes[i], dstTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
- public List<Class<?>> parameterList() { return null; }
+ /*non-public*/
+ static boolean canConvert(Class<?> src, Class<?> dst) {
+ // short-circuit a few cases:
+ if (src == dst || src == Object.class || dst == Object.class) return true;
+ // the remainder of this logic is documented in MethodHandle.asType
+ if (src.isPrimitive()) {
+ // can force void to an explicit null, a la reflect.Method.invoke
+ // can also force void to a primitive zero, by analogy
+ if (src == void.class) return true; //or !dst.isPrimitive()?
+ Wrapper sw = Wrapper.forPrimitiveType(src);
+ if (dst.isPrimitive()) {
+ // P->P must widen
+ return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw);
+ } else {
+ // P->R must box and widen
+ return dst.isAssignableFrom(sw.wrapperType());
+ }
+ } else if (dst.isPrimitive()) {
+ // any value can be dropped
+ if (dst == void.class) return true;
+ Wrapper dw = Wrapper.forPrimitiveType(dst);
+ // R->P must be able to unbox (from a dynamically chosen type) and widen
+ // For example:
+ // Byte/Number/Comparable/Object -> dw:Byte -> byte.
+ // Character/Comparable/Object -> dw:Character -> char
+ // Boolean/Comparable/Object -> dw:Boolean -> boolean
+ // This means that dw must be cast-compatible with src.
+ if (src.isAssignableFrom(dw.wrapperType())) {
+ return true;
+ }
+ // The above does not work if the source reference is strongly typed
+ // to a wrapper whose primitive must be widened. For example:
+ // Byte -> unbox:byte -> short/int/long/float/double
+ // Character -> unbox:char -> int/long/float/double
+ if (Wrapper.isWrapperType(src) &&
+ dw.isConvertibleFrom(Wrapper.forWrapperType(src))) {
+ // can unbox from src and then widen to dst
+ return true;
+ }
+ // We have already covered cases which arise due to runtime unboxing
+ // of a reference type which covers several wrapper types:
+ // Object -> cast:Integer -> unbox:int -> long/float/double
+ // Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double
+ // An marginal case is Number -> dw:Character -> char, which would be OK if there were a
+ // subclass of Number which wraps a value that can convert to char.
+ // Since there is none, we don't need an extra check here to cover char or boolean.
+ return false;
+ } else {
+ // R->R always works, since null is always valid dynamically
+ return true;
+ }
+ }
- public Class<?>[] parameterArray() { return null; }
+ /** Reports the number of JVM stack slots required to invoke a method
+ * of this type. Note that (for historical reasons) the JVM requires
+ * a second stack slot to pass long and double arguments.
+ * So this method returns {@link #parameterCount() parameterCount} plus the
+ * number of long and double parameters (if any).
+ * <p>
+ * This method is included for the benefit of applications that must
+ * generate bytecodes that process method handles and invokedynamic.
+ * @return the number of JVM stack slots for this type's parameters
+ */
+ /*non-public*/ int parameterSlotCount() {
+ return form.parameterSlotCount();
+ }
+ /// Queries which have to do with the bytecode architecture
+
+ // Android-changed: These methods aren't needed on Android and are unused within the JDK.
+ //
+ // int parameterSlotDepth(int num);
+ // int returnSlotCount();
+ //
+ // Android-changed: Removed cache of higher order adapters.
+ //
+ // Invokers invokers();
+
+ /**
+ * Finds or creates an instance of a method type, given the spelling of its bytecode descriptor.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * Any class or interface name embedded in the descriptor string
+ * will be resolved by calling {@link ClassLoader#loadClass(java.lang.String)}
+ * on the given loader (or if it is null, on the system class loader).
+ * <p>
+ * Note that it is possible to encounter method types which cannot be
+ * constructed by this method, because their component types are
+ * not all reachable from a common class loader.
+ * <p>
+ * This method is included for the benefit of applications that must
+ * generate bytecodes that process method handles and {@code invokedynamic}.
+ * @param descriptor a bytecode-level type descriptor string "(T...)T"
+ * @param loader the class loader in which to look up the types
+ * @return a method type matching the bytecode-level type descriptor
+ * @throws NullPointerException if the string is null
+ * @throws IllegalArgumentException if the string is not well-formed
+ * @throws TypeNotPresentException if a named type cannot be found
+ */
public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
- throws IllegalArgumentException, TypeNotPresentException { return null; }
+ throws IllegalArgumentException, TypeNotPresentException
+ {
+ if (!descriptor.startsWith("(") || // also generates NPE if needed
+ descriptor.indexOf(')') < 0 ||
+ descriptor.indexOf('.') >= 0)
+ throw newIllegalArgumentException("not a method descriptor: "+descriptor);
+ List<Class<?>> types = BytecodeDescriptor.parseMethod(descriptor, loader);
+ Class<?> rtype = types.remove(types.size() - 1);
+ checkSlotCount(types.size());
+ Class<?>[] ptypes = listToArray(types);
+ return makeImpl(rtype, ptypes, true);
+ }
+
+ /**
+ * Produces a bytecode descriptor representation of the method type.
+ * <p>
+ * Note that this is not a strict inverse of {@link #fromMethodDescriptorString fromMethodDescriptorString}.
+ * Two distinct classes which share a common name but have different class loaders
+ * will appear identical when viewed within descriptor strings.
+ * <p>
+ * This method is included for the benefit of applications that must
+ * generate bytecodes that process method handles and {@code invokedynamic}.
+ * {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) fromMethodDescriptorString},
+ * because the latter requires a suitable class loader argument.
+ * @return the bytecode type descriptor representation
+ */
+ public String toMethodDescriptorString() {
+ String desc = methodDescriptor;
+ if (desc == null) {
+ desc = BytecodeDescriptor.unparse(this);
+ methodDescriptor = desc;
+ }
+ return desc;
+ }
+
+ /*non-public*/ static String toFieldDescriptorString(Class<?> cls) {
+ return BytecodeDescriptor.unparse(cls);
+ }
- public String toMethodDescriptorString() { return null; }
+ /// Serialization.
+
+ /**
+ * There are no serializable fields for {@code MethodType}.
+ */
+ private static final java.io.ObjectStreamField[] serialPersistentFields = { };
+
+ /**
+ * Save the {@code MethodType} instance to a stream.
+ *
+ * @serialData
+ * For portability, the serialized format does not refer to named fields.
+ * Instead, the return type and parameter type arrays are written directly
+ * from the {@code writeObject} method, using two calls to {@code s.writeObject}
+ * as follows:
+ * <blockquote><pre>{@code
+s.writeObject(this.returnType());
+s.writeObject(this.parameterArray());
+ * }</pre></blockquote>
+ * <p>
+ * The deserialized field values are checked as if they were
+ * provided to the factory method {@link #methodType(Class,Class[]) methodType}.
+ * For example, null values, or {@code void} parameter types,
+ * will lead to exceptions during deserialization.
+ * @param s the stream to write the object to
+ * @throws java.io.IOException if there is a problem writing the object
+ */
+ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
+ s.defaultWriteObject(); // requires serialPersistentFields to be an empty array
+ s.writeObject(returnType());
+ s.writeObject(parameterArray());
+ }
+
+ /**
+ * Reconstitute the {@code MethodType} instance from a stream (that is,
+ * deserialize it).
+ * This instance is a scratch object with bogus final fields.
+ * It provides the parameters to the factory method called by
+ * {@link #readResolve readResolve}.
+ * After that call it is discarded.
+ * @param s the stream to read the object from
+ * @throws java.io.IOException if there is a problem reading the object
+ * @throws ClassNotFoundException if one of the component classes cannot be resolved
+ * @see #MethodType()
+ * @see #readResolve
+ * @see #writeObject
+ */
+ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
+ s.defaultReadObject(); // requires serialPersistentFields to be an empty array
+
+ Class<?> returnType = (Class<?>) s.readObject();
+ Class<?>[] parameterArray = (Class<?>[]) s.readObject();
+
+ // Probably this object will never escape, but let's check
+ // the field values now, just to be sure.
+ checkRtype(returnType);
+ checkPtypes(parameterArray);
+
+ parameterArray = parameterArray.clone(); // make sure it is unshared
+ MethodType_init(returnType, parameterArray);
+ }
+
+ /**
+ * For serialization only.
+ * Sets the final fields to null, pending {@code Unsafe.putObject}.
+ */
+ private MethodType() {
+ this.rtype = null;
+ this.ptypes = null;
+ }
+ private void MethodType_init(Class<?> rtype, Class<?>[] ptypes) {
+ // In order to communicate these values to readResolve, we must
+ // store them into the implementation-specific final fields.
+ checkRtype(rtype);
+ checkPtypes(ptypes);
+ UNSAFE.putObject(this, rtypeOffset, rtype);
+ UNSAFE.putObject(this, ptypesOffset, ptypes);
+ }
+
+ // Support for resetting final fields while deserializing
+ private static final long rtypeOffset, ptypesOffset;
+ static {
+ try {
+ rtypeOffset = UNSAFE.objectFieldOffset
+ (MethodType.class.getDeclaredField("rtype"));
+ ptypesOffset = UNSAFE.objectFieldOffset
+ (MethodType.class.getDeclaredField("ptypes"));
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ /**
+ * Resolves and initializes a {@code MethodType} object
+ * after serialization.
+ * @return the fully initialized {@code MethodType} object
+ */
+ private Object readResolve() {
+ // Do not use a trusted path for deserialization:
+ //return makeImpl(rtype, ptypes, true);
+ // Verify all operands, and make sure ptypes is unshared:
+ return methodType(rtype, ptypes);
+ }
+
+ /**
+ * Simple implementation of weak concurrent intern set.
+ *
+ * @param <T> interned type
+ */
+ private static class ConcurrentWeakInternSet<T> {
+
+ private final ConcurrentMap<WeakEntry<T>, WeakEntry<T>> map;
+ private final ReferenceQueue<T> stale;
+
+ public ConcurrentWeakInternSet() {
+ this.map = new ConcurrentHashMap<>();
+ this.stale = new ReferenceQueue<>();
+ }
+
+ /**
+ * Get the existing interned element.
+ * This method returns null if no element is interned.
+ *
+ * @param elem element to look up
+ * @return the interned element
+ */
+ public T get(T elem) {
+ if (elem == null) throw new NullPointerException();
+ expungeStaleElements();
+
+ WeakEntry<T> value = map.get(new WeakEntry<>(elem));
+ if (value != null) {
+ T res = value.get();
+ if (res != null) {
+ return res;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Interns the element.
+ * Always returns non-null element, matching the one in the intern set.
+ * Under the race against another add(), it can return <i>different</i>
+ * element, if another thread beats us to interning it.
+ *
+ * @param elem element to add
+ * @return element that was actually added
+ */
+ public T add(T elem) {
+ if (elem == null) throw new NullPointerException();
+
+ // Playing double race here, and so spinloop is required.
+ // First race is with two concurrent updaters.
+ // Second race is with GC purging weak ref under our feet.
+ // Hopefully, we almost always end up with a single pass.
+ T interned;
+ WeakEntry<T> e = new WeakEntry<>(elem, stale);
+ do {
+ expungeStaleElements();
+ WeakEntry<T> exist = map.putIfAbsent(e, e);
+ interned = (exist == null) ? elem : exist.get();
+ } while (interned == null);
+ return interned;
+ }
+
+ private void expungeStaleElements() {
+ Reference<? extends T> reference;
+ while ((reference = stale.poll()) != null) {
+ map.remove(reference);
+ }
+ }
+
+ private static class WeakEntry<T> extends WeakReference<T> {
+
+ public final int hashcode;
+
+ public WeakEntry(T key, ReferenceQueue<T> queue) {
+ super(key, queue);
+ hashcode = key.hashCode();
+ }
+
+ public WeakEntry(T key) {
+ super(key);
+ hashcode = key.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WeakEntry) {
+ Object that = ((WeakEntry) obj).get();
+ Object mine = get();
+ return (that == null || mine == null) ? (this == obj) : mine.equals(that);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashcode;
+ }
+
+ }
+ }
}
diff --git a/java/lang/invoke/VarHandle.java b/java/lang/invoke/VarHandle.java
index 562efb6e..da56e8d4 100644
--- a/java/lang/invoke/VarHandle.java
+++ b/java/lang/invoke/VarHandle.java
@@ -32,6 +32,7 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* A VarHandle is a dynamically strongly typed reference to a variable, or to a
@@ -450,8 +451,12 @@ public abstract class VarHandle {
/** The target type for accesses. */
private final Class<?> varType;
- /** The coordinate types of a VarHandle instance. */
- private final List<Class<?>> coordinateTypes;
+ /** This VarHandle's first coordinate, or null if this VarHandle has no coordinates. */
+ private final Class<?> coordinateType0;
+
+ /** This VarHandle's second coordinate, or null if this VarHandle has less than two
+ * coordinates. */
+ private final Class<?> coordinateType1;
/** BitMask of supported access mode indexed by AccessMode.ordinal(). */
private final int accessModesBitMask;
@@ -1932,8 +1937,14 @@ public abstract class VarHandle {
// Android-removed: existing implementation.
// MethodType typeGet = accessModeType(AccessMode.GET);
// return typeGet.parameterList();
- // Android-added: return instance field.
- return coordinateTypes;
+ // Android-added: Android specific implementation.
+ if (coordinateType0 == null) {
+ return Collections.EMPTY_LIST;
+ } else if (coordinateType1 == null) {
+ return Collections.singletonList(coordinateType0);
+ } else {
+ return Collections.unmodifiableList(Arrays.asList(coordinateType0, coordinateType1));
+ }
}
/**
@@ -1964,16 +1975,12 @@ public abstract class VarHandle {
// END Android-removed: Relies on internal class that is not part of the
// Android implementation.
// Android-added: alternative implementation.
- switch (coordinateTypes.size()) {
- case 0:
- return accessMode.at.accessModeType(null, varType);
- case 1:
- return accessMode.at.accessModeType(coordinateTypes.get(0), varType);
- case 2:
- return accessMode.at.accessModeType(coordinateTypes.get(0), varType,
- coordinateTypes.get(1));
- default:
- throw new InternalError("bad coordinateTypes: " + coordinateTypes);
+ if (coordinateType1 == null) {
+ // accessModeType() treats the first argument as the
+ // receiver and adapts accordingly if it is null.
+ return accessMode.at.accessModeType(coordinateType0, varType);
+ } else {
+ return accessMode.at.accessModeType(coordinateType0, varType, coordinateType1);
}
}
@@ -2201,8 +2208,9 @@ public abstract class VarHandle {
* @hide
*/
VarHandle(Class<?> varType, boolean isFinal) {
- this.varType = varType;
- this.coordinateTypes = Collections.EMPTY_LIST;
+ this.varType = Objects.requireNonNull(varType);
+ this.coordinateType0 = null;
+ this.coordinateType1 = null;
this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
}
@@ -2211,12 +2219,13 @@ public abstract class VarHandle {
*
* @param varType the variable type of variables to be referenced
* @param isFinal whether the target variables are final (non-modifiable)
- * @param coordinate the coordinate
+ * @param coordinateType the coordinate
* @hide
*/
- VarHandle(Class<?> varType, boolean isFinal, Class<?> coordinate) {
- this.varType = varType;
- this.coordinateTypes = Collections.singletonList(coordinate);
+ VarHandle(Class<?> varType, boolean isFinal, Class<?> coordinateType) {
+ this.varType = Objects.requireNonNull(varType);
+ this.coordinateType0 = Objects.requireNonNull(coordinateType);
+ this.coordinateType1 = null;
this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
}
@@ -2226,15 +2235,16 @@ public abstract class VarHandle {
* @param varType the variable type of variables to be referenced
* @param backingArrayType the type of the array accesses will be performed on
* @param isFinal whether the target variables are final (non-modifiable)
- * @param coordinate0 the first coordinate
- * @param coordinate1 the second coordinate
+ * @param coordinateType0 the first coordinate
+ * @param coordinateType1 the second coordinate
* @hide
*/
VarHandle(Class<?> varType, Class<?> backingArrayType, boolean isFinal,
- Class<?> coordinate0, Class<?> coordinate1) {
- this.varType = varType;
- this.coordinateTypes = Collections.unmodifiableList(
- Arrays.asList(coordinate0, coordinate1));
+ Class<?> coordinateType0, Class<?> coordinateType1) {
+ this.varType = Objects.requireNonNull(varType);
+ this.coordinateType0 = Objects.requireNonNull(coordinateType0);
+ this.coordinateType1 = Objects.requireNonNull(coordinateType1);
+ Objects.requireNonNull(backingArrayType);
Class<?> backingArrayComponentType = backingArrayType.getComponentType();
if (backingArrayComponentType != varType && backingArrayComponentType != byte.class) {
throw new InternalError("Unsupported backingArrayType: " + backingArrayType);
diff --git a/java/net/URL.java b/java/net/URL.java
index d4ed35be..576439df 100644
--- a/java/net/URL.java
+++ b/java/net/URL.java
@@ -1202,10 +1202,10 @@ public final class URL implements java.io.Serializable {
handler = new sun.net.www.protocol.jar.Handler();
} else if (protocol.equals("http")) {
handler = (URLStreamHandler)Class.
- forName("libcore.net.http.HttpHandler").newInstance();
+ forName("com.android.okhttp.HttpHandler").newInstance();
} else if (protocol.equals("https")) {
handler = (URLStreamHandler)Class.
- forName("libcore.net.http.HttpsHandler").newInstance();
+ forName("com.android.okhttp.HttpsHandler").newInstance();
}
// END Android-changed
} catch (Exception e) {
diff --git a/java/text/DecimalFormat.java b/java/text/DecimalFormat.java
index d2e8530c..ff9d90f0 100644
--- a/java/text/DecimalFormat.java
+++ b/java/text/DecimalFormat.java
@@ -387,7 +387,8 @@ public class DecimalFormat extends NumberFormat {
// to implement DecimalFormat.
// Android-added: ICU DecimalFormat to delegate to.
- private transient android.icu.text.DecimalFormat icuDecimalFormat;
+ // TODO(b/68143370): switch back to ICU DecimalFormat once it can reproduce ICU 58 behavior.
+ private transient android.icu.text.DecimalFormat_ICU58_Android icuDecimalFormat;
/**
* Creates a DecimalFormat using the default pattern and symbols
@@ -486,7 +487,7 @@ public class DecimalFormat extends NumberFormat {
* {@link #icuDecimalFormat} in the process. This should only be called from constructors.
*/
private void initPattern(String pattern) {
- this.icuDecimalFormat = new android.icu.text.DecimalFormat(pattern,
+ this.icuDecimalFormat = new android.icu.text.DecimalFormat_ICU58_Android(pattern,
symbols.getIcuDecimalFormatSymbols());
updateFieldsFromIcu();
}
@@ -1186,7 +1187,7 @@ public class DecimalFormat extends NumberFormat {
// BEGIN Android-changed: Use ICU, remove fast path related code.
try {
DecimalFormat other = (DecimalFormat) super.clone();
- other.icuDecimalFormat = (android.icu.text.DecimalFormat) icuDecimalFormat.clone();
+ other.icuDecimalFormat = (android.icu.text.DecimalFormat_ICU58_Android) icuDecimalFormat.clone();
other.symbols = (DecimalFormatSymbols) symbols.clone();
return other;
} catch (Exception e) {
@@ -1216,7 +1217,7 @@ public class DecimalFormat extends NumberFormat {
&& compareIcuRoundingIncrement(other.icuDecimalFormat);
}
- private boolean compareIcuRoundingIncrement(android.icu.text.DecimalFormat other) {
+ private boolean compareIcuRoundingIncrement(android.icu.text.DecimalFormat_ICU58_Android other) {
BigDecimal increment = this.icuDecimalFormat.getRoundingIncrement();
if (increment != null) {
return (other.getRoundingIncrement() != null)
diff --git a/javax/obex/ObexHelper.java b/javax/obex/ObexHelper.java
index fa509433..478297f2 100644
--- a/javax/obex/ObexHelper.java
+++ b/javax/obex/ObexHelper.java
@@ -80,6 +80,9 @@ public final class ObexHelper {
// The minimum allowed max packet size is 255 according to the OBEX specification
public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
+ // The length of OBEX Byte Sequency Header Id according to the OBEX specification
+ public static final int OBEX_BYTE_SEQ_HEADER_LEN = 0x03;
+
/**
* Temporary workaround to be able to push files to Windows 7.
* TODO: Should be removed as soon as Microsoft updates their driver.
@@ -205,12 +208,15 @@ public final class ObexHelper {
case 0x40:
boolean trimTail = true;
index++;
- length = 0xFF & headerArray[index];
- length = length << 8;
- index++;
- length += 0xFF & headerArray[index];
- length -= 3;
- index++;
+ length = ((0xFF & headerArray[index]) << 8) +
+ (0xFF & headerArray[index + 1]);
+ index += 2;
+ if (length <= OBEX_BYTE_SEQ_HEADER_LEN) {
+ Log.e(TAG, "Remote sent an OBEX packet with " +
+ "incorrect header length = " + length);
+ break;
+ }
+ length -= OBEX_BYTE_SEQ_HEADER_LEN;
value = new byte[length];
System.arraycopy(headerArray, index, value, 0, length);
if (length == 0 || (length > 0 && (value[length - 1] != 0))) {
diff --git a/org/json/JSONArrayTest.java b/org/json/JSONArrayTest.java
deleted file mode 100644
index 4c86b8ad..00000000
--- a/org/json/JSONArrayTest.java
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.json;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONArrayTest extends TestCase {
-
- public void testEmptyArray() throws JSONException {
- JSONArray array = new JSONArray();
- assertEquals(0, array.length());
- assertEquals("", array.join(" AND "));
- try {
- array.get(0);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.getBoolean(0);
- fail();
- } catch (JSONException e) {
- }
-
- assertEquals("[]", array.toString());
- assertEquals("[]", array.toString(4));
-
- // out of bounds is co-opted with defaulting
- assertTrue(array.isNull(0));
- assertNull(array.opt(0));
- assertFalse(array.optBoolean(0));
- assertTrue(array.optBoolean(0, true));
-
- // bogus (but documented) behaviour: returns null rather than an empty object!
- assertNull(array.toJSONObject(new JSONArray()));
- }
-
- public void testEqualsAndHashCode() throws JSONException {
- JSONArray a = new JSONArray();
- JSONArray b = new JSONArray();
- assertTrue(a.equals(b));
- assertEquals("equals() not consistent with hashCode()", a.hashCode(), b.hashCode());
-
- a.put(true);
- a.put(false);
- b.put(true);
- b.put(false);
- assertTrue(a.equals(b));
- assertEquals(a.hashCode(), b.hashCode());
-
- b.put(true);
- assertFalse(a.equals(b));
- assertTrue(a.hashCode() != b.hashCode());
- }
-
- public void testBooleans() throws JSONException {
- JSONArray array = new JSONArray();
- array.put(true);
- array.put(false);
- array.put(2, false);
- array.put(3, false);
- array.put(2, true);
- assertEquals("[true,false,true,false]", array.toString());
- assertEquals(4, array.length());
- assertEquals(Boolean.TRUE, array.get(0));
- assertEquals(Boolean.FALSE, array.get(1));
- assertEquals(Boolean.TRUE, array.get(2));
- assertEquals(Boolean.FALSE, array.get(3));
- assertFalse(array.isNull(0));
- assertFalse(array.isNull(1));
- assertFalse(array.isNull(2));
- assertFalse(array.isNull(3));
- assertEquals(true, array.optBoolean(0));
- assertEquals(false, array.optBoolean(1, true));
- assertEquals(true, array.optBoolean(2, false));
- assertEquals(false, array.optBoolean(3));
- assertEquals("true", array.getString(0));
- assertEquals("false", array.getString(1));
- assertEquals("true", array.optString(2));
- assertEquals("false", array.optString(3, "x"));
- assertEquals("[\n true,\n false,\n true,\n false\n]", array.toString(5));
-
- JSONArray other = new JSONArray();
- other.put(true);
- other.put(false);
- other.put(true);
- other.put(false);
- assertTrue(array.equals(other));
- other.put(true);
- assertFalse(array.equals(other));
-
- other = new JSONArray();
- other.put("true");
- other.put("false");
- other.put("truE");
- other.put("FALSE");
- assertFalse(array.equals(other));
- assertFalse(other.equals(array));
- assertEquals(true, other.getBoolean(0));
- assertEquals(false, other.optBoolean(1, true));
- assertEquals(true, other.optBoolean(2));
- assertEquals(false, other.getBoolean(3));
- }
-
- // http://code.google.com/p/android/issues/detail?id=16411
- public void testCoerceStringToBoolean() throws JSONException {
- JSONArray array = new JSONArray();
- array.put("maybe");
- try {
- array.getBoolean(0);
- fail();
- } catch (JSONException expected) {
- }
- assertEquals(false, array.optBoolean(0));
- assertEquals(true, array.optBoolean(0, true));
- }
-
- public void testNulls() throws JSONException {
- JSONArray array = new JSONArray();
- array.put(3, (Collection) null);
- array.put(0, JSONObject.NULL);
- assertEquals(4, array.length());
- assertEquals("[null,null,null,null]", array.toString());
-
- // there's 2 ways to represent null; each behaves differently!
- assertEquals(JSONObject.NULL, array.get(0));
- try {
- array.get(1);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.get(2);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.get(3);
- fail();
- } catch (JSONException e) {
- }
- assertEquals(JSONObject.NULL, array.opt(0));
- assertEquals(null, array.opt(1));
- assertEquals(null, array.opt(2));
- assertEquals(null, array.opt(3));
- assertTrue(array.isNull(0));
- assertTrue(array.isNull(1));
- assertTrue(array.isNull(2));
- assertTrue(array.isNull(3));
- assertEquals("null", array.optString(0));
- assertEquals("", array.optString(1));
- assertEquals("", array.optString(2));
- assertEquals("", array.optString(3));
- }
-
- /**
- * Our behaviour is questioned by this bug:
- * http://code.google.com/p/android/issues/detail?id=7257
- */
- public void testParseNullYieldsJSONObjectNull() throws JSONException {
- JSONArray array = new JSONArray("[\"null\",null]");
- array.put((Collection) null);
- assertEquals("null", array.get(0));
- assertEquals(JSONObject.NULL, array.get(1));
- try {
- array.get(2);
- fail();
- } catch (JSONException e) {
- }
- assertEquals("null", array.getString(0));
- assertEquals("null", array.getString(1));
- try {
- array.getString(2);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testNumbers() throws JSONException {
- JSONArray array = new JSONArray();
- array.put(Double.MIN_VALUE);
- array.put(9223372036854775806L);
- array.put(Double.MAX_VALUE);
- array.put(-0d);
- assertEquals(4, array.length());
-
- // toString() and getString(int) return different values for -0d
- assertEquals("[4.9E-324,9223372036854775806,1.7976931348623157E308,-0]", array.toString());
-
- assertEquals(Double.MIN_VALUE, array.get(0));
- assertEquals(9223372036854775806L, array.get(1));
- assertEquals(Double.MAX_VALUE, array.get(2));
- assertEquals(-0d, array.get(3));
- assertEquals(Double.MIN_VALUE, array.getDouble(0));
- assertEquals(9.223372036854776E18, array.getDouble(1));
- assertEquals(Double.MAX_VALUE, array.getDouble(2));
- assertEquals(-0d, array.getDouble(3));
- assertEquals(0, array.getLong(0));
- assertEquals(9223372036854775806L, array.getLong(1));
- assertEquals(Long.MAX_VALUE, array.getLong(2));
- assertEquals(0, array.getLong(3));
- assertEquals(0, array.getInt(0));
- assertEquals(-2, array.getInt(1));
- assertEquals(Integer.MAX_VALUE, array.getInt(2));
- assertEquals(0, array.getInt(3));
- assertEquals(Double.MIN_VALUE, array.opt(0));
- assertEquals(Double.MIN_VALUE, array.optDouble(0));
- assertEquals(0, array.optLong(0, 1L));
- assertEquals(0, array.optInt(0, 1));
- assertEquals("4.9E-324", array.getString(0));
- assertEquals("9223372036854775806", array.getString(1));
- assertEquals("1.7976931348623157E308", array.getString(2));
- assertEquals("-0.0", array.getString(3));
-
- JSONArray other = new JSONArray();
- other.put(Double.MIN_VALUE);
- other.put(9223372036854775806L);
- other.put(Double.MAX_VALUE);
- other.put(-0d);
- assertTrue(array.equals(other));
- other.put(0, 0L);
- assertFalse(array.equals(other));
- }
-
- public void testStrings() throws JSONException {
- JSONArray array = new JSONArray();
- array.put("true");
- array.put("5.5");
- array.put("9223372036854775806");
- array.put("null");
- array.put("5\"8' tall");
- assertEquals(5, array.length());
- assertEquals("[\"true\",\"5.5\",\"9223372036854775806\",\"null\",\"5\\\"8' tall\"]",
- array.toString());
-
- // although the documentation doesn't mention it, join() escapes text and wraps
- // strings in quotes
- assertEquals("\"true\" \"5.5\" \"9223372036854775806\" \"null\" \"5\\\"8' tall\"",
- array.join(" "));
-
- assertEquals("true", array.get(0));
- assertEquals("null", array.getString(3));
- assertEquals("5\"8' tall", array.getString(4));
- assertEquals("true", array.opt(0));
- assertEquals("5.5", array.optString(1));
- assertEquals("9223372036854775806", array.optString(2, null));
- assertEquals("null", array.optString(3, "-1"));
- assertFalse(array.isNull(0));
- assertFalse(array.isNull(3));
-
- assertEquals(true, array.getBoolean(0));
- assertEquals(true, array.optBoolean(0));
- assertEquals(true, array.optBoolean(0, false));
- assertEquals(0, array.optInt(0));
- assertEquals(-2, array.optInt(0, -2));
-
- assertEquals(5.5d, array.getDouble(1));
- assertEquals(5L, array.getLong(1));
- assertEquals(5, array.getInt(1));
- assertEquals(5, array.optInt(1, 3));
-
- // The last digit of the string is a 6 but getLong returns a 7. It's probably parsing as a
- // double and then converting that to a long. This is consistent with JavaScript.
- assertEquals(9223372036854775807L, array.getLong(2));
- assertEquals(9.223372036854776E18, array.getDouble(2));
- assertEquals(Integer.MAX_VALUE, array.getInt(2));
-
- assertFalse(array.isNull(3));
- try {
- array.getDouble(3);
- fail();
- } catch (JSONException e) {
- }
- assertEquals(Double.NaN, array.optDouble(3));
- assertEquals(-1.0d, array.optDouble(3, -1.0d));
- }
-
- public void testJoin() throws JSONException {
- JSONArray array = new JSONArray();
- array.put((Collection) null);
- assertEquals("null", array.join(" & "));
- array.put("\"");
- assertEquals("null & \"\\\"\"", array.join(" & "));
- array.put(5);
- assertEquals("null & \"\\\"\" & 5", array.join(" & "));
- array.put(true);
- assertEquals("null & \"\\\"\" & 5 & true", array.join(" & "));
- array.put(new JSONArray(Arrays.asList(true, false)));
- assertEquals("null & \"\\\"\" & 5 & true & [true,false]", array.join(" & "));
- array.put(new JSONObject(Collections.singletonMap("x", 6)));
- assertEquals("null & \"\\\"\" & 5 & true & [true,false] & {\"x\":6}", array.join(" & "));
- }
-
- public void testJoinWithNull() throws JSONException {
- JSONArray array = new JSONArray(Arrays.asList(5, 6));
- assertEquals("5null6", array.join(null));
- }
-
- public void testJoinWithSpecialCharacters() throws JSONException {
- JSONArray array = new JSONArray(Arrays.asList(5, 6));
- assertEquals("5\"6", array.join("\""));
- }
-
- public void testToJSONObject() throws JSONException {
- JSONArray keys = new JSONArray();
- keys.put("a");
- keys.put("b");
-
- JSONArray values = new JSONArray();
- values.put(5.5d);
- values.put(false);
-
- JSONObject object = values.toJSONObject(keys);
- assertEquals(5.5d, object.get("a"));
- assertEquals(false, object.get("b"));
-
- keys.put(0, "a");
- values.put(0, 11.0d);
- assertEquals(5.5d, object.get("a"));
- }
-
- public void testToJSONObjectWithNulls() throws JSONException {
- JSONArray keys = new JSONArray();
- keys.put("a");
- keys.put("b");
-
- JSONArray values = new JSONArray();
- values.put(5.5d);
- values.put((Collection) null);
-
- // null values are stripped!
- JSONObject object = values.toJSONObject(keys);
- assertEquals(1, object.length());
- assertFalse(object.has("b"));
- assertEquals("{\"a\":5.5}", object.toString());
- }
-
- public void testToJSONObjectMoreNamesThanValues() throws JSONException {
- JSONArray keys = new JSONArray();
- keys.put("a");
- keys.put("b");
- JSONArray values = new JSONArray();
- values.put(5.5d);
- JSONObject object = values.toJSONObject(keys);
- assertEquals(1, object.length());
- assertEquals(5.5d, object.get("a"));
- }
-
- public void testToJSONObjectMoreValuesThanNames() throws JSONException {
- JSONArray keys = new JSONArray();
- keys.put("a");
- JSONArray values = new JSONArray();
- values.put(5.5d);
- values.put(11.0d);
- JSONObject object = values.toJSONObject(keys);
- assertEquals(1, object.length());
- assertEquals(5.5d, object.get("a"));
- }
-
- public void testToJSONObjectNullKey() throws JSONException {
- JSONArray keys = new JSONArray();
- keys.put(JSONObject.NULL);
- JSONArray values = new JSONArray();
- values.put(5.5d);
- JSONObject object = values.toJSONObject(keys);
- assertEquals(1, object.length());
- assertEquals(5.5d, object.get("null"));
- }
-
- public void testPutUnsupportedNumbers() throws JSONException {
- JSONArray array = new JSONArray();
-
- try {
- array.put(Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.put(0, Double.NEGATIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.put(0, Double.POSITIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testPutUnsupportedNumbersAsObject() throws JSONException {
- JSONArray array = new JSONArray();
- array.put(Double.valueOf(Double.NaN));
- array.put(Double.valueOf(Double.NEGATIVE_INFINITY));
- array.put(Double.valueOf(Double.POSITIVE_INFINITY));
- assertEquals(null, array.toString());
- }
-
- /**
- * Although JSONArray is usually defensive about which numbers it accepts,
- * it doesn't check inputs in its constructor.
- */
- public void testCreateWithUnsupportedNumbers() throws JSONException {
- JSONArray array = new JSONArray(Arrays.asList(5.5, Double.NaN));
- assertEquals(2, array.length());
- assertEquals(5.5, array.getDouble(0));
- assertEquals(Double.NaN, array.getDouble(1));
- }
-
- public void testToStringWithUnsupportedNumbers() throws JSONException {
- // when the array contains an unsupported number, toString returns null!
- JSONArray array = new JSONArray(Arrays.asList(5.5, Double.NaN));
- assertNull(array.toString());
- }
-
- public void testListConstructorCopiesContents() throws JSONException {
- List<Object> contents = Arrays.<Object>asList(5);
- JSONArray array = new JSONArray(contents);
- contents.set(0, 10);
- assertEquals(5, array.get(0));
- }
-
- public void testTokenerConstructor() throws JSONException {
- JSONArray object = new JSONArray(new JSONTokener("[false]"));
- assertEquals(1, object.length());
- assertEquals(false, object.get(0));
- }
-
- public void testTokenerConstructorWrongType() throws JSONException {
- try {
- new JSONArray(new JSONTokener("{\"foo\": false}"));
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testTokenerConstructorNull() throws JSONException {
- try {
- new JSONArray((JSONTokener) null);
- fail();
- } catch (NullPointerException e) {
- }
- }
-
- public void testTokenerConstructorParseFail() {
- try {
- new JSONArray(new JSONTokener("["));
- fail();
- } catch (JSONException e) {
- } catch (StackOverflowError e) {
- fail("Stack overflowed on input: \"[\"");
- }
- }
-
- public void testStringConstructor() throws JSONException {
- JSONArray object = new JSONArray("[false]");
- assertEquals(1, object.length());
- assertEquals(false, object.get(0));
- }
-
- public void testStringConstructorWrongType() throws JSONException {
- try {
- new JSONArray("{\"foo\": false}");
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testStringConstructorNull() throws JSONException {
- try {
- new JSONArray((String) null);
- fail();
- } catch (NullPointerException e) {
- }
- }
-
- public void testStringConstructorParseFail() {
- try {
- new JSONArray("[");
- fail();
- } catch (JSONException e) {
- } catch (StackOverflowError e) {
- fail("Stack overflowed on input: \"[\"");
- }
- }
-
- public void testCreate() throws JSONException {
- JSONArray array = new JSONArray(Arrays.asList(5.5, true));
- assertEquals(2, array.length());
- assertEquals(5.5, array.getDouble(0));
- assertEquals(true, array.get(1));
- assertEquals("[5.5,true]", array.toString());
- }
-
- public void testAccessOutOfBounds() throws JSONException {
- JSONArray array = new JSONArray();
- array.put("foo");
- assertEquals(null, array.opt(3));
- assertEquals(null, array.opt(-3));
- assertEquals("", array.optString(3));
- assertEquals("", array.optString(-3));
- try {
- array.get(3);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.get(-3);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.getString(3);
- fail();
- } catch (JSONException e) {
- }
- try {
- array.getString(-3);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void test_remove() throws Exception {
- JSONArray a = new JSONArray();
- assertEquals(null, a.remove(-1));
- assertEquals(null, a.remove(0));
-
- a.put("hello");
- assertEquals(null, a.remove(-1));
- assertEquals(null, a.remove(1));
- assertEquals("hello", a.remove(0));
- assertEquals(null, a.remove(0));
- }
-
- enum MyEnum { A, B, C; }
-
- // https://code.google.com/p/android/issues/detail?id=62539
- public void testEnums() throws Exception {
- // This works because it's in java.* and any class in there falls back to toString.
- JSONArray a1 = new JSONArray(java.lang.annotation.RetentionPolicy.values());
- assertEquals("[\"SOURCE\",\"CLASS\",\"RUNTIME\"]", a1.toString());
-
- // This doesn't because it's not.
- JSONArray a2 = new JSONArray(MyEnum.values());
- assertEquals("[null,null,null]", a2.toString());
- }
-}
diff --git a/org/json/JSONObjectTest.java b/org/json/JSONObjectTest.java
deleted file mode 100644
index 07d1cf64..00000000
--- a/org/json/JSONObjectTest.java
+++ /dev/null
@@ -1,1047 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.json;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONObjectTest extends TestCase {
-
- public void testEmptyObject() throws JSONException {
- JSONObject object = new JSONObject();
- assertEquals(0, object.length());
-
- // bogus (but documented) behaviour: returns null rather than the empty object!
- assertNull(object.names());
-
- // returns null rather than an empty array!
- assertNull(object.toJSONArray(new JSONArray()));
- assertEquals("{}", object.toString());
- assertEquals("{}", object.toString(5));
- try {
- object.get("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getBoolean("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getDouble("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getInt("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getJSONArray("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getJSONObject("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getLong("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getString("foo");
- fail();
- } catch (JSONException e) {
- }
- assertFalse(object.has("foo"));
- assertTrue(object.isNull("foo")); // isNull also means "is not present"
- assertNull(object.opt("foo"));
- assertEquals(false, object.optBoolean("foo"));
- assertEquals(true, object.optBoolean("foo", true));
- assertEquals(Double.NaN, object.optDouble("foo"));
- assertEquals(5.0, object.optDouble("foo", 5.0));
- assertEquals(0, object.optInt("foo"));
- assertEquals(5, object.optInt("foo", 5));
- assertEquals(null, object.optJSONArray("foo"));
- assertEquals(null, object.optJSONObject("foo"));
- assertEquals(0, object.optLong("foo"));
- assertEquals(Long.MAX_VALUE-1, object.optLong("foo", Long.MAX_VALUE-1));
- assertEquals("", object.optString("foo")); // empty string is default!
- assertEquals("bar", object.optString("foo", "bar"));
- assertNull(object.remove("foo"));
- }
-
- public void testEqualsAndHashCode() throws JSONException {
- JSONObject a = new JSONObject();
- JSONObject b = new JSONObject();
-
- // JSON object doesn't override either equals or hashCode (!)
- assertFalse(a.equals(b));
- assertEquals(a.hashCode(), System.identityHashCode(a));
- }
-
- public void testGet() throws JSONException {
- JSONObject object = new JSONObject();
- Object value = new Object();
- object.put("foo", value);
- object.put("bar", new Object());
- object.put("baz", new Object());
- assertSame(value, object.get("foo"));
- try {
- object.get("FOO");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.put(null, value);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.get(null);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testPut() throws JSONException {
- JSONObject object = new JSONObject();
- assertSame(object, object.put("foo", true));
- object.put("foo", false);
- assertEquals(false, object.get("foo"));
-
- object.put("foo", 5.0d);
- assertEquals(5.0d, object.get("foo"));
- object.put("foo", 0);
- assertEquals(0, object.get("foo"));
- object.put("bar", Long.MAX_VALUE - 1);
- assertEquals(Long.MAX_VALUE - 1, object.get("bar"));
- object.put("baz", "x");
- assertEquals("x", object.get("baz"));
- object.put("bar", JSONObject.NULL);
- assertSame(JSONObject.NULL, object.get("bar"));
- }
-
- public void testPutNullRemoves() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "bar");
- object.put("foo", (Collection) null);
- assertEquals(0, object.length());
- assertFalse(object.has("foo"));
- try {
- object.get("foo");
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testPutOpt() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "bar");
- object.putOpt("foo", null);
- assertEquals("bar", object.get("foo"));
- object.putOpt(null, null);
- assertEquals(1, object.length());
- object.putOpt(null, "bar");
- assertEquals(1, object.length());
- }
-
- public void testPutOptUnsupportedNumbers() throws JSONException {
- JSONObject object = new JSONObject();
- try {
- object.putOpt("foo", Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.putOpt("foo", Double.NEGATIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.putOpt("foo", Double.POSITIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testRemove() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "bar");
- assertEquals(null, object.remove(null));
- assertEquals(null, object.remove(""));
- assertEquals(null, object.remove("bar"));
- assertEquals("bar", object.remove("foo"));
- assertEquals(null, object.remove("foo"));
- }
-
- public void testBooleans() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", true);
- object.put("bar", false);
- object.put("baz", "true");
- object.put("quux", "false");
- assertEquals(4, object.length());
- assertEquals(true, object.getBoolean("foo"));
- assertEquals(false, object.getBoolean("bar"));
- assertEquals(true, object.getBoolean("baz"));
- assertEquals(false, object.getBoolean("quux"));
- assertFalse(object.isNull("foo"));
- assertFalse(object.isNull("quux"));
- assertTrue(object.has("foo"));
- assertTrue(object.has("quux"));
- assertFalse(object.has("missing"));
- assertEquals(true, object.optBoolean("foo"));
- assertEquals(false, object.optBoolean("bar"));
- assertEquals(true, object.optBoolean("baz"));
- assertEquals(false, object.optBoolean("quux"));
- assertEquals(false, object.optBoolean("missing"));
- assertEquals(true, object.optBoolean("foo", true));
- assertEquals(false, object.optBoolean("bar", true));
- assertEquals(true, object.optBoolean("baz", true));
- assertEquals(false, object.optBoolean("quux", true));
- assertEquals(true, object.optBoolean("missing", true));
-
- object.put("foo", "truE");
- object.put("bar", "FALSE");
- assertEquals(true, object.getBoolean("foo"));
- assertEquals(false, object.getBoolean("bar"));
- assertEquals(true, object.optBoolean("foo"));
- assertEquals(false, object.optBoolean("bar"));
- assertEquals(true, object.optBoolean("foo", false));
- assertEquals(false, object.optBoolean("bar", false));
- }
-
- // http://code.google.com/p/android/issues/detail?id=16411
- public void testCoerceStringToBoolean() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "maybe");
- try {
- object.getBoolean("foo");
- fail();
- } catch (JSONException expected) {
- }
- assertEquals(false, object.optBoolean("foo"));
- assertEquals(true, object.optBoolean("foo", true));
- }
-
- public void testNumbers() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", Double.MIN_VALUE);
- object.put("bar", 9223372036854775806L);
- object.put("baz", Double.MAX_VALUE);
- object.put("quux", -0d);
- assertEquals(4, object.length());
-
- String toString = object.toString();
- assertTrue(toString, toString.contains("\"foo\":4.9E-324"));
- assertTrue(toString, toString.contains("\"bar\":9223372036854775806"));
- assertTrue(toString, toString.contains("\"baz\":1.7976931348623157E308"));
-
- // toString() and getString() return different values for -0d!
- assertTrue(toString, toString.contains("\"quux\":-0}") // no trailing decimal point
- || toString.contains("\"quux\":-0,"));
-
- assertEquals(Double.MIN_VALUE, object.get("foo"));
- assertEquals(9223372036854775806L, object.get("bar"));
- assertEquals(Double.MAX_VALUE, object.get("baz"));
- assertEquals(-0d, object.get("quux"));
- assertEquals(Double.MIN_VALUE, object.getDouble("foo"));
- assertEquals(9.223372036854776E18, object.getDouble("bar"));
- assertEquals(Double.MAX_VALUE, object.getDouble("baz"));
- assertEquals(-0d, object.getDouble("quux"));
- assertEquals(0, object.getLong("foo"));
- assertEquals(9223372036854775806L, object.getLong("bar"));
- assertEquals(Long.MAX_VALUE, object.getLong("baz"));
- assertEquals(0, object.getLong("quux"));
- assertEquals(0, object.getInt("foo"));
- assertEquals(-2, object.getInt("bar"));
- assertEquals(Integer.MAX_VALUE, object.getInt("baz"));
- assertEquals(0, object.getInt("quux"));
- assertEquals(Double.MIN_VALUE, object.opt("foo"));
- assertEquals(9223372036854775806L, object.optLong("bar"));
- assertEquals(Double.MAX_VALUE, object.optDouble("baz"));
- assertEquals(0, object.optInt("quux"));
- assertEquals(Double.MIN_VALUE, object.opt("foo"));
- assertEquals(9223372036854775806L, object.optLong("bar"));
- assertEquals(Double.MAX_VALUE, object.optDouble("baz"));
- assertEquals(0, object.optInt("quux"));
- assertEquals(Double.MIN_VALUE, object.optDouble("foo", 5.0d));
- assertEquals(9223372036854775806L, object.optLong("bar", 1L));
- assertEquals(Long.MAX_VALUE, object.optLong("baz", 1L));
- assertEquals(0, object.optInt("quux", -1));
- assertEquals("4.9E-324", object.getString("foo"));
- assertEquals("9223372036854775806", object.getString("bar"));
- assertEquals("1.7976931348623157E308", object.getString("baz"));
- assertEquals("-0.0", object.getString("quux"));
- }
-
- public void testFloats() throws JSONException {
- JSONObject object = new JSONObject();
- try {
- object.put("foo", (Float) Float.NaN);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.put("foo", (Float) Float.NEGATIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.put("foo", (Float) Float.POSITIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testOtherNumbers() throws JSONException {
- Number nan = new Number() {
- public int intValue() {
- throw new UnsupportedOperationException();
- }
- public long longValue() {
- throw new UnsupportedOperationException();
- }
- public float floatValue() {
- throw new UnsupportedOperationException();
- }
- public double doubleValue() {
- return Double.NaN;
- }
- @Override public String toString() {
- return "x";
- }
- };
-
- JSONObject object = new JSONObject();
- try {
- object.put("foo", nan);
- fail("Object.put() accepted a NaN (via a custom Number class)");
- } catch (JSONException e) {
- }
- }
-
- public void testForeignObjects() throws JSONException {
- Object foreign = new Object() {
- @Override public String toString() {
- return "x";
- }
- };
-
- // foreign object types are accepted and treated as Strings!
- JSONObject object = new JSONObject();
- object.put("foo", foreign);
- assertEquals("{\"foo\":\"x\"}", object.toString());
- }
-
- public void testNullKeys() {
- try {
- new JSONObject().put(null, false);
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONObject().put(null, 0.0d);
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONObject().put(null, 5);
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONObject().put(null, 5L);
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONObject().put(null, "foo");
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testStrings() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "true");
- object.put("bar", "5.5");
- object.put("baz", "9223372036854775806");
- object.put("quux", "null");
- object.put("height", "5\"8' tall");
-
- assertTrue(object.toString().contains("\"foo\":\"true\""));
- assertTrue(object.toString().contains("\"bar\":\"5.5\""));
- assertTrue(object.toString().contains("\"baz\":\"9223372036854775806\""));
- assertTrue(object.toString().contains("\"quux\":\"null\""));
- assertTrue(object.toString().contains("\"height\":\"5\\\"8' tall\""));
-
- assertEquals("true", object.get("foo"));
- assertEquals("null", object.getString("quux"));
- assertEquals("5\"8' tall", object.getString("height"));
- assertEquals("true", object.opt("foo"));
- assertEquals("5.5", object.optString("bar"));
- assertEquals("true", object.optString("foo", "x"));
- assertFalse(object.isNull("foo"));
-
- assertEquals(true, object.getBoolean("foo"));
- assertEquals(true, object.optBoolean("foo"));
- assertEquals(true, object.optBoolean("foo", false));
- assertEquals(0, object.optInt("foo"));
- assertEquals(-2, object.optInt("foo", -2));
-
- assertEquals(5.5d, object.getDouble("bar"));
- assertEquals(5L, object.getLong("bar"));
- assertEquals(5, object.getInt("bar"));
- assertEquals(5, object.optInt("bar", 3));
-
- // The last digit of the string is a 6 but getLong returns a 7. It's probably parsing as a
- // double and then converting that to a long. This is consistent with JavaScript.
- assertEquals(9223372036854775807L, object.getLong("baz"));
- assertEquals(9.223372036854776E18, object.getDouble("baz"));
- assertEquals(Integer.MAX_VALUE, object.getInt("baz"));
-
- assertFalse(object.isNull("quux"));
- try {
- object.getDouble("quux");
- fail();
- } catch (JSONException e) {
- }
- assertEquals(Double.NaN, object.optDouble("quux"));
- assertEquals(-1.0d, object.optDouble("quux", -1.0d));
-
- object.put("foo", "TRUE");
- assertEquals(true, object.getBoolean("foo"));
- }
-
- public void testJSONObjects() throws JSONException {
- JSONObject object = new JSONObject();
-
- JSONArray a = new JSONArray();
- JSONObject b = new JSONObject();
- object.put("foo", a);
- object.put("bar", b);
-
- assertSame(a, object.getJSONArray("foo"));
- assertSame(b, object.getJSONObject("bar"));
- try {
- object.getJSONObject("foo");
- fail();
- } catch (JSONException e) {
- }
- try {
- object.getJSONArray("bar");
- fail();
- } catch (JSONException e) {
- }
- assertEquals(a, object.optJSONArray("foo"));
- assertEquals(b, object.optJSONObject("bar"));
- assertEquals(null, object.optJSONArray("bar"));
- assertEquals(null, object.optJSONObject("foo"));
- }
-
- public void testNullCoercionToString() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", JSONObject.NULL);
- assertEquals("null", object.getString("foo"));
- }
-
- public void testArrayCoercion() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "[true]");
- try {
- object.getJSONArray("foo");
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testObjectCoercion() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "{}");
- try {
- object.getJSONObject("foo");
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testAccumulateValueChecking() throws JSONException {
- JSONObject object = new JSONObject();
- try {
- object.accumulate("foo", Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- object.accumulate("foo", 1);
- try {
- object.accumulate("foo", Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- object.accumulate("foo", 2);
- try {
- object.accumulate("foo", Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testToJSONArray() throws JSONException {
- JSONObject object = new JSONObject();
- Object value = new Object();
- object.put("foo", true);
- object.put("bar", 5.0d);
- object.put("baz", -0.0d);
- object.put("quux", value);
-
- JSONArray names = new JSONArray();
- names.put("baz");
- names.put("quux");
- names.put("foo");
-
- JSONArray array = object.toJSONArray(names);
- assertEquals(-0.0d, array.get(0));
- assertEquals(value, array.get(1));
- assertEquals(true, array.get(2));
-
- object.put("foo", false);
- assertEquals(true, array.get(2));
- }
-
- public void testToJSONArrayMissingNames() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", true);
- object.put("bar", 5.0d);
- object.put("baz", JSONObject.NULL);
-
- JSONArray names = new JSONArray();
- names.put("bar");
- names.put("foo");
- names.put("quux");
- names.put("baz");
-
- JSONArray array = object.toJSONArray(names);
- assertEquals(4, array.length());
-
- assertEquals(5.0d, array.get(0));
- assertEquals(true, array.get(1));
- try {
- array.get(2);
- fail();
- } catch (JSONException e) {
- }
- assertEquals(JSONObject.NULL, array.get(3));
- }
-
- public void testToJSONArrayNull() throws JSONException {
- JSONObject object = new JSONObject();
- assertEquals(null, object.toJSONArray(null));
- object.put("foo", 5);
- try {
- object.toJSONArray(null);
- } catch (JSONException e) {
- }
- }
-
- public void testToJSONArrayEndsUpEmpty() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- JSONArray array = new JSONArray();
- array.put("bar");
- assertEquals(1, object.toJSONArray(array).length());
- }
-
- public void testToJSONArrayNonString() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- object.put("null", 10);
- object.put("false", 15);
-
- JSONArray names = new JSONArray();
- names.put(JSONObject.NULL);
- names.put(false);
- names.put("foo");
-
- // array elements are converted to strings to do name lookups on the map!
- JSONArray array = object.toJSONArray(names);
- assertEquals(3, array.length());
- assertEquals(10, array.get(0));
- assertEquals(15, array.get(1));
- assertEquals(5, array.get(2));
- }
-
- public void testPutUnsupportedNumbers() throws JSONException {
- JSONObject object = new JSONObject();
- try {
- object.put("foo", Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.put("foo", Double.NEGATIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.put("foo", Double.POSITIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testPutUnsupportedNumbersAsObjects() throws JSONException {
- JSONObject object = new JSONObject();
- try {
- object.put("foo", (Double) Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.put("foo", (Double) Double.NEGATIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- try {
- object.put("foo", (Double) Double.POSITIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- }
-
- /**
- * Although JSONObject is usually defensive about which numbers it accepts,
- * it doesn't check inputs in its constructor.
- */
- public void testCreateWithUnsupportedNumbers() throws JSONException {
- Map<String, Object> contents = new HashMap<String, Object>();
- contents.put("foo", Double.NaN);
- contents.put("bar", Double.NEGATIVE_INFINITY);
- contents.put("baz", Double.POSITIVE_INFINITY);
-
- JSONObject object = new JSONObject(contents);
- assertEquals(Double.NaN, object.get("foo"));
- assertEquals(Double.NEGATIVE_INFINITY, object.get("bar"));
- assertEquals(Double.POSITIVE_INFINITY, object.get("baz"));
- }
-
- public void testToStringWithUnsupportedNumbers() {
- // when the object contains an unsupported number, toString returns null!
- JSONObject object = new JSONObject(Collections.singletonMap("foo", Double.NaN));
- assertEquals(null, object.toString());
- }
-
- public void testMapConstructorCopiesContents() throws JSONException {
- Map<String, Object> contents = new HashMap<String, Object>();
- contents.put("foo", 5);
- JSONObject object = new JSONObject(contents);
- contents.put("foo", 10);
- assertEquals(5, object.get("foo"));
- }
-
- public void testMapConstructorWithBogusEntries() {
- Map<Object, Object> contents = new HashMap<Object, Object>();
- contents.put(5, 5);
-
- try {
- new JSONObject(contents);
- fail("JSONObject constructor doesn't validate its input!");
- } catch (Exception e) {
- }
- }
-
- public void testTokenerConstructor() throws JSONException {
- JSONObject object = new JSONObject(new JSONTokener("{\"foo\": false}"));
- assertEquals(1, object.length());
- assertEquals(false, object.get("foo"));
- }
-
- public void testTokenerConstructorWrongType() throws JSONException {
- try {
- new JSONObject(new JSONTokener("[\"foo\", false]"));
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testTokenerConstructorNull() throws JSONException {
- try {
- new JSONObject((JSONTokener) null);
- fail();
- } catch (NullPointerException e) {
- }
- }
-
- public void testTokenerConstructorParseFail() {
- try {
- new JSONObject(new JSONTokener("{"));
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testStringConstructor() throws JSONException {
- JSONObject object = new JSONObject("{\"foo\": false}");
- assertEquals(1, object.length());
- assertEquals(false, object.get("foo"));
- }
-
- public void testStringConstructorWrongType() throws JSONException {
- try {
- new JSONObject("[\"foo\", false]");
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testStringConstructorNull() throws JSONException {
- try {
- new JSONObject((String) null);
- fail();
- } catch (NullPointerException e) {
- }
- }
-
- public void testStringConstructorParseFail() {
- try {
- new JSONObject("{");
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testCopyConstructor() throws JSONException {
- JSONObject source = new JSONObject();
- source.put("a", JSONObject.NULL);
- source.put("b", false);
- source.put("c", 5);
-
- JSONObject copy = new JSONObject(source, new String[] { "a", "c" });
- assertEquals(2, copy.length());
- assertEquals(JSONObject.NULL, copy.get("a"));
- assertEquals(5, copy.get("c"));
- assertEquals(null, copy.opt("b"));
- }
-
- public void testCopyConstructorMissingName() throws JSONException {
- JSONObject source = new JSONObject();
- source.put("a", JSONObject.NULL);
- source.put("b", false);
- source.put("c", 5);
-
- JSONObject copy = new JSONObject(source, new String[]{ "a", "c", "d" });
- assertEquals(2, copy.length());
- assertEquals(JSONObject.NULL, copy.get("a"));
- assertEquals(5, copy.get("c"));
- assertEquals(0, copy.optInt("b"));
- }
-
- public void testAccumulateMutatesInPlace() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- object.accumulate("foo", 6);
- JSONArray array = object.getJSONArray("foo");
- assertEquals("[5,6]", array.toString());
- object.accumulate("foo", 7);
- assertEquals("[5,6,7]", array.toString());
- }
-
- public void testAccumulateExistingArray() throws JSONException {
- JSONArray array = new JSONArray();
- JSONObject object = new JSONObject();
- object.put("foo", array);
- object.accumulate("foo", 5);
- assertEquals("[5]", array.toString());
- }
-
- public void testAccumulatePutArray() throws JSONException {
- JSONObject object = new JSONObject();
- object.accumulate("foo", 5);
- assertEquals("{\"foo\":5}", object.toString());
- object.accumulate("foo", new JSONArray());
- assertEquals("{\"foo\":[5,[]]}", object.toString());
- }
-
- public void testAccumulateNull() {
- JSONObject object = new JSONObject();
- try {
- object.accumulate(null, 5);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testEmptyStringKey() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("", 5);
- assertEquals(5, object.get(""));
- assertEquals("{\"\":5}", object.toString());
- }
-
- public void testNullValue() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", JSONObject.NULL);
- object.put("bar", (Collection) null);
-
- // there are two ways to represent null; each behaves differently!
- assertTrue(object.has("foo"));
- assertFalse(object.has("bar"));
- assertTrue(object.isNull("foo"));
- assertTrue(object.isNull("bar"));
- }
-
- public void testNullValue_equalsAndHashCode() {
- assertTrue(JSONObject.NULL.equals(null)); // guaranteed by javadoc
- // not guaranteed by javadoc, but seems like a good idea
- assertEquals(Objects.hashCode(null), JSONObject.NULL.hashCode());
- }
-
- public void testHas() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- assertTrue(object.has("foo"));
- assertFalse(object.has("bar"));
- assertFalse(object.has(null));
- }
-
- public void testOptNull() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", "bar");
- assertEquals(null, object.opt(null));
- assertEquals(false, object.optBoolean(null));
- assertEquals(Double.NaN, object.optDouble(null));
- assertEquals(0, object.optInt(null));
- assertEquals(0L, object.optLong(null));
- assertEquals(null, object.optJSONArray(null));
- assertEquals(null, object.optJSONObject(null));
- assertEquals("", object.optString(null));
- assertEquals(true, object.optBoolean(null, true));
- assertEquals(0.0d, object.optDouble(null, 0.0d));
- assertEquals(1, object.optInt(null, 1));
- assertEquals(1L, object.optLong(null, 1L));
- assertEquals("baz", object.optString(null, "baz"));
- }
-
- public void testToStringWithIndentFactor() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", new JSONArray(Arrays.asList(5, 6)));
- object.put("bar", new JSONObject());
- String foobar = "{\n" +
- " \"foo\": [\n" +
- " 5,\n" +
- " 6\n" +
- " ],\n" +
- " \"bar\": {}\n" +
- "}";
- String barfoo = "{\n" +
- " \"bar\": {},\n" +
- " \"foo\": [\n" +
- " 5,\n" +
- " 6\n" +
- " ]\n" +
- "}";
- String string = object.toString(5);
- assertTrue(string, foobar.equals(string) || barfoo.equals(string));
- }
-
- public void testNames() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- object.put("bar", 6);
- object.put("baz", 7);
- JSONArray array = object.names();
- assertTrue(array.toString().contains("foo"));
- assertTrue(array.toString().contains("bar"));
- assertTrue(array.toString().contains("baz"));
- }
-
- public void testKeysEmptyObject() {
- JSONObject object = new JSONObject();
- assertFalse(object.keys().hasNext());
- try {
- object.keys().next();
- fail();
- } catch (NoSuchElementException e) {
- }
- }
-
- public void testKeys() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- object.put("bar", 6);
- object.put("foo", 7);
-
- @SuppressWarnings("unchecked")
- Iterator<String> keys = (Iterator<String>) object.keys();
- Set<String> result = new HashSet<String>();
- assertTrue(keys.hasNext());
- result.add(keys.next());
- assertTrue(keys.hasNext());
- result.add(keys.next());
- assertFalse(keys.hasNext());
- assertEquals(new HashSet<String>(Arrays.asList("foo", "bar")), result);
-
- try {
- keys.next();
- fail();
- } catch (NoSuchElementException e) {
- }
- }
-
- public void testMutatingKeysMutatesObject() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- Iterator keys = object.keys();
- keys.next();
- keys.remove();
- assertEquals(0, object.length());
- }
-
- public void testQuote() {
- // covered by JSONStringerTest.testEscaping
- }
-
- public void testQuoteNull() throws JSONException {
- assertEquals("\"\"", JSONObject.quote(null));
- }
-
- public void testNumberToString() throws JSONException {
- assertEquals("5", JSONObject.numberToString(5));
- assertEquals("-0", JSONObject.numberToString(-0.0d));
- assertEquals("9223372036854775806", JSONObject.numberToString(9223372036854775806L));
- assertEquals("4.9E-324", JSONObject.numberToString(Double.MIN_VALUE));
- assertEquals("1.7976931348623157E308", JSONObject.numberToString(Double.MAX_VALUE));
- try {
- JSONObject.numberToString(Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- try {
- JSONObject.numberToString(Double.NEGATIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- try {
- JSONObject.numberToString(Double.POSITIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- assertEquals("0.001", JSONObject.numberToString(new BigDecimal("0.001")));
- assertEquals("9223372036854775806",
- JSONObject.numberToString(new BigInteger("9223372036854775806")));
- try {
- JSONObject.numberToString(null);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void test_wrap() throws Exception {
- assertEquals(JSONObject.NULL, JSONObject.wrap(null));
-
- JSONArray a = new JSONArray();
- assertEquals(a, JSONObject.wrap(a));
-
- JSONObject o = new JSONObject();
- assertEquals(o, JSONObject.wrap(o));
-
- assertEquals(JSONObject.NULL, JSONObject.wrap(JSONObject.NULL));
-
- assertTrue(JSONObject.wrap(new byte[0]) instanceof JSONArray);
- assertTrue(JSONObject.wrap(new ArrayList<String>()) instanceof JSONArray);
- assertTrue(JSONObject.wrap(new HashMap<String, String>()) instanceof JSONObject);
- assertTrue(JSONObject.wrap(Double.valueOf(0)) instanceof Double);
- assertTrue(JSONObject.wrap("hello") instanceof String);
- }
-
- // https://code.google.com/p/android/issues/detail?id=55114
- public void test_toString_listAsMapValue() throws Exception {
- ArrayList<Object> list = new ArrayList<Object>();
- list.add("a");
- list.add(new ArrayList<String>());
- Map<String, Object> map = new TreeMap<String, Object>();
- map.put("x", "l");
- map.put("y", list);
- assertEquals("{\"x\":\"l\",\"y\":[\"a\",[]]}", new JSONObject(map).toString());
- }
-
- public void testAppendExistingInvalidKey() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("foo", 5);
- try {
- object.append("foo", 6);
- fail();
- } catch (JSONException expected) {
- }
- }
-
- public void testAppendExistingArray() throws JSONException {
- JSONArray array = new JSONArray();
- JSONObject object = new JSONObject();
- object.put("foo", array);
- object.append("foo", 5);
- assertEquals("[5]", array.toString());
- }
-
- public void testAppendPutArray() throws JSONException {
- JSONObject object = new JSONObject();
- object.append("foo", 5);
- assertEquals("{\"foo\":[5]}", object.toString());
- object.append("foo", new JSONArray());
- assertEquals("{\"foo\":[5,[]]}", object.toString());
- }
-
- public void testAppendNull() {
- JSONObject object = new JSONObject();
- try {
- object.append(null, 5);
- fail();
- } catch (JSONException e) {
- }
- }
-
- // https://code.google.com/p/android/issues/detail?id=103641
- public void testInvalidUnicodeEscape() {
- try {
- new JSONObject("{\"q\":\"\\u\", \"r\":[]}");
- fail();
- } catch (JSONException expected) {
- }
- }
-}
diff --git a/org/json/JSONStringerTest.java b/org/json/JSONStringerTest.java
deleted file mode 100644
index 1e4e0ecd..00000000
--- a/org/json/JSONStringerTest.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.json;
-
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONStringerTest extends TestCase {
-
- public void testEmptyStringer() {
- // why isn't this the empty string?
- assertNull(new JSONStringer().toString());
- }
-
- public void testValueJSONNull() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.value(JSONObject.NULL);
- stringer.endArray();
- assertEquals("[null]", stringer.toString());
- }
-
- public void testEmptyObject() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.object();
- stringer.endObject();
- assertEquals("{}", stringer.toString());
- }
-
- public void testEmptyArray() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.endArray();
- assertEquals("[]", stringer.toString());
- }
-
- public void testArray() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.value(false);
- stringer.value(5.0);
- stringer.value(5L);
- stringer.value("five");
- stringer.value(null);
- stringer.endArray();
- assertEquals("[false,5,5,\"five\",null]", stringer.toString());
- }
-
- public void testValueObjectMethods() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.value(Boolean.FALSE);
- stringer.value(Double.valueOf(5.0));
- stringer.value(Long.valueOf(5L));
- stringer.endArray();
- assertEquals("[false,5,5]", stringer.toString());
- }
-
- public void testKeyValue() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.object();
- stringer.key("a").value(false);
- stringer.key("b").value(5.0);
- stringer.key("c").value(5L);
- stringer.key("d").value("five");
- stringer.key("e").value(null);
- stringer.endObject();
- assertEquals("{\"a\":false," +
- "\"b\":5," +
- "\"c\":5," +
- "\"d\":\"five\"," +
- "\"e\":null}", stringer.toString());
- }
-
- /**
- * Test what happens when extreme values are emitted. Such values are likely
- * to be rounded during parsing.
- */
- public void testNumericRepresentations() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.value(Long.MAX_VALUE);
- stringer.value(Double.MIN_VALUE);
- stringer.endArray();
- assertEquals("[9223372036854775807,4.9E-324]", stringer.toString());
- }
-
- public void testWeirdNumbers() throws JSONException {
- try {
- new JSONStringer().array().value(Double.NaN);
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().array().value(Double.NEGATIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().array().value(Double.POSITIVE_INFINITY);
- fail();
- } catch (JSONException e) {
- }
-
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.value(-0.0d);
- stringer.value(0.0d);
- stringer.endArray();
- assertEquals("[-0,0]", stringer.toString());
- }
-
- public void testMismatchedScopes() {
- try {
- new JSONStringer().key("a");
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().value("a");
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().endObject();
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().endArray();
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().array().endObject();
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().object().endArray();
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().object().key("a").key("a");
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONStringer().object().value(false);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testNullKey() {
- try {
- new JSONStringer().object().key(null);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testRepeatedKey() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.object();
- stringer.key("a").value(true);
- stringer.key("a").value(false);
- stringer.endObject();
- // JSONStringer doesn't attempt to detect duplicates
- assertEquals("{\"a\":true,\"a\":false}", stringer.toString());
- }
-
- public void testEmptyKey() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.object();
- stringer.key("").value(false);
- stringer.endObject();
- assertEquals("{\"\":false}", stringer.toString()); // legit behaviour!
- }
-
- public void testEscaping() throws JSONException {
- assertEscapedAllWays("a", "a");
- assertEscapedAllWays("a\\\"", "a\"");
- assertEscapedAllWays("\\\"", "\"");
- assertEscapedAllWays(":", ":");
- assertEscapedAllWays(",", ",");
- assertEscapedAllWays("\\b", "\b");
- assertEscapedAllWays("\\f", "\f");
- assertEscapedAllWays("\\n", "\n");
- assertEscapedAllWays("\\r", "\r");
- assertEscapedAllWays("\\t", "\t");
- assertEscapedAllWays(" ", " ");
- assertEscapedAllWays("\\\\", "\\");
- assertEscapedAllWays("{", "{");
- assertEscapedAllWays("}", "}");
- assertEscapedAllWays("[", "[");
- assertEscapedAllWays("]", "]");
- assertEscapedAllWays("\\u0000", "\0");
- assertEscapedAllWays("\\u0019", "\u0019");
- assertEscapedAllWays(" ", "\u0020");
- }
-
- private void assertEscapedAllWays(String escaped, String original) throws JSONException {
- assertEquals("{\"" + escaped + "\":false}",
- new JSONStringer().object().key(original).value(false).endObject().toString());
- assertEquals("{\"a\":\"" + escaped + "\"}",
- new JSONStringer().object().key("a").value(original).endObject().toString());
- assertEquals("[\"" + escaped + "\"]",
- new JSONStringer().array().value(original).endArray().toString());
- assertEquals("\"" + escaped + "\"", JSONObject.quote(original));
- }
-
- public void testJSONArrayAsValue() throws JSONException {
- JSONArray array = new JSONArray();
- array.put(false);
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.value(array);
- stringer.endArray();
- assertEquals("[[false]]", stringer.toString());
- }
-
- public void testJSONObjectAsValue() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("a", false);
- JSONStringer stringer = new JSONStringer();
- stringer.object();
- stringer.key("b").value(object);
- stringer.endObject();
- assertEquals("{\"b\":{\"a\":false}}", stringer.toString());
- }
-
- public void testArrayNestingMaxDepthSupports20() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- for (int i = 0; i < 20; i++) {
- stringer.array();
- }
- for (int i = 0; i < 20; i++) {
- stringer.endArray();
- }
- assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringer.toString());
-
- stringer = new JSONStringer();
- for (int i = 0; i < 20; i++) {
- stringer.array();
- }
- }
-
- public void testObjectNestingMaxDepthSupports20() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- for (int i = 0; i < 20; i++) {
- stringer.object();
- stringer.key("a");
- }
- stringer.value(false);
- for (int i = 0; i < 20; i++) {
- stringer.endObject();
- }
- assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":" +
- "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":false" +
- "}}}}}}}}}}}}}}}}}}}}", stringer.toString());
-
- stringer = new JSONStringer();
- for (int i = 0; i < 20; i++) {
- stringer.object();
- stringer.key("a");
- }
- }
-
- public void testMixedMaxDepthSupports20() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- for (int i = 0; i < 20; i+=2) {
- stringer.array();
- stringer.object();
- stringer.key("a");
- }
- stringer.value(false);
- for (int i = 0; i < 20; i+=2) {
- stringer.endObject();
- stringer.endArray();
- }
- assertEquals("[{\"a\":[{\"a\":[{\"a\":[{\"a\":[{\"a\":" +
- "[{\"a\":[{\"a\":[{\"a\":[{\"a\":[{\"a\":false" +
- "}]}]}]}]}]}]}]}]}]}]", stringer.toString());
-
- stringer = new JSONStringer();
- for (int i = 0; i < 20; i+=2) {
- stringer.array();
- stringer.object();
- stringer.key("a");
- }
- }
-
- public void testMaxDepthWithArrayValue() throws JSONException {
- JSONArray array = new JSONArray();
- array.put(false);
-
- JSONStringer stringer = new JSONStringer();
- for (int i = 0; i < 20; i++) {
- stringer.array();
- }
- stringer.value(array);
- for (int i = 0; i < 20; i++) {
- stringer.endArray();
- }
- assertEquals("[[[[[[[[[[[[[[[[[[[[[false]]]]]]]]]]]]]]]]]]]]]", stringer.toString());
- }
-
- public void testMaxDepthWithObjectValue() throws JSONException {
- JSONObject object = new JSONObject();
- object.put("a", false);
- JSONStringer stringer = new JSONStringer();
- for (int i = 0; i < 20; i++) {
- stringer.object();
- stringer.key("b");
- }
- stringer.value(object);
- for (int i = 0; i < 20; i++) {
- stringer.endObject();
- }
- assertEquals("{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":" +
- "{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":" +
- "{\"a\":false}}}}}}}}}}}}}}}}}}}}}", stringer.toString());
- }
-
- public void testMultipleRoots() throws JSONException {
- JSONStringer stringer = new JSONStringer();
- stringer.array();
- stringer.endArray();
- try {
- stringer.object();
- fail();
- } catch (JSONException e) {
- }
- }
-}
diff --git a/org/json/JSONTokenerTest.java b/org/json/JSONTokenerTest.java
deleted file mode 100644
index 1152e465..00000000
--- a/org/json/JSONTokenerTest.java
+++ /dev/null
@@ -1,619 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.json;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONTokenerTest extends TestCase {
-
- public void testNulls() throws JSONException {
- // JSONTokener accepts null, only to fail later on almost all APIs!
- new JSONTokener(null).back();
-
- try {
- new JSONTokener(null).more();
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).next();
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).next(3);
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).next('A');
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).nextClean();
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).nextString('"');
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).nextTo('A');
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).nextTo("ABC");
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).nextValue();
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).skipPast("ABC");
- fail();
- } catch (NullPointerException e) {
- }
-
- try {
- new JSONTokener(null).skipTo('A');
- fail();
- } catch (NullPointerException e) {
- }
-
- assertEquals("foo! at character 0 of null",
- new JSONTokener(null).syntaxError("foo!").getMessage());
-
- assertEquals(" at character 0 of null", new JSONTokener(null).toString());
- }
-
- public void testEmptyString() throws JSONException {
- JSONTokener backTokener = new JSONTokener("");
- backTokener.back();
- assertEquals(" at character 0 of ", backTokener.toString());
- assertFalse(new JSONTokener("").more());
- assertEquals('\0', new JSONTokener("").next());
- try {
- new JSONTokener("").next(3);
- fail();
- } catch (JSONException expected) {
- }
- try {
- new JSONTokener("").next('A');
- fail();
- } catch (JSONException e) {
- }
- assertEquals('\0', new JSONTokener("").nextClean());
- try {
- new JSONTokener("").nextString('"');
- fail();
- } catch (JSONException e) {
- }
- assertEquals("", new JSONTokener("").nextTo('A'));
- assertEquals("", new JSONTokener("").nextTo("ABC"));
- try {
- new JSONTokener("").nextValue();
- fail();
- } catch (JSONException e) {
- }
- new JSONTokener("").skipPast("ABC");
- assertEquals('\0', new JSONTokener("").skipTo('A'));
- assertEquals("foo! at character 0 of ",
- new JSONTokener("").syntaxError("foo!").getMessage());
- assertEquals(" at character 0 of ", new JSONTokener("").toString());
- }
-
- public void testCharacterNavigation() throws JSONException {
- JSONTokener abcdeTokener = new JSONTokener("ABCDE");
- assertEquals('A', abcdeTokener.next());
- assertEquals('B', abcdeTokener.next('B'));
- assertEquals("CD", abcdeTokener.next(2));
- try {
- abcdeTokener.next(2);
- fail();
- } catch (JSONException e) {
- }
- assertEquals('E', abcdeTokener.nextClean());
- assertEquals('\0', abcdeTokener.next());
- assertFalse(abcdeTokener.more());
- abcdeTokener.back();
- assertTrue(abcdeTokener.more());
- assertEquals('E', abcdeTokener.next());
- }
-
- public void testBackNextAndMore() throws JSONException {
- JSONTokener abcTokener = new JSONTokener("ABC");
- assertTrue(abcTokener.more());
- abcTokener.next();
- abcTokener.next();
- assertTrue(abcTokener.more());
- abcTokener.next();
- assertFalse(abcTokener.more());
- abcTokener.back();
- assertTrue(abcTokener.more());
- abcTokener.next();
- assertFalse(abcTokener.more());
- abcTokener.back();
- abcTokener.back();
- abcTokener.back();
- abcTokener.back(); // you can back up before the beginning of a String!
- assertEquals('A', abcTokener.next());
- }
-
- public void testNextMatching() throws JSONException {
- JSONTokener abcdTokener = new JSONTokener("ABCD");
- assertEquals('A', abcdTokener.next('A'));
- try {
- abcdTokener.next('C'); // although it failed, this op consumes a character of input
- fail();
- } catch (JSONException e) {
- }
- assertEquals('C', abcdTokener.next('C'));
- assertEquals('D', abcdTokener.next('D'));
- try {
- abcdTokener.next('E');
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testNextN() throws JSONException {
- JSONTokener abcdeTokener = new JSONTokener("ABCDEF");
- assertEquals("", abcdeTokener.next(0));
- try {
- abcdeTokener.next(7);
- fail();
- } catch (JSONException e) {
- }
- assertEquals("ABC", abcdeTokener.next(3));
- try {
- abcdeTokener.next(4);
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testNextNWithAllRemaining() throws JSONException {
- JSONTokener tokener = new JSONTokener("ABCDEF");
- tokener.next(3);
- try {
- tokener.next(3);
- } catch (JSONException e) {
- AssertionFailedError error = new AssertionFailedError("off-by-one error?");
- error.initCause(e);
- throw error;
- }
- }
-
- public void testNext0() throws JSONException {
- JSONTokener tokener = new JSONTokener("ABCDEF");
- tokener.next(5);
- tokener.next();
- try {
- tokener.next(0);
- } catch (JSONException e) {
- Error error = new AssertionFailedError("Returning an empty string should be valid");
- error.initCause(e);
- throw error;
- }
- }
-
- public void testNextCleanComments() throws JSONException {
- JSONTokener tokener = new JSONTokener(
- " A /*XX*/B/*XX//XX\n//XX\nXX*/C//X//X//X\nD/*X*///X\n");
- assertEquals('A', tokener.nextClean());
- assertEquals('B', tokener.nextClean());
- assertEquals('C', tokener.nextClean());
- assertEquals('D', tokener.nextClean());
- assertEquals('\0', tokener.nextClean());
- }
-
- public void testNextCleanNestedCStyleComments() throws JSONException {
- JSONTokener tokener = new JSONTokener("A /* B /* C */ D */ E");
- assertEquals('A', tokener.nextClean());
- assertEquals('D', tokener.nextClean());
- assertEquals('*', tokener.nextClean());
- assertEquals('/', tokener.nextClean());
- assertEquals('E', tokener.nextClean());
- }
-
- /**
- * Some applications rely on parsing '#' to lead an end-of-line comment.
- * http://b/2571423
- */
- public void testNextCleanHashComments() throws JSONException {
- JSONTokener tokener = new JSONTokener("A # B */ /* C */ \nD #");
- assertEquals('A', tokener.nextClean());
- assertEquals('D', tokener.nextClean());
- assertEquals('\0', tokener.nextClean());
- }
-
- public void testNextCleanCommentsTrailingSingleSlash() throws JSONException {
- JSONTokener tokener = new JSONTokener(" / S /");
- assertEquals('/', tokener.nextClean());
- assertEquals('S', tokener.nextClean());
- assertEquals('/', tokener.nextClean());
- assertEquals("nextClean doesn't consume a trailing slash",
- '\0', tokener.nextClean());
- }
-
- public void testNextCleanTrailingOpenComment() throws JSONException {
- try {
- new JSONTokener(" /* ").nextClean();
- fail();
- } catch (JSONException e) {
- }
- assertEquals('\0', new JSONTokener(" // ").nextClean());
- }
-
- public void testNextCleanNewlineDelimiters() throws JSONException {
- assertEquals('B', new JSONTokener(" // \r\n B ").nextClean());
- assertEquals('B', new JSONTokener(" // \n B ").nextClean());
- assertEquals('B', new JSONTokener(" // \r B ").nextClean());
- }
-
- public void testNextCleanSkippedWhitespace() throws JSONException {
- assertEquals("character tabulation", 'A', new JSONTokener("\tA").nextClean());
- assertEquals("line feed", 'A', new JSONTokener("\nA").nextClean());
- assertEquals("carriage return", 'A', new JSONTokener("\rA").nextClean());
- assertEquals("space", 'A', new JSONTokener(" A").nextClean());
- }
-
- /**
- * Tests which characters tokener treats as ignorable whitespace. See Kevin Bourrillion's
- * <a href="https://spreadsheets.google.com/pub?key=pd8dAQyHbdewRsnE5x5GzKQ">list
- * of whitespace characters</a>.
- */
- public void testNextCleanRetainedWhitespace() throws JSONException {
- assertNotClean("null", '\u0000');
- assertNotClean("next line", '\u0085');
- assertNotClean("non-breaking space", '\u00a0');
- assertNotClean("ogham space mark", '\u1680');
- assertNotClean("mongolian vowel separator", '\u180e');
- assertNotClean("en quad", '\u2000');
- assertNotClean("em quad", '\u2001');
- assertNotClean("en space", '\u2002');
- assertNotClean("em space", '\u2003');
- assertNotClean("three-per-em space", '\u2004');
- assertNotClean("four-per-em space", '\u2005');
- assertNotClean("six-per-em space", '\u2006');
- assertNotClean("figure space", '\u2007');
- assertNotClean("punctuation space", '\u2008');
- assertNotClean("thin space", '\u2009');
- assertNotClean("hair space", '\u200a');
- assertNotClean("zero-width space", '\u200b');
- assertNotClean("left-to-right mark", '\u200e');
- assertNotClean("right-to-left mark", '\u200f');
- assertNotClean("line separator", '\u2028');
- assertNotClean("paragraph separator", '\u2029');
- assertNotClean("narrow non-breaking space", '\u202f');
- assertNotClean("medium mathematical space", '\u205f');
- assertNotClean("ideographic space", '\u3000');
- assertNotClean("line tabulation", '\u000b');
- assertNotClean("form feed", '\u000c');
- assertNotClean("information separator 4", '\u001c');
- assertNotClean("information separator 3", '\u001d');
- assertNotClean("information separator 2", '\u001e');
- assertNotClean("information separator 1", '\u001f');
- }
-
- private void assertNotClean(String name, char c) throws JSONException {
- assertEquals("The character " + name + " is not whitespace according to the JSON spec.",
- c, new JSONTokener(new String(new char[] { c, 'A' })).nextClean());
- }
-
- public void testNextString() throws JSONException {
- assertEquals("", new JSONTokener("'").nextString('\''));
- assertEquals("", new JSONTokener("\"").nextString('\"'));
- assertEquals("ABC", new JSONTokener("ABC'DEF").nextString('\''));
- assertEquals("ABC", new JSONTokener("ABC'''DEF").nextString('\''));
-
- // nextString permits slash-escaping of arbitrary characters!
- assertEquals("ABC", new JSONTokener("A\\B\\C'DEF").nextString('\''));
-
- JSONTokener tokener = new JSONTokener(" 'abc' 'def' \"ghi\"");
- tokener.next();
- assertEquals('\'', tokener.next());
- assertEquals("abc", tokener.nextString('\''));
- tokener.next();
- assertEquals('\'', tokener.next());
- assertEquals("def", tokener.nextString('\''));
- tokener.next();
- assertEquals('"', tokener.next());
- assertEquals("ghi", tokener.nextString('\"'));
- assertFalse(tokener.more());
- }
-
- public void testNextStringNoDelimiter() throws JSONException {
- try {
- new JSONTokener("").nextString('\'');
- fail();
- } catch (JSONException e) {
- }
-
- JSONTokener tokener = new JSONTokener(" 'abc");
- tokener.next();
- tokener.next();
- try {
- tokener.next('\'');
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testNextStringEscapedQuote() throws JSONException {
- try {
- new JSONTokener("abc\\").nextString('"');
- fail();
- } catch (JSONException e) {
- }
-
- // we're mixing Java escaping like \" and JavaScript escaping like \\\"
- // which makes these tests extra tricky to read!
- assertEquals("abc\"def", new JSONTokener("abc\\\"def\"ghi").nextString('"'));
- assertEquals("abc\\def", new JSONTokener("abc\\\\def\"ghi").nextString('"'));
- assertEquals("abc/def", new JSONTokener("abc\\/def\"ghi").nextString('"'));
- assertEquals("abc\bdef", new JSONTokener("abc\\bdef\"ghi").nextString('"'));
- assertEquals("abc\fdef", new JSONTokener("abc\\fdef\"ghi").nextString('"'));
- assertEquals("abc\ndef", new JSONTokener("abc\\ndef\"ghi").nextString('"'));
- assertEquals("abc\rdef", new JSONTokener("abc\\rdef\"ghi").nextString('"'));
- assertEquals("abc\tdef", new JSONTokener("abc\\tdef\"ghi").nextString('"'));
- }
-
- public void testNextStringUnicodeEscaped() throws JSONException {
- // we're mixing Java escaping like \\ and JavaScript escaping like \\u
- assertEquals("abc def", new JSONTokener("abc\\u0020def\"ghi").nextString('"'));
- assertEquals("abcU0020def", new JSONTokener("abc\\U0020def\"ghi").nextString('"'));
-
- // JSON requires 4 hex characters after a unicode escape
- try {
- new JSONTokener("abc\\u002\"").nextString('"');
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONTokener("abc\\u").nextString('"');
- fail();
- } catch (JSONException e) {
- }
- try {
- new JSONTokener("abc\\u \"").nextString('"');
- fail();
- } catch (JSONException e) {
- }
- assertEquals("abc\"def", new JSONTokener("abc\\u0022def\"ghi").nextString('"'));
- try {
- new JSONTokener("abc\\u000G\"").nextString('"');
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testNextStringNonQuote() throws JSONException {
- assertEquals("AB", new JSONTokener("ABC").nextString('C'));
- assertEquals("ABCD", new JSONTokener("AB\\CDC").nextString('C'));
- assertEquals("AB\nC", new JSONTokener("AB\\nCn").nextString('n'));
- }
-
- public void testNextTo() throws JSONException {
- assertEquals("ABC", new JSONTokener("ABCDEFG").nextTo("DHI"));
- assertEquals("ABCDEF", new JSONTokener("ABCDEF").nextTo(""));
-
- JSONTokener tokener = new JSONTokener("ABC\rDEF\nGHI\r\nJKL");
- assertEquals("ABC", tokener.nextTo("M"));
- assertEquals('\r', tokener.next());
- assertEquals("DEF", tokener.nextTo("M"));
- assertEquals('\n', tokener.next());
- assertEquals("GHI", tokener.nextTo("M"));
- assertEquals('\r', tokener.next());
- assertEquals('\n', tokener.next());
- assertEquals("JKL", tokener.nextTo("M"));
-
- tokener = new JSONTokener("ABCDEFGHI");
- assertEquals("ABC", tokener.nextTo("DEF"));
- assertEquals("", tokener.nextTo("DEF"));
- assertEquals('D', tokener.next());
- assertEquals("", tokener.nextTo("DEF"));
- assertEquals('E', tokener.next());
- assertEquals("", tokener.nextTo("DEF"));
- assertEquals('F', tokener.next());
- assertEquals("GHI", tokener.nextTo("DEF"));
- assertEquals("", tokener.nextTo("DEF"));
-
- tokener = new JSONTokener(" \t \fABC \t DEF");
- assertEquals("ABC", tokener.nextTo("DEF"));
- assertEquals('D', tokener.next());
-
- tokener = new JSONTokener(" \t \fABC \n DEF");
- assertEquals("ABC", tokener.nextTo("\n"));
- assertEquals("", tokener.nextTo("\n"));
-
- tokener = new JSONTokener("");
- try {
- tokener.nextTo(null);
- fail();
- } catch (NullPointerException e) {
- }
- }
-
- public void testNextToTrimming() {
- assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo("DE"));
- assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo('D'));
- }
-
- public void testNextToTrailing() {
- assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo("G"));
- assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo('G'));
- }
-
- public void testNextToDoesntStopOnNull() {
- String message = "nextTo() shouldn't stop after \\0 characters";
- JSONTokener tokener = new JSONTokener(" \0\t \fABC \n DEF");
- assertEquals(message, "ABC", tokener.nextTo("D"));
- assertEquals(message, '\n', tokener.next());
- assertEquals(message, "", tokener.nextTo("D"));
- }
-
- public void testNextToConsumesNull() {
- String message = "nextTo shouldn't consume \\0.";
- JSONTokener tokener = new JSONTokener("ABC\0DEF");
- assertEquals(message, "ABC", tokener.nextTo("\0"));
- assertEquals(message, '\0', tokener.next());
- assertEquals(message, "DEF", tokener.nextTo("\0"));
- }
-
- public void testSkipPast() {
- JSONTokener tokener = new JSONTokener("ABCDEF");
- tokener.skipPast("ABC");
- assertEquals('D', tokener.next());
- tokener.skipPast("EF");
- assertEquals('\0', tokener.next());
-
- tokener = new JSONTokener("ABCDEF");
- tokener.skipPast("ABCDEF");
- assertEquals('\0', tokener.next());
-
- tokener = new JSONTokener("ABCDEF");
- tokener.skipPast("G");
- assertEquals('\0', tokener.next());
-
- tokener = new JSONTokener("ABC\0ABC");
- tokener.skipPast("ABC");
- assertEquals('\0', tokener.next());
- assertEquals('A', tokener.next());
-
- tokener = new JSONTokener("\0ABC");
- tokener.skipPast("ABC");
- assertEquals('\0', tokener.next());
-
- tokener = new JSONTokener("ABC\nDEF");
- tokener.skipPast("DEF");
- assertEquals('\0', tokener.next());
-
- tokener = new JSONTokener("ABC");
- tokener.skipPast("ABCDEF");
- assertEquals('\0', tokener.next());
-
- tokener = new JSONTokener("ABCDABCDABCD");
- tokener.skipPast("ABC");
- assertEquals('D', tokener.next());
- tokener.skipPast("ABC");
- assertEquals('D', tokener.next());
- tokener.skipPast("ABC");
- assertEquals('D', tokener.next());
-
- tokener = new JSONTokener("");
- try {
- tokener.skipPast(null);
- fail();
- } catch (NullPointerException e) {
- }
- }
-
- public void testSkipTo() {
- JSONTokener tokener = new JSONTokener("ABCDEF");
- tokener.skipTo('A');
- assertEquals('A', tokener.next());
- tokener.skipTo('D');
- assertEquals('D', tokener.next());
- tokener.skipTo('G');
- assertEquals('E', tokener.next());
- tokener.skipTo('A');
- assertEquals('F', tokener.next());
-
- tokener = new JSONTokener("ABC\nDEF");
- tokener.skipTo('F');
- assertEquals('F', tokener.next());
-
- tokener = new JSONTokener("ABCfDEF");
- tokener.skipTo('F');
- assertEquals('F', tokener.next());
-
- tokener = new JSONTokener("ABC/* DEF */");
- tokener.skipTo('D');
- assertEquals('D', tokener.next());
- }
-
- public void testSkipToStopsOnNull() {
- JSONTokener tokener = new JSONTokener("ABC\0DEF");
- tokener.skipTo('F');
- assertEquals("skipTo shouldn't stop when it sees '\\0'", 'F', tokener.next());
- }
-
- public void testBomIgnoredAsFirstCharacterOfDocument() throws JSONException {
- JSONTokener tokener = new JSONTokener("\ufeff[]");
- JSONArray array = (JSONArray) tokener.nextValue();
- assertEquals(0, array.length());
- }
-
- public void testBomTreatedAsCharacterInRestOfDocument() throws JSONException {
- JSONTokener tokener = new JSONTokener("[\ufeff]");
- JSONArray array = (JSONArray) tokener.nextValue();
- assertEquals(1, array.length());
- }
-
- public void testDehexchar() {
- assertEquals( 0, JSONTokener.dehexchar('0'));
- assertEquals( 1, JSONTokener.dehexchar('1'));
- assertEquals( 2, JSONTokener.dehexchar('2'));
- assertEquals( 3, JSONTokener.dehexchar('3'));
- assertEquals( 4, JSONTokener.dehexchar('4'));
- assertEquals( 5, JSONTokener.dehexchar('5'));
- assertEquals( 6, JSONTokener.dehexchar('6'));
- assertEquals( 7, JSONTokener.dehexchar('7'));
- assertEquals( 8, JSONTokener.dehexchar('8'));
- assertEquals( 9, JSONTokener.dehexchar('9'));
- assertEquals(10, JSONTokener.dehexchar('A'));
- assertEquals(11, JSONTokener.dehexchar('B'));
- assertEquals(12, JSONTokener.dehexchar('C'));
- assertEquals(13, JSONTokener.dehexchar('D'));
- assertEquals(14, JSONTokener.dehexchar('E'));
- assertEquals(15, JSONTokener.dehexchar('F'));
- assertEquals(10, JSONTokener.dehexchar('a'));
- assertEquals(11, JSONTokener.dehexchar('b'));
- assertEquals(12, JSONTokener.dehexchar('c'));
- assertEquals(13, JSONTokener.dehexchar('d'));
- assertEquals(14, JSONTokener.dehexchar('e'));
- assertEquals(15, JSONTokener.dehexchar('f'));
-
- for (int c = 0; c <= 0xFFFF; c++) {
- if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
- continue;
- }
- assertEquals("dehexchar " + c, -1, JSONTokener.dehexchar((char) c));
- }
- }
-}
diff --git a/org/json/ParsingTest.java b/org/json/ParsingTest.java
deleted file mode 100644
index 641f5b98..00000000
--- a/org/json/ParsingTest.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.json;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import junit.framework.TestCase;
-
-public class ParsingTest extends TestCase {
-
- public void testParsingNoObjects() {
- try {
- new JSONTokener("").nextValue();
- fail();
- } catch (JSONException e) {
- }
- }
-
- public void testParsingLiterals() throws JSONException {
- assertParsed(Boolean.TRUE, "true");
- assertParsed(Boolean.FALSE, "false");
- assertParsed(JSONObject.NULL, "null");
- assertParsed(JSONObject.NULL, "NULL");
- assertParsed(Boolean.FALSE, "False");
- assertParsed(Boolean.TRUE, "truE");
- }
-
- public void testParsingQuotedStrings() throws JSONException {
- assertParsed("abc", "\"abc\"");
- assertParsed("123", "\"123\"");
- assertParsed("foo\nbar", "\"foo\\nbar\"");
- assertParsed("foo bar", "\"foo\\u0020bar\"");
- assertParsed("\"{}[]/\\:,=;#", "\"\\\"{}[]/\\\\:,=;#\"");
- }
-
- public void testParsingSingleQuotedStrings() throws JSONException {
- assertParsed("abc", "'abc'");
- assertParsed("123", "'123'");
- assertParsed("foo\nbar", "'foo\\nbar'");
- assertParsed("foo bar", "'foo\\u0020bar'");
- assertParsed("\"{}[]/\\:,=;#", "'\\\"{}[]/\\\\:,=;#'");
- }
-
- public void testParsingUnquotedStrings() throws JSONException {
- assertParsed("abc", "abc");
- assertParsed("123abc", "123abc");
- assertParsed("123e0x", "123e0x");
- assertParsed("123e", "123e");
- assertParsed("123ee21", "123ee21");
- assertParsed("0xFFFFFFFFFFFFFFFFF", "0xFFFFFFFFFFFFFFFFF");
- }
-
- /**
- * Unfortunately the original implementation attempts to figure out what
- * Java number type best suits an input value.
- */
- public void testParsingNumbersThatAreBestRepresentedAsLongs() throws JSONException {
- assertParsed(9223372036854775807L, "9223372036854775807");
- assertParsed(9223372036854775806L, "9223372036854775806");
- assertParsed(-9223372036854775808L, "-9223372036854775808");
- assertParsed(-9223372036854775807L, "-9223372036854775807");
- }
-
- public void testParsingNumbersThatAreBestRepresentedAsIntegers() throws JSONException {
- assertParsed(0, "0");
- assertParsed(5, "5");
- assertParsed(-2147483648, "-2147483648");
- assertParsed(2147483647, "2147483647");
- }
-
- public void testParsingNegativeZero() throws JSONException {
- assertParsed(0, "-0");
- }
-
- public void testParsingIntegersWithAdditionalPrecisionYieldDoubles() throws JSONException {
- assertParsed(1d, "1.00");
- assertParsed(1d, "1.0");
- assertParsed(0d, "0.0");
- assertParsed(-0d, "-0.0");
- }
-
- public void testParsingNumbersThatAreBestRepresentedAsDoubles() throws JSONException {
- assertParsed(9.223372036854776E18, "9223372036854775808");
- assertParsed(-9.223372036854776E18, "-9223372036854775809");
- assertParsed(1.7976931348623157E308, "1.7976931348623157e308");
- assertParsed(2.2250738585072014E-308, "2.2250738585072014E-308");
- assertParsed(4.9E-324, "4.9E-324");
- assertParsed(4.9E-324, "4.9e-324");
- }
-
- public void testParsingOctalNumbers() throws JSONException {
- assertParsed(5, "05");
- assertParsed(8, "010");
- assertParsed(1046, "02026");
- }
-
- public void testParsingHexNumbers() throws JSONException {
- assertParsed(5, "0x5");
- assertParsed(16, "0x10");
- assertParsed(8230, "0x2026");
- assertParsed(180150010, "0xABCDEFA");
- assertParsed(2077093803, "0x7BCDEFAB");
- }
-
- public void testParsingLargeHexValues() throws JSONException {
- assertParsed(Integer.MAX_VALUE, "0x7FFFFFFF");
- String message = "Hex values are parsed as Strings if their signed " +
- "value is greater than Integer.MAX_VALUE.";
- assertParsed(message, 0x80000000L, "0x80000000");
- }
-
- public void test64BitHexValues() throws JSONException {
- assertParsed("Large hex longs shouldn't be yield ints or strings",
- -1L, "0xFFFFFFFFFFFFFFFF");
- }
-
- public void testParsingWithCommentsAndWhitespace() throws JSONException {
- assertParsed("baz", " // foo bar \n baz");
- assertParsed("baz", " // foo bar \r baz");
- assertParsed("baz", " // foo bar \r\n baz");
- assertParsed("baz", " # foo bar \n baz");
- assertParsed("baz", " # foo bar \r baz");
- assertParsed("baz", " # foo bar \r\n baz");
- assertParsed(5, " /* foo bar \n baz */ 5");
- assertParsed(5, " /* foo bar \n baz */ 5 // quux");
- assertParsed(5, " 5 ");
- assertParsed(5, " 5 \r\n\t ");
- assertParsed(5, "\r\n\t 5 ");
- }
-
- public void testParsingArrays() throws JSONException {
- assertParsed(array(), "[]");
- assertParsed(array(5, 6, true), "[5,6,true]");
- assertParsed(array(5, 6, array()), "[5,6,[]]");
- assertParsed(array(5, 6, 7), "[5;6;7]");
- assertParsed(array(5, 6, 7), "[5 , 6 \t; \r\n 7\n]");
- assertParsed(array(5, 6, 7, null), "[5,6,7,]");
- assertParsed(array(null, null), "[,]");
- assertParsed(array(5, null, null, null, 5), "[5,,,,5]");
- assertParsed(array(null, 5), "[,5]");
- assertParsed(array(null, null, null), "[,,]");
- assertParsed(array(null, null, null, 5), "[,,,5]");
- }
-
- public void testParsingObjects() throws JSONException {
- assertParsed(object("foo", 5), "{\"foo\": 5}");
- assertParsed(object("foo", 5), "{foo: 5}");
- assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\": 5, \"bar\": \"baz\"}");
- assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\": 5; \"bar\": \"baz\"}");
- assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\"= 5; \"bar\"= \"baz\"}");
- assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\"=> 5; \"bar\"=> \"baz\"}");
- assertParsed(object("foo", object(), "bar", array()), "{\"foo\"=> {}; \"bar\"=> []}");
- assertParsed(object("foo", object("foo", array(5, 6))), "{\"foo\": {\"foo\": [5, 6]}}");
- assertParsed(object("foo", object("foo", array(5, 6))), "{\"foo\":\n\t{\t \"foo\":[5,\r6]}}");
- }
-
- public void testSyntaxProblemUnterminatedObject() {
- assertParseFail("{");
- assertParseFail("{\"foo\"");
- assertParseFail("{\"foo\":");
- assertParseFail("{\"foo\":bar");
- assertParseFail("{\"foo\":bar,");
- assertParseFail("{\"foo\":bar,\"baz\"");
- assertParseFail("{\"foo\":bar,\"baz\":");
- assertParseFail("{\"foo\":bar,\"baz\":true");
- assertParseFail("{\"foo\":bar,\"baz\":true,");
- }
-
- public void testSyntaxProblemEmptyString() {
- assertParseFail("");
- }
-
- public void testSyntaxProblemUnterminatedArray() {
- assertParseFail("[");
- assertParseFail("[,");
- assertParseFail("[,,");
- assertParseFail("[true");
- assertParseFail("[true,");
- assertParseFail("[true,,");
- }
-
- public void testSyntaxProblemMalformedObject() {
- assertParseFail("{:}");
- assertParseFail("{\"key\":}");
- assertParseFail("{:true}");
- assertParseFail("{\"key\":true:}");
- assertParseFail("{null:true}");
- assertParseFail("{true:true}");
- assertParseFail("{0xFF:true}");
- }
-
- private void assertParseFail(String malformedJson) {
- try {
- new JSONTokener(malformedJson).nextValue();
- fail("Successfully parsed: \"" + malformedJson + "\"");
- } catch (JSONException e) {
- } catch (StackOverflowError e) {
- fail("Stack overflowed on input: \"" + malformedJson + "\"");
- }
- }
-
- private JSONArray array(Object... elements) {
- return new JSONArray(Arrays.asList(elements));
- }
-
- private JSONObject object(Object... keyValuePairs) throws JSONException {
- JSONObject result = new JSONObject();
- for (int i = 0; i < keyValuePairs.length; i+=2) {
- result.put((String) keyValuePairs[i], keyValuePairs[i+1]);
- }
- return result;
- }
-
- private void assertParsed(String message, Object expected, String json) throws JSONException {
- Object actual = new JSONTokener(json).nextValue();
- actual = canonicalize(actual);
- expected = canonicalize(expected);
- assertEquals("For input \"" + json + "\" " + message, expected, actual);
- }
-
- private void assertParsed(Object expected, String json) throws JSONException {
- assertParsed("", expected, json);
- }
-
- /**
- * Since they don't implement equals or hashCode properly, this recursively
- * replaces JSONObjects with an equivalent HashMap, and JSONArrays with the
- * equivalent ArrayList.
- */
- private Object canonicalize(Object input) throws JSONException {
- if (input instanceof JSONArray) {
- JSONArray array = (JSONArray) input;
- List<Object> result = new ArrayList<Object>();
- for (int i = 0; i < array.length(); i++) {
- result.add(canonicalize(array.opt(i)));
- }
- return result;
- } else if (input instanceof JSONObject) {
- JSONObject object = (JSONObject) input;
- Map<String, Object> result = new HashMap<String, Object>();
- for (Iterator<?> i = object.keys(); i.hasNext(); ) {
- String key = (String) i.next();
- result.put(key, canonicalize(object.get(key)));
- }
- return result;
- } else if (input == null || input.equals(JSONObject.NULL)) {
- return JSONObject.NULL;
- } else {
- return input;
- }
- }
-}
diff --git a/org/json/SelfUseTest.java b/org/json/SelfUseTest.java
deleted file mode 100644
index 15a08241..00000000
--- a/org/json/SelfUseTest.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.json;
-
-import junit.framework.TestCase;
-
-/**
- * These tests checks self use calls. For the most part we doesn't attempt to
- * cover self-use, except in those cases where our clean room implementation
- * does it.
- *
- * <p>This black box test was written without inspecting the non-free org.json
- * sourcecode.
- */
-public class SelfUseTest extends TestCase {
-
- private int objectPutCalls = 0;
- private int objectGetCalls = 0;
- private int objectOptCalls = 0;
- private int objectOptTypeCalls = 0;
- private int arrayPutCalls = 0;
- private int arrayGetCalls = 0;
- private int arrayOptCalls = 0;
- private int arrayOptTypeCalls = 0;
- private int tokenerNextCalls = 0;
- private int tokenerNextValueCalls = 0;
-
- private final JSONObject object = new JSONObject() {
- @Override public JSONObject put(String name, Object value) throws JSONException {
- objectPutCalls++;
- return super.put(name, value);
- }
- @Override public Object get(String name) throws JSONException {
- objectGetCalls++;
- return super.get(name);
- }
- @Override public Object opt(String name) {
- objectOptCalls++;
- return super.opt(name);
- }
- @Override public boolean optBoolean(String key, boolean defaultValue) {
- objectOptTypeCalls++;
- return super.optBoolean(key, defaultValue);
- }
- @Override public double optDouble(String key, double defaultValue) {
- objectOptTypeCalls++;
- return super.optDouble(key, defaultValue);
- }
- @Override public int optInt(String key, int defaultValue) {
- objectOptTypeCalls++;
- return super.optInt(key, defaultValue);
- }
- @Override public long optLong(String key, long defaultValue) {
- objectOptTypeCalls++;
- return super.optLong(key, defaultValue);
- }
- @Override public String optString(String key, String defaultValue) {
- objectOptTypeCalls++;
- return super.optString(key, defaultValue);
- }
- };
-
- private final JSONArray array = new JSONArray() {
- @Override public JSONArray put(int index, Object value) throws JSONException {
- arrayPutCalls++;
- return super.put(index, value);
- }
- @Override public Object get(int index) throws JSONException {
- arrayGetCalls++;
- return super.get(index);
- }
- @Override public Object opt(int index) {
- arrayOptCalls++;
- return super.opt(index);
- }
- @Override public boolean optBoolean(int index, boolean fallback) {
- arrayOptTypeCalls++;
- return super.optBoolean(index, fallback);
- }
- @Override public double optDouble(int index, double fallback) {
- arrayOptTypeCalls++;
- return super.optDouble(index, fallback);
- }
- @Override public long optLong(int index, long fallback) {
- arrayOptTypeCalls++;
- return super.optLong(index, fallback);
- }
- @Override public String optString(int index, String fallback) {
- arrayOptTypeCalls++;
- return super.optString(index, fallback);
- }
- @Override public int optInt(int index, int fallback) {
- arrayOptTypeCalls++;
- return super.optInt(index, fallback);
- }
- };
-
- private final JSONTokener tokener = new JSONTokener("{\"foo\": [true]}") {
- @Override public char next() {
- tokenerNextCalls++;
- return super.next();
- }
- @Override public Object nextValue() throws JSONException {
- tokenerNextValueCalls++;
- return super.nextValue();
- }
- };
-
-
- public void testObjectPut() throws JSONException {
- object.putOpt("foo", "bar");
- assertEquals(1, objectPutCalls);
- }
-
- public void testObjectAccumulate() throws JSONException {
- object.accumulate("foo", "bar");
- assertEquals(1, objectPutCalls);
- }
-
- public void testObjectGetBoolean() throws JSONException {
- object.put("foo", "true");
- object.getBoolean("foo");
- assertEquals(1, objectGetCalls);
- }
-
- public void testObjectOptType() throws JSONException {
- object.optBoolean("foo");
- assertEquals(1, objectOptCalls);
- assertEquals(1, objectOptTypeCalls);
- object.optDouble("foo");
- assertEquals(2, objectOptCalls);
- assertEquals(2, objectOptTypeCalls);
- object.optInt("foo");
- assertEquals(3, objectOptCalls);
- assertEquals(3, objectOptTypeCalls);
- object.optLong("foo");
- assertEquals(4, objectOptCalls);
- assertEquals(4, objectOptTypeCalls);
- object.optString("foo");
- assertEquals(5, objectOptCalls);
- assertEquals(5, objectOptTypeCalls);
- }
-
- public void testToJSONArray() throws JSONException {
- object.put("foo", 5);
- object.put("bar", 10);
- array.put("foo");
- array.put("baz");
- array.put("bar");
- object.toJSONArray(array);
- assertEquals(3, arrayOptCalls);
- assertEquals(0, arrayOptTypeCalls);
- assertEquals(3, objectOptCalls);
- assertEquals(0, objectOptTypeCalls);
- }
-
- public void testPutAtIndex() throws JSONException {
- array.put(10, false);
- assertEquals(1, arrayPutCalls);
- }
-
- public void testIsNull() {
- array.isNull(5);
- assertEquals(1, arrayOptCalls);
- }
-
- public void testArrayGetType() throws JSONException {
- array.put(true);
- array.getBoolean(0);
- assertEquals(1, arrayGetCalls);
- }
-
- public void testArrayOptType() throws JSONException {
- array.optBoolean(3);
- assertEquals(1, arrayOptCalls);
- assertEquals(1, arrayOptTypeCalls);
- array.optDouble(3);
- assertEquals(2, arrayOptCalls);
- assertEquals(2, arrayOptTypeCalls);
- array.optInt(3);
- assertEquals(3, arrayOptCalls);
- assertEquals(3, arrayOptTypeCalls);
- array.optLong(3);
- assertEquals(4, arrayOptCalls);
- assertEquals(4, arrayOptTypeCalls);
- array.optString(3);
- assertEquals(5, arrayOptCalls);
- assertEquals(5, arrayOptTypeCalls);
- }
-
- public void testToJSONObject() throws JSONException {
- array.put("foo");
- array.put("baz");
- array.put("bar");
- JSONArray values = new JSONArray();
- values.put(5.5d);
- values.put(11d);
- values.put(30);
- values.toJSONObject(array);
- assertEquals(3, arrayOptCalls);
- assertEquals(0, arrayOptTypeCalls);
- }
-
- public void testNextExpecting() throws JSONException {
- tokener.next('{');
- assertEquals(1, tokenerNextCalls);
- tokener.next('\"');
- assertEquals(2, tokenerNextCalls);
- }
-
- public void testNextValue() throws JSONException {
- tokener.nextValue();
- assertEquals(4, tokenerNextValueCalls);
- }
-}
diff --git a/tck/java/time/zone/TCKZoneRules.java b/tck/java/time/zone/TCKZoneRules.java
index 67a10c04..2781ca54 100644
--- a/tck/java/time/zone/TCKZoneRules.java
+++ b/tck/java/time/zone/TCKZoneRules.java
@@ -943,21 +943,23 @@ public class TCKZoneRules {
}
public void test_Apia_jumpForwardOverInternationalDateLine_P12_to_M12() {
- // transition occurred at 1879-07-04T00:00+12:33:04
+ // Android-changed: 1879 changed to 1892 in this test due to 2017c IANA update. Upstream
+ // will probably do the same. See https://bugs.openjdk.java.net/browse/JDK-8190259
+ // transition occurred at 1892-07-04T00:00+12:33:04
ZoneRules test = pacificApia();
- Instant instantBefore = LocalDate.of(1879, 7, 2).atStartOfDay(ZoneOffset.UTC).toInstant();
+ Instant instantBefore = LocalDate.of(1892, 7, 2).atStartOfDay(ZoneOffset.UTC).toInstant();
ZoneOffsetTransition trans = test.nextTransition(instantBefore);
- assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(1879, 7, 5, 0, 0));
- assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(1879, 7, 4, 0, 0));
+ assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(1892, 7, 5, 0, 0));
+ assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(1892, 7, 4, 0, 0));
assertEquals(trans.isGap(), false);
assertEquals(trans.isOverlap(), true);
assertEquals(trans.isValidOffset(ZoneOffset.ofHoursMinutesSeconds(+12, 33, 4)), true);
assertEquals(trans.isValidOffset(ZoneOffset.ofHoursMinutesSeconds(-11, -26, -56)), true);
assertEquals(trans.getDuration(), Duration.ofHours(-24));
- assertEquals(trans.getInstant(), LocalDateTime.of(1879, 7, 4, 0, 0).toInstant(ZoneOffset.ofHoursMinutesSeconds(-11, -26, -56)));
+ assertEquals(trans.getInstant(), LocalDateTime.of(1892, 7, 4, 0, 0).toInstant(ZoneOffset.ofHoursMinutesSeconds(-11, -26, -56)));
- ZonedDateTime zdt = ZonedDateTime.of(1879, 7, 4, 23, 0, 0, 0, ZoneId.of("Pacific/Apia"));
- assertEquals(zdt.plusHours(2).toLocalDateTime(), LocalDateTime.of(1879, 7, 4, 1, 0, 0));
+ ZonedDateTime zdt = ZonedDateTime.of(1892, 7, 4, 23, 0, 0, 0, ZoneId.of("Pacific/Apia"));
+ assertEquals(zdt.plusHours(2).toLocalDateTime(), LocalDateTime.of(1892, 7, 4, 1, 0, 0));
}
//-------------------------------------------------------------------------