summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-10-24 19:50:40 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-10-24 19:50:40 -0400
commit47ed54e5d312f899507d28d6e95ccc18a0de19fe (patch)
tree7a2d435c55c36fbc1d07e895bd0c68b18f84e12c /android
parent07f9f65561c2b81bcd189b895b31bb2ad0438d74 (diff)
downloadandroid-28-47ed54e5d312f899507d28d6e95ccc18a0de19fe.tar.gz
Import Android SDK Platform P [4413397]
/google/data/ro/projects/android/fetch_artifact \ --bid 4413397 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4413397.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I3cf1f7c36e61c090dcc7de7bcfa812ef2bf96c00
Diffstat (limited to 'android')
-rw-r--r--android/accessibilityservice/GestureDescription.java12
-rw-r--r--android/app/Activity.java8
-rw-r--r--android/app/ActivityManager.java294
-rw-r--r--android/app/ActivityOptions.java43
-rw-r--r--android/app/KeyguardManager.java2
-rw-r--r--android/app/NotificationChannel.java2
-rw-r--r--android/app/NotificationManager.java9
-rw-r--r--android/app/StatusBarManager.java5
-rw-r--r--android/app/TaskStackListener.java7
-rw-r--r--android/app/WallpaperManager.java31
-rw-r--r--android/app/WindowConfiguration.java3
-rw-r--r--android/app/admin/DevicePolicyManager.java46
-rw-r--r--android/app/assist/AssistStructure.java57
-rw-r--r--android/app/job/JobScheduler.java41
-rw-r--r--android/app/job/JobService.java102
-rw-r--r--android/app/slice/Slice.java (renamed from android/slice/Slice.java)154
-rw-r--r--android/app/slice/SliceItem.java (renamed from android/slice/SliceItem.java)26
-rw-r--r--android/app/slice/SliceProvider.java (renamed from android/slice/SliceProvider.java)38
-rw-r--r--android/app/slice/SliceQuery.java (renamed from android/slice/SliceQuery.java)15
-rw-r--r--android/app/slice/views/ActionRow.java (renamed from android/slice/views/ActionRow.java)8
-rw-r--r--android/app/slice/views/GridView.java (renamed from android/slice/views/GridView.java)18
-rw-r--r--android/app/slice/views/LargeSliceAdapter.java (renamed from android/slice/views/LargeSliceAdapter.java)10
-rw-r--r--android/app/slice/views/LargeTemplateView.java (renamed from android/slice/views/LargeTemplateView.java)15
-rw-r--r--android/app/slice/views/MessageView.java (renamed from android/slice/views/MessageView.java)10
-rw-r--r--android/app/slice/views/RemoteInputView.java (renamed from android/slice/views/RemoteInputView.java)2
-rw-r--r--android/app/slice/views/ShortcutView.java (renamed from android/slice/views/ShortcutView.java)10
-rw-r--r--android/app/slice/views/SliceView.java (renamed from android/slice/views/SliceView.java)18
-rw-r--r--android/app/slice/views/SliceViewUtil.java (renamed from android/slice/views/SliceViewUtil.java)2
-rw-r--r--android/app/slice/views/SmallTemplateView.java (renamed from android/slice/views/SmallTemplateView.java)24
-rw-r--r--android/app/usage/UsageStatsManager.java19
-rw-r--r--android/appwidget/AppWidgetManager.java40
-rw-r--r--android/arch/lifecycle/ActivityFullLifecycleTest.java58
-rw-r--r--android/arch/lifecycle/AndroidViewModel.java7
-rw-r--r--android/arch/lifecycle/ClassesInfoCache.java16
-rw-r--r--android/arch/lifecycle/Lifecycle.java7
-rw-r--r--android/arch/lifecycle/LifecycleOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistry.java63
-rw-r--r--android/arch/lifecycle/LifecycleRegistryOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistryTest.java19
-rw-r--r--android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java177
-rw-r--r--android/arch/lifecycle/LiveDataTest.java224
-rw-r--r--android/arch/lifecycle/MediatorLiveData.java39
-rw-r--r--android/arch/lifecycle/MissingClassTest.java44
-rw-r--r--android/arch/lifecycle/PartiallyCoveredActivityTest.java200
-rw-r--r--android/arch/lifecycle/ProcessLifecycleOwner.java4
-rw-r--r--android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java (renamed from android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java)2
-rw-r--r--android/arch/lifecycle/ProcessOwnerTest.java24
-rw-r--r--android/arch/lifecycle/ReportFragment.java1
-rw-r--r--android/arch/lifecycle/TestUtils.java108
-rw-r--r--android/arch/lifecycle/Transformations.java8
-rw-r--r--android/arch/lifecycle/ViewModelProvider.java11
-rw-r--r--android/arch/lifecycle/ViewModelProviders.java3
-rw-r--r--android/arch/lifecycle/ViewModelStoreOwner.java3
-rw-r--r--android/arch/lifecycle/ViewModelStores.java5
-rw-r--r--android/arch/lifecycle/testapp/CollectingLifecycleOwner.java (renamed from android/arch/lifecycle/testapp/CollectingActivity.java)11
-rw-r--r--android/arch/lifecycle/testapp/CollectingSupportActivity.java113
-rw-r--r--android/arch/lifecycle/testapp/CollectingSupportFragment.java104
-rw-r--r--android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java32
-rw-r--r--android/arch/lifecycle/testapp/FullLifecycleTestActivity.java88
-rw-r--r--android/arch/lifecycle/testapp/MainActivity.java31
-rw-r--r--android/arch/lifecycle/testapp/NavigationDialogActivity.java6
-rw-r--r--android/arch/lifecycle/testapp/NonSupportActivity.java85
-rw-r--r--android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java95
-rw-r--r--android/arch/lifecycle/testapp/TestEvent.java4
-rw-r--r--android/arch/lifecycle/testapp/TestObserver.java2
-rw-r--r--android/arch/paging/BoundedDataSource.java2
-rw-r--r--android/arch/paging/ContiguousDataSource.java110
-rw-r--r--android/arch/paging/ContiguousDiffHelperTest.java128
-rw-r--r--android/arch/paging/ContiguousPagedList.java408
-rw-r--r--android/arch/paging/ContiguousPagedListTest.java333
-rw-r--r--android/arch/paging/DataSource.java14
-rw-r--r--android/arch/paging/KeyedDataSource.java15
-rw-r--r--android/arch/paging/ListDataSource.java38
-rw-r--r--android/arch/paging/LivePagedListProvider.java132
-rw-r--r--android/arch/paging/NullPaddedList.java75
-rw-r--r--android/arch/paging/NullPaddedListTest.java68
-rw-r--r--android/arch/paging/Page.java51
-rw-r--r--android/arch/paging/PageArrayList.java130
-rw-r--r--android/arch/paging/PageArrayListTest.java49
-rw-r--r--android/arch/paging/PageResult.java56
-rw-r--r--android/arch/paging/PagedList.java200
-rw-r--r--android/arch/paging/PagedListAdapterHelper.java109
-rw-r--r--android/arch/paging/PagedListAdapterHelperTest.java308
-rw-r--r--android/arch/paging/PagedStorage.java433
-rw-r--r--android/arch/paging/PagedStorageDiffHelper.java (renamed from android/arch/paging/ContiguousDiffHelper.java)65
-rw-r--r--android/arch/paging/PositionalDataSource.java25
-rw-r--r--android/arch/paging/SnapshotPagedList.java64
-rw-r--r--android/arch/paging/SparseDiffHelper.java99
-rw-r--r--android/arch/paging/StringPagedList.java25
-rw-r--r--android/arch/paging/TestExecutor.java41
-rw-r--r--android/arch/paging/TiledDataSource.java58
-rw-r--r--android/arch/paging/TiledPagedList.java276
-rw-r--r--android/arch/paging/TiledPagedListTest.java288
-rw-r--r--android/arch/persistence/room/InvalidationTracker.java4
-rw-r--r--android/arch/persistence/room/Relation.java18
-rw-r--r--android/arch/persistence/room/Room.java2
-rw-r--r--android/arch/persistence/room/RoomDatabase.java15
-rw-r--r--android/arch/persistence/room/RoomWarnings.java8
-rw-r--r--android/arch/persistence/room/Transaction.java37
-rw-r--r--android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java1
-rw-r--r--android/arch/persistence/room/integration/testapp/database/CustomerDao.java2
-rw-r--r--android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java47
-rw-r--r--android/arch/persistence/room/integration/testapp/test/InvalidationTest.java120
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java8
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java471
-rw-r--r--android/arch/persistence/room/migration/Migration.java3
-rw-r--r--android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java5
-rw-r--r--android/arch/persistence/room/paging/LimitOffsetDataSource.java36
-rw-r--r--android/arch/persistence/room/util/StringUtil.java7
-rw-r--r--android/bluetooth/BluetoothAdapter.java48
-rw-r--r--android/bluetooth/BluetoothDevice.java8
-rw-r--r--android/bluetooth/BluetoothHeadset.java8
-rw-r--r--android/bluetooth/BluetoothHidDevice.java (renamed from android/bluetooth/BluetoothInputHost.java)44
-rw-r--r--android/bluetooth/BluetoothHidHost.java (renamed from android/bluetooth/BluetoothInputDevice.java)70
-rw-r--r--android/bluetooth/BluetoothProfile.java8
-rw-r--r--android/content/ContentProvider.java3
-rw-r--r--android/content/ContentResolver.java36
-rw-r--r--android/content/Context.java1
-rw-r--r--android/content/Intent.java52
-rw-r--r--android/content/IntentFilter.java63
-rw-r--r--android/content/pm/FeatureInfo.java13
-rw-r--r--android/content/pm/LauncherApps.java51
-rw-r--r--android/content/pm/PackageManagerInternal.java5
-rw-r--r--android/content/pm/PackageParser.java23
-rw-r--r--android/content/pm/PermissionInfo.java5
-rw-r--r--android/content/pm/ResolveInfo.java34
-rw-r--r--android/content/pm/ShortcutInfo.java207
-rw-r--r--android/content/pm/ShortcutServiceInternal.java7
-rw-r--r--android/content/res/FontResourcesParser.java15
-rw-r--r--android/content/res/ResourcesImpl.java2
-rw-r--r--android/database/CursorWindow.java45
-rw-r--r--android/database/SQLiteDatabaseIoPerfTest.java176
-rw-r--r--android/database/SQLiteDatabasePerfTest.java223
-rw-r--r--android/graphics/Bitmap.java2
-rw-r--r--android/graphics/BitmapFactory.java3
-rw-r--r--android/graphics/Typeface.java4
-rw-r--r--android/graphics/pdf/PdfEditor.java5
-rw-r--r--android/graphics/pdf/PdfRenderer.java6
-rw-r--r--android/inputmethodservice/AbstractInputMethodService.java4
-rw-r--r--android/inputmethodservice/IInputMethodWrapper.java93
-rw-r--r--android/inputmethodservice/InputMethodService.java42
-rw-r--r--android/location/GnssMeasurement.java12
-rw-r--r--android/location/Location.java44
-rw-r--r--android/media/AudioAttributes.java23
-rw-r--r--android/media/MediaMetadataRetriever.java2
-rw-r--r--android/media/MediaPlayer.java8
-rw-r--r--android/media/MediaRecorder.java6
-rw-r--r--android/media/projection/MediaProjectionManager.java9
-rw-r--r--android/media/tv/TvInputManager.java7
-rw-r--r--android/mtp/MtpDatabase.java26
-rw-r--r--android/net/IpSecAlgorithm.java24
-rw-r--r--android/net/IpSecConfig.java20
-rw-r--r--android/net/IpSecTransform.java27
-rw-r--r--android/net/LinkProperties.java30
-rw-r--r--android/net/apf/ApfFilter.java16
-rw-r--r--android/net/ip/ConnectivityPacketTracker.java23
-rw-r--r--android/net/ip/IpClient.java1712
-rw-r--r--android/net/ip/IpManager.java1689
-rw-r--r--android/net/metrics/ConnectStats.java11
-rw-r--r--android/net/metrics/DnsEvent.java21
-rw-r--r--android/net/metrics/NetworkMetrics.java168
-rw-r--r--android/net/netlink/NetlinkSocket.java2
-rw-r--r--android/net/util/SharedLog.java4
-rw-r--r--android/net/wifi/WifiManager.java20
-rw-r--r--android/net/wifi/rtt/RangingRequest.java173
-rw-r--r--android/net/wifi/rtt/RangingResult.java167
-rw-r--r--android/net/wifi/rtt/RangingResultCallback.java30
-rw-r--r--android/net/wifi/rtt/WifiRttManager.java21
-rw-r--r--android/os/BatteryProperty.java7
-rw-r--r--android/os/BatteryStats.java1227
-rw-r--r--android/os/Debug.java20
-rw-r--r--android/os/ParcelFileDescriptor.java4
-rw-r--r--android/os/PatternMatcher.java16
-rw-r--r--android/os/ServiceManager.java105
-rw-r--r--android/os/SomeService.java42
-rw-r--r--android/os/StatsLogEventWrapper.java141
-rw-r--r--android/os/StrictModeTest.java127
-rw-r--r--android/os/SystemProperties.java8
-rw-r--r--android/os/UserManager.java19
-rw-r--r--android/preference/SeekBarVolumizer.java3
-rw-r--r--android/provider/Settings.java53
-rw-r--r--android/security/net/config/ManifestConfigSource.java40
-rw-r--r--android/security/net/config/NetworkSecurityConfig.java14
-rw-r--r--android/security/net/config/XmlConfigSource.java36
-rw-r--r--android/service/autofill/AutofillService.java77
-rw-r--r--android/service/autofill/BatchUpdates.java216
-rw-r--r--android/service/autofill/CustomDescription.java195
-rw-r--r--android/service/autofill/Dataset.java30
-rw-r--r--android/service/autofill/FillEventHistory.java288
-rw-r--r--android/service/autofill/FillResponse.java48
-rw-r--r--android/service/autofill/InternalTransformation.java45
-rw-r--r--android/service/autofill/InternalValidator.java3
-rw-r--r--android/service/autofill/LuhnChecksumValidator.java18
-rw-r--r--android/service/autofill/OptionalValidators.java4
-rw-r--r--android/service/autofill/RequiredValidators.java4
-rw-r--r--android/service/autofill/SaveInfo.java88
-rw-r--r--android/service/autofill/SaveRequest.java12
-rw-r--r--android/service/autofill/Validator.java4
-rw-r--r--android/service/notification/ZenModeConfig.java79
-rw-r--r--android/service/settings/suggestions/SuggestionService.java17
-rw-r--r--android/support/LibraryVersions.java8
-rw-r--r--android/support/car/drawer/CarDrawerActivity.java152
-rw-r--r--android/support/car/drawer/CarDrawerAdapter.java182
-rw-r--r--android/support/car/drawer/CarDrawerController.java306
-rw-r--r--android/support/car/drawer/DrawerItemClickListener.java (renamed from android/arch/paging/User.java)25
-rw-r--r--android/support/car/drawer/DrawerItemViewHolder.java87
-rw-r--r--android/support/car/widget/PagedListView.java57
-rw-r--r--android/support/media/tv/BasePreviewProgram.java100
-rw-r--r--android/support/media/tv/BaseProgram.java23
-rw-r--r--android/support/media/tv/Program.java3
-rw-r--r--android/support/media/tv/TvContractCompat.java82
-rw-r--r--android/support/media/tv/WatchNextProgram.java27
-rw-r--r--android/support/mediacompat/testlib/IntentConstants.java9
-rw-r--r--android/support/mediacompat/testlib/MediaControllerConstants.java53
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java59
-rw-r--r--android/support/transition/AutoTransition.java6
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java18
-rw-r--r--android/support/v4/content/res/ResourcesCompat.java4
-rw-r--r--android/support/v4/media/RatingCompat.java23
-rw-r--r--android/support/v4/media/RatingCompatKitkat.java67
-rw-r--r--android/support/v4/provider/FontsContractCompat.java3
-rw-r--r--android/support/v7/app/MediaRouteButton.java3
-rw-r--r--android/support/v7/app/MediaRouteChooserDialog.java6
-rw-r--r--android/support/v7/app/MediaRouteControllerDialog.java8
-rw-r--r--android/support/v7/app/MediaRouterThemeHelper.java110
-rw-r--r--android/support/v7/recyclerview/extensions/ListAdapterHelperTest.java164
-rw-r--r--android/support/v7/util/DiffUtil.java67
-rw-r--r--android/support/v7/widget/AppCompatTextHelper.java37
-rw-r--r--android/support/v7/widget/RecyclerView.java18
-rw-r--r--android/support/v7/widget/TintTypedArray.java5
-rw-r--r--android/support/wear/ambient/AmbientMode.java15
-rw-r--r--android/system/ErrnoException.java92
-rw-r--r--android/system/GaiException.java92
-rw-r--r--android/system/Os.java1181
-rw-r--r--android/system/StructAddrinfo.java38
-rw-r--r--android/system/StructCapUserData.java34
-rw-r--r--android/system/StructCapUserHeader.java36
-rw-r--r--android/system/StructFlock.java26
-rw-r--r--android/system/StructGroupReq.java18
-rw-r--r--android/system/StructLinger.java28
-rw-r--r--android/system/StructPasswd.java36
-rw-r--r--android/system/StructPollfd.java36
-rw-r--r--android/system/StructRlimit.java18
-rw-r--r--android/system/StructStat.java190
-rw-r--r--android/system/StructStatVfs.java86
-rw-r--r--android/system/StructTimespec.java92
-rw-r--r--android/system/StructTimeval.java48
-rw-r--r--android/system/StructUcred.java28
-rw-r--r--android/system/StructUtsname.java58
-rw-r--r--android/telephony/CarrierConfigManager.java47
-rw-r--r--android/telephony/MbmsDownloadSession.java41
-rw-r--r--android/telephony/NetworkScanRequest.java105
-rw-r--r--android/telephony/ServiceState.java9
-rw-r--r--android/telephony/SignalStrength.java65
-rw-r--r--android/telephony/mbms/DownloadRequest.java30
-rw-r--r--android/telephony/mbms/MbmsDownloadReceiver.java11
-rw-r--r--android/telephony/mbms/vendor/MbmsDownloadServiceBase.java64
-rw-r--r--android/telephony/mbms/vendor/MbmsStreamingServiceBase.java46
-rw-r--r--android/text/AndroidBidi.java62
-rw-r--r--android/text/AndroidBidi_Delegate.java63
-rw-r--r--android/text/BoringLayoutCreateDrawPerfTest.java2
-rw-r--r--android/text/BoringLayoutIsBoringPerfTest.java2
-rw-r--r--android/text/DynamicLayout.java6
-rw-r--r--android/text/Hyphenator.java251
-rw-r--r--android/text/Layout.java14
-rw-r--r--android/text/MeasuredText.java6
-rw-r--r--android/text/PaintMeasureDrawPerfTest.java2
-rw-r--r--android/text/StaticLayout.java74
-rw-r--r--android/text/StaticLayoutCreateDrawPerfTest.java2
-rw-r--r--android/text/StaticLayout_Delegate.java26
-rw-r--r--android/text/TextLine.java10
-rw-r--r--android/text/TextViewSetTextMeasurePerfTest.java4
-rw-r--r--android/util/Log.java295
-rw-r--r--android/util/LruCache.java25
-rw-r--r--android/util/MutableBoolean.java8
-rw-r--r--android/util/MutableByte.java8
-rw-r--r--android/util/MutableChar.java8
-rw-r--r--android/util/MutableDouble.java8
-rw-r--r--android/util/MutableFloat.java8
-rw-r--r--android/util/MutableInt.java8
-rw-r--r--android/util/MutableLong.java8
-rw-r--r--android/util/MutableShort.java8
-rw-r--r--android/util/StatsLog.java76
-rw-r--r--android/util/StatsLogKey.java48
-rw-r--r--android/util/StatsLogTag.java32
-rw-r--r--android/util/StatsLogValue.java54
-rw-r--r--android/util/apk/ApkSignatureSchemeV2Verifier.java11
-rw-r--r--android/view/MenuItem.java21
-rw-r--r--android/view/SurfaceControl.java538
-rw-r--r--android/view/SurfaceView.java1135
-rw-r--r--android/view/View.java262
-rw-r--r--android/view/ViewConfiguration.java18
-rw-r--r--android/view/ViewDebug.java167
-rw-r--r--android/view/ViewGroup.java98
-rw-r--r--android/view/ViewRootImpl.java758
-rw-r--r--android/view/ViewStructure.java24
-rw-r--r--android/view/WindowManagerInternal.java4
-rw-r--r--android/view/WindowManagerPolicy.java7
-rw-r--r--android/view/accessibility/AccessibilityCache.java2
-rw-r--r--android/view/accessibility/AccessibilityManager.java916
-rw-r--r--android/view/autofill/AutofillManager.java149
-rw-r--r--android/view/inputmethod/InputMethod.java15
-rw-r--r--android/view/textclassifier/Log.java46
-rw-r--r--android/view/textclassifier/SmartSelection.java63
-rw-r--r--android/view/textclassifier/TextClassifier.java8
-rw-r--r--android/view/textclassifier/TextClassifierConstants.java90
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java17
-rw-r--r--android/view/textservice/TextServicesManager.java200
-rw-r--r--android/webkit/WebView.java2878
-rw-r--r--android/widget/Editor.java20
-rw-r--r--android/widget/RemoteViews.java23
-rw-r--r--android/widget/RemoteViewsAdapter.java712
-rw-r--r--android/widget/SelectionActionModeHelper.java30
-rw-r--r--android/widget/TextView.java11
314 files changed, 15080 insertions, 14189 deletions
diff --git a/android/accessibilityservice/GestureDescription.java b/android/accessibilityservice/GestureDescription.java
index 92567d75..56f4ae2b 100644
--- a/android/accessibilityservice/GestureDescription.java
+++ b/android/accessibilityservice/GestureDescription.java
@@ -428,6 +428,18 @@ public final class GestureDescription {
}
@Override
+ public String toString() {
+ return "TouchPoint{"
+ + "mStrokeId=" + mStrokeId
+ + ", mContinuedStrokeId=" + mContinuedStrokeId
+ + ", mIsStartOfPath=" + mIsStartOfPath
+ + ", mIsEndOfPath=" + mIsEndOfPath
+ + ", mX=" + mX
+ + ", mY=" + mY
+ + '}';
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/android/app/Activity.java b/android/app/Activity.java
index e0ac9113..85f73bb7 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -542,9 +542,9 @@ import java.util.List;
* <ul>
* <li> <p>When creating a new document, the backing database entry or file for
* it is created immediately. For example, if the user chooses to write
- * a new e-mail, a new entry for that e-mail is created as soon as they
+ * a new email, a new entry for that email is created as soon as they
* start entering data, so that if they go to any other activity after
- * that point this e-mail will now appear in the list of drafts.</p>
+ * that point this email will now appear in the list of drafts.</p>
* <li> <p>When an activity's <code>onPause()</code> method is called, it should
* commit to the backing content provider or file any changes the user
* has made. This ensures that those changes will be seen by any other
@@ -1879,7 +1879,7 @@ public class Activity extends ContextThemeWrapper
if (isFinishing()) {
if (mAutoFillResetNeeded) {
- getAutofillManager().commit();
+ getAutofillManager().onActivityFinished();
} else if (mIntent != null
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
// Activity was launched when user tapped a link in the Autofill Save UI - since
@@ -6259,6 +6259,8 @@ public class Activity extends ContextThemeWrapper
final AutofillManager afm = getAutofillManager();
if (afm != null) {
afm.dump(prefix, writer);
+ } else {
+ writer.print(prefix); writer.println("No AutofillManager");
}
}
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 5e61727f..fc4c8d7f 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -16,14 +16,8 @@
package android.app;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
import android.Manifest;
+import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -670,138 +664,6 @@ public class ActivityManager {
/** Invalid stack ID. */
public static final int INVALID_STACK_ID = -1;
- /** First static stack ID.
- * @hide */
- private static final int FIRST_STATIC_STACK_ID = 0;
-
- /** ID of stack where fullscreen activities are normally launched into.
- * @hide */
- public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
-
- /** ID of stack where freeform/resized activities are normally launched into.
- * @hide */
- public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
-
- /** ID of stack that occupies a dedicated region of the screen.
- * @hide */
- public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
-
- /** ID of stack that always on top (always visible) when it exist.
- * @hide */
- public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
- /** Last static stack stack ID.
- * @hide */
- private static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
-
- /** Start of ID range used by stacks that are created dynamically.
- * @hide */
- public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
-
- // TODO: Figure-out a way to remove this.
- /** @hide */
- public static boolean isStaticStack(int stackId) {
- return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID;
- }
-
- // TODO: It seems this mostly means a stack on a secondary display now. Need to see if
- // there are other meanings. If not why not just use information from the display?
- /** @hide */
- public static boolean isDynamicStack(int stackId) {
- return stackId >= FIRST_DYNAMIC_STACK_ID;
- }
-
- /**
- * Returns true if we try to maintain focus in the current stack when the top activity
- * finishes.
- * @hide
- */
- // TODO: Figure-out a way to remove. Probably isn't needed in the new world...
- public static boolean keepFocusInStackIfPossible(int stackId) {
- return stackId == FREEFORM_WORKSPACE_STACK_ID
- || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
- }
-
- /**
- * Returns true if the windows of tasks being moved to the target stack from the source
- * stack should be replaced, meaning that window manager will keep the old window around
- * until the new is ready.
- * @hide
- */
- public static boolean replaceWindowsOnTaskMove(int sourceStackId, int targetStackId) {
- return sourceStackId == FREEFORM_WORKSPACE_STACK_ID
- || targetStackId == FREEFORM_WORKSPACE_STACK_ID;
- }
-
- /**
- * Returns true if the top task in the task is allowed to return home when finished and
- * there are other tasks in the stack.
- * @hide
- */
- public static boolean allowTopTaskToReturnHome(int stackId) {
- return stackId != PINNED_STACK_ID;
- }
-
- /**
- * Returns true if the stack should be resized to match the bounds specified by
- * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
- * @hide
- */
- public static boolean resizeStackWithLaunchBounds(int stackId) {
- return stackId == PINNED_STACK_ID;
- }
-
- /**
- * Returns true if a window from the specified stack with {@param stackId} are normally
- * fullscreen, i. e. they can become the top opaque fullscreen window, meaning that it
- * controls system bars, lockscreen occluded/dismissing state, screen rotation animation,
- * etc.
- * @hide
- */
- // TODO: What about the other side of docked stack if we move this to WindowConfiguration?
- public static boolean normallyFullscreenWindows(int stackId) {
- return stackId != PINNED_STACK_ID && stackId != FREEFORM_WORKSPACE_STACK_ID
- && stackId != DOCKED_STACK_ID;
- }
-
- /** Returns the stack id for the input windowing mode.
- * @hide */
- // TODO: To be removed once we are not using stack id for stuff...
- public static int getStackIdForWindowingMode(int windowingMode) {
- switch (windowingMode) {
- case WINDOWING_MODE_PINNED: return PINNED_STACK_ID;
- case WINDOWING_MODE_FREEFORM: return FREEFORM_WORKSPACE_STACK_ID;
- case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return DOCKED_STACK_ID;
- case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return FULLSCREEN_WORKSPACE_STACK_ID;
- case WINDOWING_MODE_FULLSCREEN: return FULLSCREEN_WORKSPACE_STACK_ID;
- default: return INVALID_STACK_ID;
- }
- }
-
- /** Returns the windowing mode that should be used for this input stack id.
- * @hide */
- // TODO: To be removed once we are not using stack id for stuff...
- public static int getWindowingModeForStackId(int stackId, boolean inSplitScreenMode) {
- final int windowingMode;
- switch (stackId) {
- case FULLSCREEN_WORKSPACE_STACK_ID:
- windowingMode = inSplitScreenMode
- ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN;
- break;
- case PINNED_STACK_ID:
- windowingMode = WINDOWING_MODE_PINNED;
- break;
- case DOCKED_STACK_ID:
- windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
- break;
- case FREEFORM_WORKSPACE_STACK_ID:
- windowingMode = WINDOWING_MODE_FREEFORM;
- break;
- default :
- windowingMode = WINDOWING_MODE_UNDEFINED;
- }
- return windowingMode;
- }
}
/**
@@ -1080,11 +942,14 @@ public class ActivityManager {
ATTR_TASKDESCRIPTION_PREFIX + "color";
private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
- private static final String ATTR_TASKDESCRIPTIONICONFILENAME =
+ private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
+ private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
+ ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
private String mLabel;
private Bitmap mIcon;
+ private int mIconRes;
private String mIconFilename;
private int mColorPrimary;
private int mColorBackground;
@@ -1098,9 +963,27 @@ public class ActivityManager {
* @param icon An icon that represents the current state of this task.
* @param colorPrimary A color to override the theme's primary color. This color must be
* opaque.
+ * @deprecated use TaskDescription constructor with icon resource instead
*/
+ @Deprecated
public TaskDescription(String label, Bitmap icon, int colorPrimary) {
- this(label, icon, null, colorPrimary, 0, 0, 0);
+ this(label, icon, 0, null, colorPrimary, 0, 0, 0);
+ if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+ throw new RuntimeException("A TaskDescription's primary color should be opaque");
+ }
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this task.
+ * @param iconRes A drawable resource of an icon that represents the current state of this
+ * activity.
+ * @param colorPrimary A color to override the theme's primary color. This color must be
+ * opaque.
+ */
+ public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
+ this(label, null, iconRes, null, colorPrimary, 0, 0, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1111,9 +994,22 @@ public class ActivityManager {
*
* @param label A label and description of the current state of this activity.
* @param icon An icon that represents the current state of this activity.
+ * @deprecated use TaskDescription constructor with icon resource instead
*/
+ @Deprecated
public TaskDescription(String label, Bitmap icon) {
- this(label, icon, null, 0, 0, 0, 0);
+ this(label, icon, 0, null, 0, 0, 0, 0);
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ * @param iconRes A drawable resource of an icon that represents the current state of this
+ * activity.
+ */
+ public TaskDescription(String label, @DrawableRes int iconRes) {
+ this(label, null, iconRes, null, 0, 0, 0, 0);
}
/**
@@ -1122,21 +1018,22 @@ public class ActivityManager {
* @param label A label and description of the current state of this activity.
*/
public TaskDescription(String label) {
- this(label, null, null, 0, 0, 0, 0);
+ this(label, null, 0, null, 0, 0, 0, 0);
}
/**
* Creates an empty TaskDescription.
*/
public TaskDescription() {
- this(null, null, null, 0, 0, 0, 0);
+ this(null, null, 0, null, 0, 0, 0, 0);
}
/** @hide */
- public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary,
- int colorBackground, int statusBarColor, int navigationBarColor) {
+ public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename,
+ int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor) {
mLabel = label;
- mIcon = icon;
+ mIcon = bitmap;
+ mIconRes = iconRes;
mIconFilename = iconFilename;
mColorPrimary = colorPrimary;
mColorBackground = colorBackground;
@@ -1158,6 +1055,7 @@ public class ActivityManager {
public void copyFrom(TaskDescription other) {
mLabel = other.mLabel;
mIcon = other.mIcon;
+ mIconRes = other.mIconRes;
mIconFilename = other.mIconFilename;
mColorPrimary = other.mColorPrimary;
mColorBackground = other.mColorBackground;
@@ -1173,6 +1071,7 @@ public class ActivityManager {
public void copyFromPreserveHiddenFields(TaskDescription other) {
mLabel = other.mLabel;
mIcon = other.mIcon;
+ mIconRes = other.mIconRes;
mIconFilename = other.mIconFilename;
mColorPrimary = other.mColorPrimary;
if (other.mColorBackground != 0) {
@@ -1245,6 +1144,14 @@ public class ActivityManager {
}
/**
+ * Sets the icon resource for this task description.
+ * @hide
+ */
+ public void setIcon(int iconRes) {
+ mIconRes = iconRes;
+ }
+
+ /**
* Moves the icon bitmap reference from an actual Bitmap to a file containing the
* bitmap.
* @hide
@@ -1272,6 +1179,13 @@ public class ActivityManager {
}
/** @hide */
+ @TestApi
+ public int getIconResource() {
+ return mIconRes;
+ }
+
+ /** @hide */
+ @TestApi
public String getIconFilename() {
return mIconFilename;
}
@@ -1337,7 +1251,10 @@ public class ActivityManager {
Integer.toHexString(mColorBackground));
}
if (mIconFilename != null) {
- out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename);
+ out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
+ }
+ if (mIconRes != 0) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes));
}
}
@@ -1349,8 +1266,10 @@ public class ActivityManager {
setPrimaryColor((int) Long.parseLong(attrValue, 16));
} else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) {
setBackgroundColor((int) Long.parseLong(attrValue, 16));
- } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) {
+ } else if (ATTR_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) {
setIconFilename(attrValue);
+ } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) {
+ setIcon(Integer.parseInt(attrValue, 10));
}
}
@@ -1373,6 +1292,7 @@ public class ActivityManager {
dest.writeInt(1);
mIcon.writeToParcel(dest, 0);
}
+ dest.writeInt(mIconRes);
dest.writeInt(mColorPrimary);
dest.writeInt(mColorBackground);
dest.writeInt(mStatusBarColor);
@@ -1388,6 +1308,7 @@ public class ActivityManager {
public void readFromParcel(Parcel source) {
mLabel = source.readInt() > 0 ? source.readString() : null;
mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+ mIconRes = source.readInt();
mColorPrimary = source.readInt();
mColorBackground = source.readInt();
mStatusBarColor = source.readInt();
@@ -1408,8 +1329,8 @@ public class ActivityManager {
@Override
public String toString() {
return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
- " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary +
- " colorBackground: " + mColorBackground +
+ " IconRes: " + mIconRes + " IconFilename: " + mIconFilename +
+ " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground +
" statusBarColor: " + mColorBackground +
" navigationBarColor: " + mNavigationBarColor;
}
@@ -1571,7 +1492,6 @@ public class ActivityManager {
}
dest.writeInt(stackId);
dest.writeInt(userId);
- dest.writeLong(firstActiveTime);
dest.writeLong(lastActiveTime);
dest.writeInt(affiliatedTaskId);
dest.writeInt(affiliatedTaskColor);
@@ -1600,7 +1520,6 @@ public class ActivityManager {
TaskDescription.CREATOR.createFromParcel(source) : null;
stackId = source.readInt();
userId = source.readInt();
- firstActiveTime = source.readLong();
lastActiveTime = source.readLong();
affiliatedTaskId = source.readInt();
affiliatedTaskColor = source.readInt();
@@ -1643,31 +1562,6 @@ public class ActivityManager {
public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
/**
- * Provides a list that contains recent tasks for all
- * profiles of a user.
- * @hide
- */
- public static final int RECENT_INCLUDE_PROFILES = 0x0004;
-
- /**
- * Ignores all tasks that are on the home stack.
- * @hide
- */
- public static final int RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS = 0x0008;
-
- /**
- * Ignores the top task in the docked stack.
- * @hide
- */
- public static final int RECENT_INGORE_DOCKED_STACK_TOP_TASK = 0x0010;
-
- /**
- * Ignores all tasks that are on the pinned stack.
- * @hide
- */
- public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020;
-
- /**
* <p></p>Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
@@ -1702,33 +1596,7 @@ public class ActivityManager {
public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
throws SecurityException {
try {
- return getService().getRecentTasks(maxNum,
- flags, UserHandle.myUserId()).getList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Same as {@link #getRecentTasks(int, int)} but returns the recent tasks for a
- * specific user. It requires holding
- * the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
- * @param maxNum The maximum number of entries to return in the list. The
- * actual number returned may be smaller, depending on how many tasks the
- * user has started and the maximum number the system can remember.
- * @param flags Information about what to return. May be any combination
- * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
- *
- * @return Returns a list of RecentTaskInfo records describing each of
- * the recent tasks. Most recently activated tasks go first.
- *
- * @hide
- */
- public List<RecentTaskInfo> getRecentTasksForUser(int maxNum, int flags, int userId)
- throws SecurityException {
- try {
- return getService().getRecentTasks(maxNum,
- flags, userId).getList();
+ return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2021,22 +1889,6 @@ public class ActivityManager {
}
/**
- * Completely remove the given task.
- *
- * @param taskId Identifier of the task to be removed.
- * @return Returns true if the given task was found and removed.
- *
- * @hide
- */
- public boolean removeTask(int taskId) throws SecurityException {
- try {
- return getService().removeTask(taskId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Sets the windowing mode for a specific task. Only works on tasks of type
* {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}
* @param taskId The id of the task to set the windowing mode for.
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index a68c3a5c..b62e4c2d 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -17,13 +17,13 @@
package android.app;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -159,6 +159,12 @@ public class ActivityOptions {
private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
/**
+ * Whether the activity should be launched into LockTask mode.
+ * @see #setLockTaskMode(boolean)
+ */
+ private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
+
+ /**
* The display id the activity should be launched into.
* @see #setLaunchDisplayId(int)
* @hide
@@ -279,6 +285,7 @@ public class ActivityOptions {
private int mResultCode;
private int mExitCoordinatorIndex;
private PendingIntent mUsageTimeReport;
+ private boolean mLockTaskMode = false;
private int mLaunchDisplayId = INVALID_DISPLAY;
@WindowConfiguration.WindowingMode
private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -870,6 +877,7 @@ public class ActivityOptions {
mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
break;
}
+ mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
@@ -1056,6 +1064,37 @@ public class ActivityOptions {
}
/**
+ * Gets whether the activity is to be launched into LockTask mode.
+ * @return {@code true} if the activity is to be launched into LockTask mode.
+ * @see Activity#startLockTask()
+ * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+ */
+ public boolean getLockTaskMode() {
+ return mLockTaskMode;
+ }
+
+ /**
+ * Sets whether the activity is to be launched into LockTask mode.
+ *
+ * Use this option to start an activity in LockTask mode. Note that only apps permitted by
+ * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if
+ * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns
+ * {@code false} for the package of the target activity, a {@link SecurityException} will be
+ * thrown during {@link Context#startActivity(Intent, Bundle)}.
+ *
+ * Defaults to {@code false} if not set.
+ *
+ * @param lockTaskMode {@code true} if the activity is to be launched into LockTask mode.
+ * @return {@code this} {@link ActivityOptions} instance.
+ * @see Activity#startLockTask()
+ * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+ */
+ public ActivityOptions setLockTaskMode(boolean lockTaskMode) {
+ mLockTaskMode = lockTaskMode;
+ return this;
+ }
+
+ /**
* Gets the id of the display where activity should be launched.
* @return The id of the display where activity should be launched,
* {@link android.view.Display#INVALID_DISPLAY} if not set.
@@ -1248,6 +1287,7 @@ public class ActivityOptions {
mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
break;
}
+ mLockTaskMode = otherOptions.mLockTaskMode;
mAnimSpecs = otherOptions.mAnimSpecs;
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
@@ -1322,6 +1362,7 @@ public class ActivityOptions {
b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
break;
}
+ b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java
index 54f74b15..1fe29004 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -387,8 +387,6 @@ public class KeyguardManager {
* such as the Home key and the right soft keys, don't work.
*
* @return true if in keyguard restricted input mode.
- *
- * @see android.view.WindowManagerPolicy#inKeyguardRestrictedKeyInputMode
*/
public boolean inKeyguardRestrictedInputMode() {
try {
diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java
index 47063f08..c06ad3f3 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -32,6 +32,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.Preconditions;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index eb52cb7f..a52dc1e4 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -934,8 +934,14 @@ public class NotificationManager {
public static final int PRIORITY_CATEGORY_CALLS = 1 << 3;
/** Calls from repeat callers are prioritized. */
public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4;
+ /** Alarms are prioritized */
+ public static final int PRIORITY_CATEGORY_ALARMS = 1 << 5;
+ /** Media, system, game (catch-all for non-never suppressible sounds) are prioritized */
+ public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 1 << 6;
private static final int[] ALL_PRIORITY_CATEGORIES = {
+ PRIORITY_CATEGORY_ALARMS,
+ PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
PRIORITY_CATEGORY_REMINDERS,
PRIORITY_CATEGORY_EVENTS,
PRIORITY_CATEGORY_MESSAGES,
@@ -1135,6 +1141,9 @@ public class NotificationManager {
case PRIORITY_CATEGORY_MESSAGES: return "PRIORITY_CATEGORY_MESSAGES";
case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS";
case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS";
+ case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS";
+ case PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER:
+ return "PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER";
default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory;
}
}
diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java
index 8987bc02..23c4166d 100644
--- a/android/app/StatusBarManager.java
+++ b/android/app/StatusBarManager.java
@@ -73,15 +73,16 @@ public class StatusBarManager {
public static final int DISABLE2_QUICK_SETTINGS = 1;
public static final int DISABLE2_SYSTEM_ICONS = 1 << 1;
public static final int DISABLE2_NOTIFICATION_SHADE = 1 << 2;
+ public static final int DISABLE2_GLOBAL_ACTIONS = 1 << 3;
public static final int DISABLE2_NONE = 0x00000000;
public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS
- | DISABLE2_NOTIFICATION_SHADE;
+ | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS;
@IntDef(flag = true,
value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS,
- DISABLE2_NOTIFICATION_SHADE})
+ DISABLE2_NOTIFICATION_SHADE, DISABLE2_GLOBAL_ACTIONS})
@Retention(RetentionPolicy.SOURCE)
public @interface Disable2Flags {}
diff --git a/android/app/TaskStackListener.java b/android/app/TaskStackListener.java
index 402e2095..895d12a7 100644
--- a/android/app/TaskStackListener.java
+++ b/android/app/TaskStackListener.java
@@ -77,7 +77,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onTaskRemovalStarted(int taskId) {
+ public void onTaskRemovalStarted(int taskId) throws RemoteException {
}
@Override
@@ -91,11 +91,10 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onTaskProfileLocked(int taskId, int userId) {
+ public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
}
@Override
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
- throws RemoteException {
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException {
}
}
diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java
index 942cc995..081bd814 100644
--- a/android/app/WallpaperManager.java
+++ b/android/app/WallpaperManager.java
@@ -388,11 +388,12 @@ public class WallpaperManager {
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
@SetWallpaperFlags int which) {
- return peekWallpaperBitmap(context, returnDefault, which, context.getUserId());
+ return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
+ false /* hardware */);
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
- @SetWallpaperFlags int which, int userId) {
+ @SetWallpaperFlags int which, int userId, boolean hardware) {
if (mService != null) {
try {
if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -409,7 +410,7 @@ public class WallpaperManager {
mCachedWallpaper = null;
mCachedWallpaperUserId = 0;
try {
- mCachedWallpaper = getCurrentWallpaperLocked(context, userId);
+ mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
mCachedWallpaperUserId = userId;
} catch (OutOfMemoryError e) {
Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -447,7 +448,7 @@ public class WallpaperManager {
}
}
- private Bitmap getCurrentWallpaperLocked(Context context, int userId) {
+ private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
if (mService == null) {
Log.w(TAG, "WallpaperService not running");
return null;
@@ -460,6 +461,9 @@ public class WallpaperManager {
if (fd != null) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
+ if (hardware) {
+ options.inPreferredConfig = Bitmap.Config.HARDWARE;
+ }
return BitmapFactory.decodeFileDescriptor(
fd.getFileDescriptor(), null, options);
} catch (OutOfMemoryError e) {
@@ -814,12 +818,23 @@ public class WallpaperManager {
}
/**
- * Like {@link #getDrawable()} but returns a Bitmap.
+ * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
*
* @hide
*/
public Bitmap getBitmap() {
- return getBitmapAsUser(mContext.getUserId());
+ return getBitmap(false);
+ }
+
+ /**
+ * Like {@link #getDrawable()} but returns a Bitmap.
+ *
+ * @param hardware Asks for a hardware backed bitmap.
+ * @see Bitmap.Config#HARDWARE
+ * @hide
+ */
+ public Bitmap getBitmap(boolean hardware) {
+ return getBitmapAsUser(mContext.getUserId(), hardware);
}
/**
@@ -827,8 +842,8 @@ public class WallpaperManager {
*
* @hide
*/
- public Bitmap getBitmapAsUser(int userId) {
- return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
+ public Bitmap getBitmapAsUser(int userId, boolean hardware) {
+ return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
}
/**
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 6b405384..251863ca 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -511,7 +511,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
}
- private static String windowingModeToString(@WindowingMode int windowingMode) {
+ /** @hide */
+ public static String windowingModeToString(@WindowingMode int windowingMode) {
switch (windowingMode) {
case WINDOWING_MODE_UNDEFINED: return "undefined";
case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 3c530633..ab8edee7 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -6533,6 +6533,52 @@ public class DevicePolicyManager {
}
/**
+ * Called by device owner to set the system wall clock time. This only takes effect if called
+ * when {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
+ * returned.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+ * @param millis time in milliseconds since the Epoch
+ * @return {@code true} if set time succeeded, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public boolean setTime(@NonNull ComponentName admin, long millis) {
+ throwIfParentInstance("setTime");
+ if (mService != null) {
+ try {
+ return mService.setTime(admin, millis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device owner to set the system's persistent default time zone. This only takes
+ * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise
+ * {@code false} will be returned.
+ *
+ * @see android.app.AlarmManager#setTimeZone(String)
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+ * @param timeZone one of the Olson ids from the list returned by
+ * {@link java.util.TimeZone#getAvailableIDs}
+ * @return {@code true} if set timezone succeeded, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public boolean setTimeZone(@NonNull ComponentName admin, String timeZone) {
+ throwIfParentInstance("setTimeZone");
+ if (mService != null) {
+ try {
+ return mService.setTimeZone(admin, timeZone);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Called by profile or device owners to update {@link android.provider.Settings.Secure}
* settings. Validation that the value of the setting is in the correct form for the setting
* type should be performed by the caller.
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index d9b7cd7e..e491a4f9 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -616,6 +616,9 @@ public class AssistStructure implements Parcelable {
CharSequence[] mAutofillOptions;
boolean mSanitized;
HtmlInfo mHtmlInfo;
+ int mMinEms = -1;
+ int mMaxEms = -1;
+ int mMaxLength = -1;
// POJO used to override some autofill-related values when the node is parcelized.
// Not written to parcel.
@@ -713,6 +716,9 @@ public class AssistStructure implements Parcelable {
if (p instanceof HtmlInfo) {
mHtmlInfo = (HtmlInfo) p;
}
+ mMinEms = in.readInt();
+ mMaxEms = in.readInt();
+ mMaxLength = in.readInt();
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
mX = in.readInt();
@@ -876,6 +882,9 @@ public class AssistStructure implements Parcelable {
} else {
out.writeParcelable(null, 0);
}
+ out.writeInt(mMinEms);
+ out.writeInt(mMaxEms);
+ out.writeInt(mMaxLength);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
out.writeInt(mX);
@@ -1444,6 +1453,39 @@ public class AssistStructure implements Parcelable {
public ViewNode getChildAt(int index) {
return mChildren[index];
}
+
+ /**
+ * Returns the minimum width in ems of the text associated with this node, or {@code -1}
+ * if not supported by the node.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ */
+ public int getMinTextEms() {
+ return mMinEms;
+ }
+
+ /**
+ * Returns the maximum width in ems of the text associated with this node, or {@code -1}
+ * if not supported by the node.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ */
+ public int getMaxTextEms() {
+ return mMaxEms;
+ }
+
+ /**
+ * Returns the maximum length of the text associated with this node node, or {@code -1}
+ * if not supported by the node or not set.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ */
+ public int getMaxTextLength() {
+ return mMaxLength;
+ }
}
/**
@@ -1776,6 +1818,21 @@ public class AssistStructure implements Parcelable {
}
@Override
+ public void setMinTextEms(int minEms) {
+ mNode.mMinEms = minEms;
+ }
+
+ @Override
+ public void setMaxTextEms(int maxEms) {
+ mNode.mMaxEms = maxEms;
+ }
+
+ @Override
+ public void setMaxTextLength(int maxLength) {
+ mNode.mMaxLength = maxLength;
+ }
+
+ @Override
public void setDataIsSensitive(boolean sensitive) {
mNode.mSanitized = !sensitive;
}
diff --git a/android/app/job/JobScheduler.java b/android/app/job/JobScheduler.java
index 3868439f..0deb2e13 100644
--- a/android/app/job/JobScheduler.java
+++ b/android/app/job/JobScheduler.java
@@ -24,7 +24,6 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.ClipData;
import android.content.Context;
-import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -40,16 +39,18 @@ import java.util.List;
* and how to construct them. You will construct these JobInfo objects and pass them to the
* JobScheduler with {@link #schedule(JobInfo)}. When the criteria declared are met, the
* system will execute this job on your application's {@link android.app.job.JobService}.
- * You identify which JobService is meant to execute the logic for your job when you create the
- * JobInfo with
+ * You identify the service component that implements the logic for your job when you
+ * construct the JobInfo using
* {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}.
* </p>
* <p>
- * The framework will be intelligent about when you receive your callbacks, and attempt to batch
- * and defer them as much as possible. Typically if you don't specify a deadline on your job, it
- * can be run at any moment depending on the current state of the JobScheduler's internal queue,
- * however it might be deferred as long as until the next time the device is connected to a power
- * source.
+ * The framework will be intelligent about when it executes jobs, and attempt to batch
+ * and defer them as much as possible. Typically if you don't specify a deadline on a job, it
+ * can be run at any moment depending on the current state of the JobScheduler's internal queue.
+ * <p>
+ * While a job is running, the system holds a wakelock on behalf of your app. For this reason,
+ * you do not need to take any action to guarantee that the device stays awake for the
+ * duration of the job.
* </p>
* <p>You do not
* instantiate this class directly; instead, retrieve it through
@@ -141,30 +142,34 @@ public abstract class JobScheduler {
int userId, String tag);
/**
- * Cancel a job that is pending in the JobScheduler.
- * @param jobId unique identifier for this job. Obtain this value from the jobs returned by
- * {@link #getAllPendingJobs()}.
+ * Cancel the specified job. If the job is currently executing, it is stopped
+ * immediately and the return value from its {@link JobService#onStopJob(JobParameters)}
+ * method is ignored.
+ *
+ * @param jobId unique identifier for the job to be canceled, as supplied to
+ * {@link JobInfo.Builder#JobInfo.Builder(int, android.content.ComponentName)
+ * JobInfo.Builder(int, android.content.ComponentName)}.
*/
public abstract void cancel(int jobId);
/**
- * Cancel all jobs that have been registered with the JobScheduler by this package.
+ * Cancel <em>all</em> jobs that have been scheduled by the calling application.
*/
public abstract void cancelAll();
/**
- * Retrieve all jobs for this package that are pending in the JobScheduler.
+ * Retrieve all jobs that have been scheduled by the calling application.
*
- * @return a list of all the jobs registered by this package that have not
- * yet been executed.
+ * @return a list of all of the app's scheduled jobs. This includes jobs that are
+ * currently started as well as those that are still waiting to run.
*/
public abstract @NonNull List<JobInfo> getAllPendingJobs();
/**
- * Retrieve a specific job for this package that is pending in the
- * JobScheduler.
+ * Look up the description of a scheduled job.
*
- * @return job registered by this package that has not yet been executed.
+ * @return The {@link JobInfo} description of the given scheduled job, or {@code null}
+ * if the supplied job ID does not correspond to any job.
*/
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java
index 9096b47b..69afed20 100644
--- a/android/app/job/JobService.java
+++ b/android/app/job/JobService.java
@@ -18,16 +18,7 @@ package android.app.job;
import android.app.Service;
import android.content.Intent;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.lang.ref.WeakReference;
/**
* <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
@@ -55,7 +46,7 @@ public abstract class JobService extends Service {
* </pre>
*
* <p>If a job service is declared in the manifest but not protected with this
- * permission, that service will be ignored by the OS.
+ * permission, that service will be ignored by the system.
*/
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
@@ -81,14 +72,36 @@ public abstract class JobService extends Service {
}
/**
- * Override this method with the callback logic for your job. Any such logic needs to be
- * performed on a separate thread, as this function is executed on your application's main
- * thread.
+ * Called to indicate that the job has begun executing. Override this method with the
+ * logic for your job. Like all other component lifecycle callbacks, this method executes
+ * on your application's main thread.
+ * <p>
+ * Return {@code true} from this method if your job needs to continue running. If you
+ * do this, the job remains active until you call
+ * {@link #jobFinished(JobParameters, boolean)} to tell the system that it has completed
+ * its work, or until the job's required constraints are no longer satisfied. For
+ * example, if the job was scheduled using
+ * {@link JobInfo.Builder#setRequiresCharging(boolean) setRequiresCharging(true)},
+ * it will be immediately halted by the system if the user unplugs the device from power,
+ * the job's {@link #onStopJob(JobParameters)} callback will be invoked, and the app
+ * will be expected to shut down all ongoing work connected with that job.
+ * <p>
+ * The system holds a wakelock on behalf of your app as long as your job is executing.
+ * This wakelock is acquired before this method is invoked, and is not released until either
+ * you call {@link #jobFinished(JobParameters, boolean)}, or after the system invokes
+ * {@link #onStopJob(JobParameters)} to notify your job that it is being shut down
+ * prematurely.
+ * <p>
+ * Returning {@code false} from this method means your job is already finished. The
+ * system's wakelock for the job will be released, and {@link #onStopJob(JobParameters)}
+ * will not be invoked.
*
- * @param params Parameters specifying info about this job, including the extras bundle you
- * optionally provided at job-creation time.
- * @return True if your service needs to process the work (on a separate thread). False if
- * there's no more work to be done for this job.
+ * @param params Parameters specifying info about this job, including the optional
+ * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle).
+ * This object serves to identify this specific running job instance when calling
+ * {@link #jobFinished(JobParameters, boolean)}.
+ * @return {@code true} if your service will continue running, using a separate thread
+ * when appropriate. {@code false} means that this job has completed its work.
*/
public abstract boolean onStartJob(JobParameters params);
@@ -101,37 +114,44 @@ public abstract class JobService extends Service {
* {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
* job was executing the user toggled WiFi. Another example is if you had specified
* {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
- * idle maintenance window. You are solely responsible for the behaviour of your application
- * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
- * immediate repercussion is that the system will cease holding a wakelock for you.</p>
+ * idle maintenance window. You are solely responsible for the behavior of your application
+ * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+ * <p>
+ * Once this method returns, the system releases the wakelock that it is holding on
+ * behalf of the job.</p>
*
- * @param params Parameters specifying info about this job.
- * @return True to indicate to the JobManager whether you'd like to reschedule this job based
- * on the retry criteria provided at job creation-time. False to drop the job. Regardless of
- * the value returned, your job must stop executing.
+ * @param params The parameters identifying this job, as supplied to
+ * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
+ * this job based on the retry criteria provided at job creation-time; or {@code false}
+ * to end the job entirely. Regardless of the value returned, your job must stop executing.
*/
public abstract boolean onStopJob(JobParameters params);
/**
- * Call this to inform the JobManager you've finished executing. This can be called from any
- * thread, as it will ultimately be run on your application's main thread. When the system
- * receives this message it will release the wakelock being held.
+ * Call this to inform the JobScheduler that the job has finished its work. When the
+ * system receives this message, it releases the wakelock being held for the job.
* <p>
- * You can specify post-execution behaviour to the scheduler here with
- * <code>needsReschedule </code>. This will apply a back-off timer to your job based on
- * the default, or what was set with
- * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original
- * requirements are always honoured even for a backed-off job. Note that a job running in
- * idle mode will not be backed-off. Instead what will happen is the job will be re-added
- * to the queue and re-executed within a future idle maintenance window.
+ * You can request that the job be scheduled again by passing {@code true} as
+ * the <code>wantsReschedule</code> parameter. This will apply back-off policy
+ * for the job; this policy can be adjusted through the
+ * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
+ * when the job is originally scheduled. The job's initial
+ * requirements are preserved when jobs are rescheduled, regardless of backed-off
+ * policy.
+ * <p class="note">
+ * A job running while the device is dozing will not be rescheduled with the normal back-off
+ * policy. Instead, the job will be re-added to the queue and executed again during
+ * a future idle maintenance window.
* </p>
*
- * @param params Parameters specifying system-provided info about this job, this was given to
- * your application in {@link #onStartJob(JobParameters)}.
- * @param needsReschedule True if this job should be rescheduled according to the back-off
- * criteria specified at schedule-time. False otherwise.
+ * @param params The parameters identifying this job, as supplied to
+ * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param wantsReschedule {@code true} if this job should be rescheduled according
+ * to the back-off criteria specified when it was first scheduled; {@code false}
+ * otherwise.
*/
- public final void jobFinished(JobParameters params, boolean needsReschedule) {
- mEngine.jobFinished(params, needsReschedule);
+ public final void jobFinished(JobParameters params, boolean wantsReschedule) {
+ mEngine.jobFinished(params, wantsReschedule);
}
-} \ No newline at end of file
+}
diff --git a/android/slice/Slice.java b/android/app/slice/Slice.java
index 57686548..7f9f74b4 100644
--- a/android/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -14,38 +14,35 @@
* limitations under the License.
*/
-package android.slice;
-
-import static android.slice.SliceItem.TYPE_ACTION;
-import static android.slice.SliceItem.TYPE_COLOR;
-import static android.slice.SliceItem.TYPE_IMAGE;
-import static android.slice.SliceItem.TYPE_REMOTE_INPUT;
-import static android.slice.SliceItem.TYPE_REMOTE_VIEW;
-import static android.slice.SliceItem.TYPE_SLICE;
-import static android.slice.SliceItem.TYPE_TEXT;
-import static android.slice.SliceItem.TYPE_TIMESTAMP;
+package android.app.slice;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.ContentResolver;
+import android.content.IContentProvider;
import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.widget.RemoteViews;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* A slice is a piece of app content and actions that can be surfaced outside of the app.
*
* <p>They are constructed using {@link Builder} in a tree structure
* that provides the OS some information about how the content should be displayed.
- * @hide
*/
public final class Slice implements Parcelable {
@@ -53,7 +50,7 @@ public final class Slice implements Parcelable {
* @hide
*/
@StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
- HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT})
+ HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
public @interface SliceHint{ }
/**
@@ -102,6 +99,12 @@ public final class Slice implements Parcelable {
* Hint to indicate that this content should not be tinted.
*/
public static final String HINT_NO_TINT = "no_tint";
+ /**
+ * Hint to indicate that this slice is incomplete and an update will be sent once
+ * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
+ * OS and should not be cached by apps.
+ */
+ public static final String HINT_PARTIAL = "partial";
// These two are coming over from prototyping, but we probably don't want in
// public API, at least not right now.
@@ -109,19 +112,12 @@ public final class Slice implements Parcelable {
* @hide
*/
public static final String HINT_ALT = "alt";
- /**
- * @hide
- */
- public static final String HINT_PARTIAL = "partial";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
private Uri mUri;
- /**
- * @hide
- */
- public Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+ Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
mHints = hints;
mItems = items.toArray(new SliceItem[items.size()]);
mUri = uri;
@@ -147,15 +143,15 @@ public final class Slice implements Parcelable {
/**
* @return All child {@link SliceItem}s that this Slice contains.
*/
- public SliceItem[] getItems() {
- return mItems;
+ public List<SliceItem> getItems() {
+ return Arrays.asList(mItems);
}
/**
* @return All hints associated with this Slice.
*/
- public @SliceHint String[] getHints() {
- return mHints;
+ public @SliceHint List<String> getHints() {
+ return Arrays.asList(mHints);
}
/**
@@ -163,14 +159,14 @@ public final class Slice implements Parcelable {
*/
public SliceItem getPrimaryIcon() {
for (SliceItem item : getItems()) {
- if (item.getType() == TYPE_IMAGE) {
+ if (item.getType() == SliceItem.TYPE_IMAGE) {
return item;
}
- if (!(item.getType() == TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
&& !item.hasHint(Slice.HINT_ACTIONS)
&& !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, TYPE_IMAGE);
+ && (item.getType() != SliceItem.TYPE_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
if (icon != null) return icon;
}
}
@@ -235,10 +231,18 @@ public final class Slice implements Parcelable {
}
/**
+ * Add hints to the Slice being constructed
+ */
+ public Builder addHints(@SliceHint List<String> hints) {
+ return addHints(hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* Add a sub-slice to the slice being constructed
*/
public Builder addSubSlice(@NonNull Slice slice) {
- mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints()));
+ mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
+ new String[slice.getHints().size()])));
return this;
}
@@ -246,7 +250,7 @@ public final class Slice implements Parcelable {
* Add an action to the slice being constructed
*/
public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
- mItems.add(new SliceItem(action, s, TYPE_ACTION, new String[0]));
+ mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0]));
return this;
}
@@ -254,31 +258,53 @@ public final class Slice implements Parcelable {
* Add text to the slice being constructed
*/
public Builder addText(CharSequence text, @SliceHint String... hints) {
- mItems.add(new SliceItem(text, TYPE_TEXT, hints));
+ mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
return this;
}
/**
+ * Add text to the slice being constructed
+ */
+ public Builder addText(CharSequence text, @SliceHint List<String> hints) {
+ return addText(text, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* Add an image to the slice being constructed
*/
public Builder addIcon(Icon icon, @SliceHint String... hints) {
- mItems.add(new SliceItem(icon, TYPE_IMAGE, hints));
+ mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
return this;
}
/**
+ * Add an image to the slice being constructed
+ */
+ public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
+ return addIcon(icon, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* @hide This isn't final
*/
public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints));
+ mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
return this;
}
/**
* Add remote input to the slice being constructed
*/
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+ @SliceHint List<String> hints) {
+ return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * Add remote input to the slice being constructed
+ */
public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteInput, TYPE_REMOTE_INPUT, hints));
+ mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
return this;
}
@@ -286,19 +312,33 @@ public final class Slice implements Parcelable {
* Add a color to the slice being constructed
*/
public Builder addColor(int color, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, TYPE_COLOR, hints));
+ mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
return this;
}
/**
+ * Add a color to the slice being constructed
+ */
+ public Builder addColor(int color, @SliceHint List<String> hints) {
+ return addColor(color, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* Add a timestamp to the slice being constructed
*/
public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
- mItems.add(new SliceItem(time, TYPE_TIMESTAMP, hints));
+ mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
return this;
}
/**
+ * Add a timestamp to the slice being constructed
+ */
+ public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
+ return addTimestamp(time, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* Construct the slice.
*/
public Slice build() {
@@ -322,18 +362,18 @@ public final class Slice implements Parcelable {
* @hide
* @return A string representation of this slice.
*/
- public String getString() {
- return getString("");
+ public String toString() {
+ return toString("");
}
- private String getString(String indent) {
+ private String toString(String indent) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mItems.length; i++) {
sb.append(indent);
- if (mItems[i].getType() == TYPE_SLICE) {
+ if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
sb.append("slice:\n");
- sb.append(mItems[i].getSlice().getString(indent + " "));
- } else if (mItems[i].getType() == TYPE_TEXT) {
+ sb.append(mItems[i].getSlice().toString(indent + " "));
+ } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
sb.append("text: ");
sb.append(mItems[i].getText());
sb.append("\n");
@@ -344,4 +384,34 @@ public final class Slice implements Parcelable {
}
return sb.toString();
}
+
+ /**
+ * Turns a slice Uri into slice content.
+ *
+ * @param resolver ContentResolver to be used.
+ * @param uri The URI to a slice provider
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ */
+ public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) {
+ Preconditions.checkNotNull(uri, "uri");
+ IContentProvider provider = resolver.acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+ final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
+ null, extras);
+ Bundle.setDefusable(res, true);
+ return res.getParcelable(SliceProvider.EXTRA_SLICE);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ resolver.releaseProvider(provider);
+ }
+ }
}
diff --git a/android/slice/SliceItem.java b/android/app/slice/SliceItem.java
index 2827ab9d..6e69b051 100644
--- a/android/slice/SliceItem.java
+++ b/android/app/slice/SliceItem.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.slice;
+package android.app.slice;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -23,13 +23,15 @@ import android.app.RemoteInput;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
-import android.slice.Slice.SliceHint;
import android.text.TextUtils;
import android.util.Pair;
import android.widget.RemoteViews;
import com.android.internal.util.ArrayUtils;
+import java.util.Arrays;
+import java.util.List;
+
/**
* A SliceItem is a single unit in the tree structure of a {@link Slice}.
@@ -47,7 +49,6 @@ import com.android.internal.util.ArrayUtils;
* The hints that a {@link SliceItem} are a set of strings which annotate
* the content. The hints that are guaranteed to be understood by the system
* are defined on {@link Slice}.
- * @hide
*/
public final class SliceItem implements Parcelable {
@@ -97,14 +98,15 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- protected @SliceHint String[] mHints;
+ protected @Slice.SliceHint
+ String[] mHints;
private final int mType;
private final Object mObj;
/**
* @hide
*/
- public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) {
+ public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
mHints = hints;
mType = type;
mObj = obj;
@@ -113,7 +115,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) {
+ public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
this(new Pair<>(intent, slice), type, hints);
}
@@ -121,14 +123,14 @@ public final class SliceItem implements Parcelable {
* Gets all hints associated with this SliceItem.
* @return Array of hints.
*/
- public @NonNull @SliceHint String[] getHints() {
- return mHints;
+ public @NonNull @Slice.SliceHint List<String> getHints() {
+ return Arrays.asList(mHints);
}
/**
* @hide
*/
- public void addHint(@SliceHint String hint) {
+ public void addHint(@Slice.SliceHint String hint) {
mHints = ArrayUtils.appendElement(String.class, mHints, hint);
}
@@ -206,7 +208,7 @@ public final class SliceItem implements Parcelable {
* @param hint The hint to check for
* @return true if this item contains the given hint
*/
- public boolean hasHint(@SliceHint String hint) {
+ public boolean hasHint(@Slice.SliceHint String hint) {
return ArrayUtils.contains(mHints, hint);
}
@@ -234,7 +236,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public boolean hasHints(@SliceHint String[] hints) {
+ public boolean hasHints(@Slice.SliceHint String[] hints) {
if (hints == null) return true;
for (String hint : hints) {
if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
@@ -247,7 +249,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public boolean hasAnyHints(@SliceHint String[] hints) {
+ public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
if (hints == null) return false;
for (String hint : hints) {
if (ArrayUtils.contains(mHints, hint)) {
diff --git a/android/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 4e21371b..df87b455 100644
--- a/android/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.slice;
+package android.app.slice;
import android.Manifest.permission;
import android.content.ContentProvider;
@@ -26,6 +26,8 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
@@ -51,10 +53,15 @@ import java.util.concurrent.CountDownLatch;
* </pre>
*
* @see Slice
- * @hide
*/
public abstract class SliceProvider extends ContentProvider {
+ /**
+ * This is the Android platform's MIME type for a slice: URI
+ * containing a slice implemented through {@link SliceProvider}.
+ */
+ public static final String SLICE_TYPE = "vnd.android.slice";
+
private static final String TAG = "SliceProvider";
/**
* @hide
@@ -73,8 +80,18 @@ public abstract class SliceProvider extends ContentProvider {
/**
* Implemented to create a slice. Will be called on the main thread.
+ * <p>
+ * onBindSlice should return as quickly as possible so that the UI tied
+ * to this slice can be responsive. No network or other IO will be allowed
+ * during onBindSlice. Any loading that needs to be done should happen
+ * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+ * when the app is ready to provide the complete data in onBindSlice.
+ * <p>
+ *
* @see {@link Slice}.
+ * @see {@link Slice#HINT_PARTIAL}
*/
+ // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
public abstract Slice onBindSlice(Uri sliceUri);
@Override
@@ -120,11 +137,11 @@ public abstract class SliceProvider extends ContentProvider {
@Override
public final String getType(Uri uri) {
if (DEBUG) Log.d(TAG, "getType " + uri);
- return null;
+ return SLICE_TYPE;
}
@Override
- public final Bundle call(String method, String arg, Bundle extras) {
+ public Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
getContext().enforceCallingPermission(permission.BIND_SLICE,
"Slice binding requires the permission BIND_SLICE");
@@ -143,8 +160,17 @@ public abstract class SliceProvider extends ContentProvider {
CountDownLatch latch = new CountDownLatch(1);
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {
- output[0] = onBindSlice(sliceUri);
- latch.countDown();
+ ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyDeath()
+ .build());
+ output[0] = onBindSlice(sliceUri);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ latch.countDown();
+ }
});
try {
latch.await();
diff --git a/android/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
index d99b26a5..d1fe2c90 100644
--- a/android/slice/SliceQuery.java
+++ b/android/app/slice/SliceQuery.java
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-package android.slice;
+package android.app.slice;
-import static android.slice.SliceItem.TYPE_ACTION;
-import static android.slice.SliceItem.TYPE_SLICE;
-
-import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -114,7 +110,9 @@ public class SliceQuery {
* @hide
*/
public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
- return find(new SliceItem(s, TYPE_SLICE, s.getHints()), type, hints, nonHints);
+ List<String> h = s.getHints();
+ return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
+ hints, nonHints);
}
/**
@@ -140,8 +138,9 @@ public class SliceQuery {
@Override
public SliceItem next() {
SliceItem item = items.poll();
- if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) {
- items.addAll(Arrays.asList(item.getSlice().getItems()));
+ if (item.getType() == SliceItem.TYPE_SLICE
+ || item.getType() == SliceItem.TYPE_ACTION) {
+ items.addAll(item.getSlice().getItems());
}
return item;
}
diff --git a/android/slice/views/ActionRow.java b/android/app/slice/views/ActionRow.java
index 93e9c035..c7d99f7f 100644
--- a/android/slice/views/ActionRow.java
+++ b/android/app/slice/views/ActionRow.java
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewParent;
diff --git a/android/slice/views/GridView.java b/android/app/slice/views/GridView.java
index 18a90f7d..6f30c507 100644
--- a/android/slice/views/GridView.java
+++ b/android/app/slice/views/GridView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
import android.content.Context;
import android.graphics.Color;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.views.LargeSliceAdapter.SliceListView;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
@@ -38,7 +38,7 @@ import android.widget.TextView;
import com.android.internal.R;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.List;
/**
* @hide
@@ -76,10 +76,10 @@ public class GridView extends LinearLayout implements SliceListView {
removeAllViews();
int total = 1;
if (slice.getType() == SliceItem.TYPE_SLICE) {
- SliceItem[] items = slice.getSlice().getItems();
- total = items.length;
+ List<SliceItem> items = slice.getSlice().getItems();
+ total = items.size();
for (int i = 0; i < total; i++) {
- SliceItem item = items[i];
+ SliceItem item = items.get(i);
if (isFull()) {
continue;
}
@@ -142,7 +142,7 @@ public class GridView extends LinearLayout implements SliceListView {
// TODO: Unify sporadic inflates that happen throughout the code.
ArrayList<SliceItem> items = new ArrayList<>();
if (item.getType() == SliceItem.TYPE_SLICE) {
- items.addAll(Arrays.asList(item.getSlice().getItems()));
+ items.addAll(item.getSlice().getItems());
}
items.forEach(i -> {
Context context = getContext();
diff --git a/android/slice/views/LargeSliceAdapter.java b/android/app/slice/views/LargeSliceAdapter.java
index e77a1b2a..6794ff98 100644
--- a/android/slice/views/LargeSliceAdapter.java
+++ b/android/app/slice/views/LargeSliceAdapter.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceViewHolder;
import android.content.Context;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.LargeSliceAdapter.SliceViewHolder;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/android/slice/views/LargeTemplateView.java b/android/app/slice/views/LargeTemplateView.java
index d53e8fcb..9e225162 100644
--- a/android/slice/views/LargeTemplateView.java
+++ b/android/app/slice/views/LargeTemplateView.java
@@ -14,22 +14,21 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.SliceView.SliceModeView;
import android.content.Context;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.SliceView.SliceModeView;
import android.util.TypedValue;
import com.android.internal.widget.LinearLayoutManager;
import com.android.internal.widget.RecyclerView;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -86,7 +85,7 @@ public class LargeTemplateView extends SliceModeView {
if (slice.hasHint(Slice.HINT_LIST)) {
addList(slice, items);
} else {
- Arrays.asList(slice.getItems()).forEach(item -> {
+ slice.getItems().forEach(item -> {
if (item.hasHint(Slice.HINT_ACTIONS)) {
return;
} else if (item.getType() == SliceItem.TYPE_COLOR) {
@@ -109,7 +108,7 @@ public class LargeTemplateView extends SliceModeView {
}
private void addList(Slice slice, List<SliceItem> items) {
- List<SliceItem> sliceItems = Arrays.asList(slice.getItems());
+ List<SliceItem> sliceItems = slice.getItems();
sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
items.addAll(sliceItems);
}
diff --git a/android/slice/views/MessageView.java b/android/app/slice/views/MessageView.java
index 7b03e0bd..77252bf2 100644
--- a/android/slice/views/MessageView.java
+++ b/android/app/slice/views/MessageView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.LargeSliceAdapter.SliceListView;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import android.util.TypedValue;
diff --git a/android/slice/views/RemoteInputView.java b/android/app/slice/views/RemoteInputView.java
index a29bb5c0..e53cb1ea 100644
--- a/android/slice/views/RemoteInputView.java
+++ b/android/app/slice/views/RemoteInputView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.animation.Animator;
import android.app.Notification;
diff --git a/android/slice/views/ShortcutView.java b/android/app/slice/views/ShortcutView.java
index 8fe2f1ac..b6790c7d 100644
--- a/android/slice/views/ShortcutView.java
+++ b/android/app/slice/views/ShortcutView.java
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.SliceView.SliceModeView;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.net.Uri;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.SliceView.SliceModeView;
import android.view.ViewGroup;
import com.android.internal.R;
diff --git a/android/slice/views/SliceView.java b/android/app/slice/views/SliceView.java
index f3792481..32484fca 100644
--- a/android/slice/views/SliceView.java
+++ b/android/app/slice/views/SliceView.java
@@ -14,23 +14,25 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.annotation.StringDef;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import java.util.List;
+
/**
* A view that can display a {@link Slice} in different {@link SliceMode}'s.
*
@@ -120,7 +122,7 @@ public class SliceView extends LinearLayout {
*/
public void bindSlice(Uri sliceUri) {
validate(sliceUri);
- Slice s = mContext.getContentResolver().bindSlice(sliceUri);
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
bindSlice(s);
}
@@ -201,7 +203,7 @@ public class SliceView extends LinearLayout {
}
// TODO: Smarter mapping here from one state to the next.
SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- SliceItem[] items = mCurrentSlice.getItems();
+ List<SliceItem> items = mCurrentSlice.getItems();
SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
Slice.HINT_ACTIONS,
Slice.HINT_ALT);
@@ -212,7 +214,7 @@ public class SliceView extends LinearLayout {
addView(mCurrentView);
addView(mActions);
}
- if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) {
+ if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
mCurrentView.setVisibility(View.VISIBLE);
mCurrentView.setSlice(mCurrentSlice);
} else {
@@ -239,7 +241,7 @@ public class SliceView extends LinearLayout {
}
private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
throw new RuntimeException("Invalid uri " + sliceUri);
}
if (sliceUri.getPathSegments().size() == 0) {
diff --git a/android/slice/views/SliceViewUtil.java b/android/app/slice/views/SliceViewUtil.java
index 1b5a6d1e..19e8e7c9 100644
--- a/android/slice/views/SliceViewUtil.java
+++ b/android/app/slice/views/SliceViewUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.annotation.ColorInt;
import android.content.Context;
diff --git a/android/slice/views/SmallTemplateView.java b/android/app/slice/views/SmallTemplateView.java
index b0b181ed..42b2d213 100644
--- a/android/slice/views/SmallTemplateView.java
+++ b/android/app/slice/views/SmallTemplateView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.views.SliceView.SliceModeView;
import android.content.Context;
import android.os.AsyncTask;
-import android.slice.Slice;
-import android.slice.SliceItem;
-import android.slice.SliceQuery;
-import android.slice.views.LargeSliceAdapter.SliceListView;
-import android.slice.views.SliceView.SliceModeView;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -34,7 +34,6 @@ import com.android.internal.R;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -117,7 +116,7 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
int itemCount = 0;
boolean hasSummary = false;
ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
- Arrays.asList(slice.getSlice().getItems()));
+ slice.getSlice().getItems());
for (int i = 0; i < sliceItems.size(); i++) {
SliceItem item = sliceItems.get(i);
if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
@@ -140,9 +139,9 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
mEndContainer.addView(tv);
itemCount++;
} else if (item.getType() == SliceItem.TYPE_SLICE) {
- SliceItem[] subItems = item.getSlice().getItems();
- for (int j = 0; j < subItems.length; j++) {
- sliceItems.add(subItems[j]);
+ List<SliceItem> subItems = item.getSlice().getItems();
+ for (int j = 0; j < subItems.size(); j++) {
+ sliceItems.add(subItems.get(j));
}
}
}
@@ -151,7 +150,8 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
@Override
public void setSlice(Slice slice) {
- setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints()));
+ setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
+ slice.getHints().toArray(new String[slice.getHints().size()])));
}
/**
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index 051dccbd..fd579fce 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -48,10 +48,10 @@ import java.util.Map;
* </pre>
* A request for data in the middle of a time interval will include that interval.
* <p/>
- * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS, which
- * is a system-level permission and will not be granted to third-party apps. However, declaring
- * the permission implies intention to use the API and the user of the device can grant permission
- * through the Settings application.
+ * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS.
+ * However, declaring the permission implies intention to use the API and the user of the device
+ * still needs to grant permission through the Settings application.
+ * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}
*/
@SystemService(Context.USAGE_STATS_SERVICE)
public final class UsageStatsManager {
@@ -122,7 +122,7 @@ public final class UsageStatsManager {
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A list of {@link UsageStats} or null if none are available.
+ * @return A list of {@link UsageStats}
*
* @see #INTERVAL_DAILY
* @see #INTERVAL_WEEKLY
@@ -139,7 +139,7 @@ public final class UsageStatsManager {
return slice.getList();
}
} catch (RemoteException e) {
- // fallthrough and return null.
+ // fallthrough and return the empty list.
}
return Collections.emptyList();
}
@@ -152,7 +152,7 @@ public final class UsageStatsManager {
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A list of {@link ConfigurationStats} or null if none are available.
+ * @return A list of {@link ConfigurationStats}
*/
public List<ConfigurationStats> queryConfigurations(int intervalType, long beginTime,
long endTime) {
@@ -185,7 +185,7 @@ public final class UsageStatsManager {
return iter;
}
} catch (RemoteException e) {
- // fallthrough and return null
+ // fallthrough and return empty result.
}
return sEmptyResults;
}
@@ -197,8 +197,7 @@ public final class UsageStatsManager {
*
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A {@link java.util.Map} keyed by package name, or null if no stats are
- * available.
+ * @return A {@link java.util.Map} keyed by package name
*/
public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
diff --git a/android/appwidget/AppWidgetManager.java b/android/appwidget/AppWidgetManager.java
index 969b19ee..37bb6b05 100644
--- a/android/appwidget/AppWidgetManager.java
+++ b/android/appwidget/AppWidgetManager.java
@@ -20,17 +20,19 @@ import android.annotation.BroadcastBehavior;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
-import android.annotation.SystemService;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.app.IServiceConnection;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -1051,43 +1053,23 @@ public class AppWidgetManager {
* The appWidgetId specified must already be bound to the calling AppWidgetHost via
* {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
*
- * @param packageName The package from which the binding is requested.
* @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
* @param intent The intent of the service which will be providing the data to the
* RemoteViewsAdapter.
* @param connection The callback interface to be notified when a connection is made or lost.
- * @hide
- */
- public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent,
- IBinder connection) {
- if (mService == null) {
- return;
- }
- try {
- mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Unbinds the RemoteViewsService for a given appWidgetId and intent.
- *
- * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
- * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
+ * @param flags Flags used for binding to the service
*
- * @param packageName The package from which the binding is requested.
- * @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
- * @param intent The intent of the service which will be providing the data to the
- * RemoteViewsAdapter.
+ * @see Context#getServiceDispatcher(ServiceConnection, Handler, int)
* @hide
*/
- public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) {
+ public boolean bindRemoteViewsService(Context context, int appWidgetId, Intent intent,
+ IServiceConnection connection, @Context.BindServiceFlags int flags) {
if (mService == null) {
- return;
+ return false;
}
try {
- mService.unbindRemoteViewsService(packageName, appWidgetId, intent);
+ return mService.bindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent,
+ context.getIApplicationThread(), context.getActivityToken(), connection, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/arch/lifecycle/ActivityFullLifecycleTest.java b/android/arch/lifecycle/ActivityFullLifecycleTest.java
index ee4e661a..78dd0150 100644
--- a/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ b/android/arch/lifecycle/ActivityFullLifecycleTest.java
@@ -16,48 +16,43 @@
package android.arch.lifecycle;
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.CollectingActivity;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
-import android.arch.lifecycle.testapp.FullLifecycleTestActivity;
-import android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity;
import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
-import android.util.Pair;
+import android.support.v4.util.Pair;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(Parameterized.class)
public class ActivityFullLifecycleTest {
@Rule
- public ActivityTestRule activityTestRule =
- new ActivityTestRule<>(FullLifecycleTestActivity.class);
+ public final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule;
@Parameterized.Parameters
public static Class[] params() {
- return new Class[]{FullLifecycleTestActivity.class,
- SupportLifecycleRegistryActivity.class,
+ return new Class[]{CollectingSupportActivity.class,
FrameworkLifecycleRegistryActivity.class};
}
@@ -68,28 +63,13 @@ public class ActivityFullLifecycleTest {
@Test
- public void testFullLifecycle() throws InterruptedException {
- Activity activity = activityTestRule.getActivity();
- List<Pair<TestEvent, Event>> results = ((CollectingActivity) activity)
- .waitForCollectedEvents();
+ public void testFullLifecycle() throws Throwable {
+ CollectingLifecycleOwner owner = activityTestRule.getActivity();
+ TestUtils.waitTillResumed(owner, activityTestRule);
+ activityTestRule.finishActivity();
- Event[] expectedEvents =
- new Event[]{ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
-
- List<Pair<TestEvent, Event>> expected = new ArrayList<>();
- boolean beforeResume = true;
- for (Event i : expectedEvents) {
- if (beforeResume) {
- expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
- expected.add(new Pair<>(LIFECYCLE_EVENT, i));
- } else {
- expected.add(new Pair<>(LIFECYCLE_EVENT, i));
- expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
- }
- if (i == ON_RESUME) {
- beforeResume = false;
- }
- }
- assertThat(results, is(expected));
+ TestUtils.waitTillDestroyed(owner, activityTestRule);
+ List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents();
+ assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
}
}
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 2c7e1739..106b2ef0 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -16,7 +16,9 @@
package android.arch.lifecycle;
+import android.annotation.SuppressLint;
import android.app.Application;
+import android.support.annotation.NonNull;
/**
* Application context aware {@link ViewModel}.
@@ -25,16 +27,19 @@ import android.app.Application;
* <p>
*/
public class AndroidViewModel extends ViewModel {
+ @SuppressLint("StaticFieldLeak")
private Application mApplication;
- public AndroidViewModel(Application application) {
+ public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
+ @NonNull
public <T extends Application> T getApplication() {
+ //noinspection unchecked
return (T) mApplication;
}
}
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
index f077daed..d88e2762 100644
--- a/android/arch/lifecycle/ClassesInfoCache.java
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -46,7 +46,7 @@ class ClassesInfoCache {
return mHasLifecycleMethods.get(klass);
}
- Method[] methods = klass.getDeclaredMethods();
+ Method[] methods = getDeclaredMethods(klass);
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation != null) {
@@ -64,6 +64,18 @@ class ClassesInfoCache {
return false;
}
+ private Method[] getDeclaredMethods(Class klass) {
+ try {
+ return klass.getDeclaredMethods();
+ } catch (NoClassDefFoundError e) {
+ throw new IllegalArgumentException("The observer class has some methods that use "
+ + "newer APIs which are not available in the current OS version. Lifecycles "
+ + "cannot access even other methods so you should make sure that your "
+ + "observer classes only access framework classes that are available "
+ + "in your min API level OR use lifecycle:compiler annotation processor.", e);
+ }
+ }
+
CallbackInfo getInfo(Class klass) {
CallbackInfo existing = mCallbackMap.get(klass);
if (existing != null) {
@@ -106,7 +118,7 @@ class ClassesInfoCache {
}
}
- Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+ Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
boolean hasLifecycleMethods = false;
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index 02db5ff9..c0a2090c 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -17,6 +17,7 @@
package android.arch.lifecycle;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
/**
* Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment}
@@ -83,7 +84,7 @@ public abstract class Lifecycle {
* @param observer The observer to notify.
*/
@MainThread
- public abstract void addObserver(LifecycleObserver observer);
+ public abstract void addObserver(@NonNull LifecycleObserver observer);
/**
* Removes the given observer from the observers list.
@@ -99,7 +100,7 @@ public abstract class Lifecycle {
* @param observer The observer to be removed.
*/
@MainThread
- public abstract void removeObserver(LifecycleObserver observer);
+ public abstract void removeObserver(@NonNull LifecycleObserver observer);
/**
* Returns the current state of the Lifecycle.
@@ -193,7 +194,7 @@ public abstract class Lifecycle {
* @param state State to compare with
* @return true if this State is greater or equal to the given {@code state}
*/
- public boolean isAtLeast(State state) {
+ public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
diff --git a/android/arch/lifecycle/LifecycleOwner.java b/android/arch/lifecycle/LifecycleOwner.java
index 934cf3a2..068bac1b 100644
--- a/android/arch/lifecycle/LifecycleOwner.java
+++ b/android/arch/lifecycle/LifecycleOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* A class that has an Android lifecycle. These events can be used by custom components to
* handle lifecycle changes without implementing any code inside the Activity or the Fragment.
@@ -29,5 +31,6 @@ public interface LifecycleOwner {
*
* @return The lifecycle of the provider.
*/
+ @NonNull
Lifecycle getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistry.java b/android/arch/lifecycle/LifecycleRegistry.java
index b83e6b8a..bf8aff79 100644
--- a/android/arch/lifecycle/LifecycleRegistry.java
+++ b/android/arch/lifecycle/LifecycleRegistry.java
@@ -29,9 +29,12 @@ import static android.arch.lifecycle.Lifecycle.State.RESUMED;
import static android.arch.lifecycle.Lifecycle.State.STARTED;
import android.arch.core.internal.FastSafeIterableMap;
+import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.util.Log;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map.Entry;
@@ -44,6 +47,8 @@ import java.util.Map.Entry;
*/
public class LifecycleRegistry extends Lifecycle {
+ private static final String LOG_TAG = "LifecycleRegistry";
+
/**
* Custom list that keeps observers and can handle removals / additions during traversal.
*
@@ -59,8 +64,12 @@ public class LifecycleRegistry extends Lifecycle {
private State mState;
/**
* The provider that owns this Lifecycle.
+ * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
+ * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
+ * because it keeps strong references on all other listeners, so you'll leak all of them as
+ * well.
*/
- private final LifecycleOwner mLifecycleOwner;
+ private final WeakReference<LifecycleOwner> mLifecycleOwner;
private int mAddingObserverCounter = 0;
@@ -86,19 +95,19 @@ public class LifecycleRegistry extends Lifecycle {
* @param provider The owner LifecycleOwner
*/
public LifecycleRegistry(@NonNull LifecycleOwner provider) {
- mLifecycleOwner = provider;
+ mLifecycleOwner = new WeakReference<>(provider);
mState = INITIALIZED;
}
/**
- * Only marks the current state as the given value. It doesn't dispatch any event to its
- * listeners.
+ * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
*
* @param state new state
*/
@SuppressWarnings("WeakerAccess")
- public void markState(State state) {
- mState = state;
+ @MainThread
+ public void markState(@NonNull State state) {
+ moveToState(state);
}
/**
@@ -109,8 +118,16 @@ public class LifecycleRegistry extends Lifecycle {
*
* @param event The event that was received
*/
- public void handleLifecycleEvent(Lifecycle.Event event) {
- mState = getStateAfter(event);
+ public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
+ State next = getStateAfter(event);
+ moveToState(next);
+ }
+
+ private void moveToState(State next) {
+ if (mState == next) {
+ return;
+ }
+ mState = next;
if (mHandlingEvent || mAddingObserverCounter != 0) {
mNewEventOccurred = true;
// we will figure out what to do on upper level.
@@ -140,7 +157,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void addObserver(LifecycleObserver observer) {
+ public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
@@ -148,15 +165,19 @@ public class LifecycleRegistry extends Lifecycle {
if (previous != null) {
return;
}
+ LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+ if (lifecycleOwner == null) {
+ // it is null we should be destroyed. Fallback quickly
+ return;
+ }
boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
-
State targetState = calculateTargetState(observer);
mAddingObserverCounter++;
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
- statefulObserver.dispatchEvent(mLifecycleOwner, upEvent(statefulObserver.mState));
+ statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
@@ -178,7 +199,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void removeObserver(LifecycleObserver observer) {
+ public void removeObserver(@NonNull LifecycleObserver observer) {
// we consciously decided not to send destruction events here in opposition to addObserver.
// Our reasons for that:
// 1. These events haven't yet happened at all. In contrast to events in addObservers, that
@@ -258,7 +279,7 @@ public class LifecycleRegistry extends Lifecycle {
throw new IllegalArgumentException("Unexpected state value " + state);
}
- private void forwardPass() {
+ private void forwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
mObserverMap.iteratorWithAdditions();
while (ascendingIterator.hasNext() && !mNewEventOccurred) {
@@ -267,13 +288,13 @@ public class LifecycleRegistry extends Lifecycle {
while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
&& mObserverMap.contains(entry.getKey()))) {
pushParentState(observer.mState);
- observer.dispatchEvent(mLifecycleOwner, upEvent(observer.mState));
+ observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
popParentState();
}
}
}
- private void backwardPass() {
+ private void backwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
mObserverMap.descendingIterator();
while (descendingIterator.hasNext() && !mNewEventOccurred) {
@@ -283,7 +304,7 @@ public class LifecycleRegistry extends Lifecycle {
&& mObserverMap.contains(entry.getKey()))) {
Event event = downEvent(observer.mState);
pushParentState(getStateAfter(event));
- observer.dispatchEvent(mLifecycleOwner, event);
+ observer.dispatchEvent(lifecycleOwner, event);
popParentState();
}
}
@@ -292,16 +313,22 @@ public class LifecycleRegistry extends Lifecycle {
// happens only on the top of stack (never in reentrance),
// so it doesn't have to take in account parents
private void sync() {
+ LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+ if (lifecycleOwner == null) {
+ Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
+ + "new events from it.");
+ return;
+ }
while (!isSynced()) {
mNewEventOccurred = false;
// no need to check eldest for nullability, because isSynced does it for us.
if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
- backwardPass();
+ backwardPass(lifecycleOwner);
}
Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
if (!mNewEventOccurred && newest != null
&& mState.compareTo(newest.getValue().mState) > 0) {
- forwardPass();
+ forwardPass(lifecycleOwner);
}
}
mNewEventOccurred = false;
diff --git a/android/arch/lifecycle/LifecycleRegistryOwner.java b/android/arch/lifecycle/LifecycleRegistryOwner.java
index 38eeb6d3..0c67fefe 100644
--- a/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ b/android/arch/lifecycle/LifecycleRegistryOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* @deprecated Use {@code android.support.v7.app.AppCompatActivity}
* which extends {@link LifecycleOwner}, so there are no use cases for this class.
@@ -23,6 +25,7 @@ package android.arch.lifecycle;
@SuppressWarnings({"WeakerAccess", "unused"})
@Deprecated
public interface LifecycleRegistryOwner extends LifecycleOwner {
+ @NonNull
@Override
LifecycleRegistry getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistryTest.java b/android/arch/lifecycle/LifecycleRegistryTest.java
index 6506454d..2a7bbad2 100644
--- a/android/arch/lifecycle/LifecycleRegistryTest.java
+++ b/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -566,6 +566,25 @@ public class LifecycleRegistryTest {
verify(observer).onCreate();
}
+ private static void forceGc() {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ }
+
+ @Test
+ public void goneLifecycleOwner() {
+ fullyInitializeRegistry();
+ mLifecycleOwner = null;
+ forceGc();
+ TestObserver observer = mock(TestObserver.class);
+ mRegistry.addObserver(observer);
+ verify(observer, never()).onCreate();
+ verify(observer, never()).onStart();
+ verify(observer, never()).onResume();
+ }
+
private void dispatchEvent(Lifecycle.Event event) {
when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
mRegistry.handleLifecycleEvent(event);
diff --git a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
new file mode 100644
index 00000000..836cfff0
--- /dev/null
+++ b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.FragmentActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LiveDataOnSaveInstanceStateTest {
+ @Rule
+ public ActivityTestRule<CollectingSupportActivity> mActivityTestRule =
+ new ActivityTestRule<>(CollectingSupportActivity.class);
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> {
+ activity.replaceFragment(fragment);
+ fragment.replaceFragment(fragment2);
+ });
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> {
+ activity.replaceFragment(fragment);
+ fragment.replaceFragment(fragment2);
+ });
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
+ }
+
+ private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
+ throws Throwable {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+ final FragmentActivity dialogActivity = launchDialog();
+
+ TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule);
+
+ // Change the LiveData value and assert that the observer is not called given that the
+ // lifecycle is in the CREATED state.
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+ assertThat(atomicInteger.get(), is(0));
+
+ // Finish the dialog Activity, wait for the main activity to be resumed, and assert that
+ // the observer's onChanged method is called.
+ mActivityTestRule.runOnUiThread(dialogActivity::finish);
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+ assertThat(atomicInteger.get(), is(1));
+ }
+
+ private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
+ throws Throwable {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+ // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
+ // lifecycleOwner to hit onPause (or enter the STARTED state). On API 24 and above, this
+ // onPause should be the last lifecycle method called (and the STARTED state should be the
+ // final resting state).
+ launchDialog();
+ TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
+
+ // Change the LiveData's value and verify that the observer's onChanged method is called
+ // since we are in the STARTED state.
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+ assertThat(atomicInteger.get(), is(1));
+ }
+
+ private FragmentActivity launchDialog() throws Throwable {
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NavigationDialogActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ FragmentActivity activity = mActivityTestRule.getActivity();
+ // helps with less flaky API 16 tests
+ Intent intent = new Intent(activity, NavigationDialogActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ activity.startActivity(intent);
+ FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+ TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule);
+ return fragmentActivity;
+ }
+}
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 9f0b4257..647d5d7a 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -53,18 +53,29 @@ import org.mockito.Mockito;
public class LiveDataTest {
private PublicLiveData<String> mLiveData;
private LifecycleOwner mOwner;
+ private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry;
+ private LifecycleRegistry mRegistry2;
private MethodExec mActiveObserversChanged;
private boolean mInObserver;
@Before
public void init() {
mLiveData = new PublicLiveData<>();
+
+ mActiveObserversChanged = mock(MethodExec.class);
+ mLiveData.activeObserversChanged = mActiveObserversChanged;
+
mOwner = mock(LifecycleOwner.class);
+
mRegistry = new LifecycleRegistry(mOwner);
when(mOwner.getLifecycle()).thenReturn(mRegistry);
- mActiveObserversChanged = mock(MethodExec.class);
- mLiveData.activeObserversChanged = mActiveObserversChanged;
+
+ mOwner2 = mock(LifecycleOwner.class);
+
+ mRegistry2 = new LifecycleRegistry(mOwner2);
+ when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
+
mInObserver = false;
}
@@ -159,14 +170,11 @@ public class LiveDataTest {
@Test
public void testAddSameObserverIn2LifecycleOwners() {
Observer<String> observer = (Observer<String>) mock(Observer.class);
- LifecycleOwner owner2 = mock(LifecycleOwner.class);
- LifecycleRegistry registry2 = new LifecycleRegistry(owner2);
- when(owner2.getLifecycle()).thenReturn(registry2);
mLiveData.observe(mOwner, observer);
Throwable throwable = null;
try {
- mLiveData.observe(owner2, observer);
+ mLiveData.observe(mOwner2, observer);
} catch (Throwable t) {
throwable = t;
}
@@ -456,6 +464,210 @@ public class LiveDataTest {
inOrder.verifyNoMoreInteractions();
}
+ @Test
+ public void setValue_neverActive_observerOnChangedNotCalled() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ mLiveData.setValue("1");
+
+ verify(observer, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer1).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+ }
+
+ @Test
+ public void setValue_twoObserversOneStopped_liveDataStaysActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ }
+
+ /**
+ * Verifies that if a lifecycle's state changes without an event, and changes to something that
+ * LiveData would become inactive in response to, LiveData will detect the change upon new data
+ * being set and become inactive. Also verifies that once the lifecycle enters into a state
+ * that LiveData should become active to, that it does indeed become active.
+ */
+ @Test
+ public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ // Marking state as CREATED should call onInactive.
+ reset(mActiveObserversChanged);
+ mRegistry.markState(Lifecycle.State.CREATED);
+ verify(mActiveObserversChanged).onCall(false);
+ reset(mActiveObserversChanged);
+
+ // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
+ // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
+ // and the Observer shouldn't be affected.
+ mLiveData.setValue("1");
+
+ // state is already CREATED so should not call again
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer, never()).onChanged(anyString());
+
+ // Sanity check. Because we've only marked the state as CREATED, sending ON_START
+ // should re-dispatch events.
+ reset(mActiveObserversChanged);
+ reset(observer);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ verify(mActiveObserversChanged).onCall(true);
+ verify(observer).onChanged("1");
+ }
+
+ /**
+ * This test verifies that LiveData will detect changes in LifecycleState that would make it
+ * inactive upon the setting of new data, but only if all of the Lifecycles it's observing
+ * are all in those states. It also makes sure that once it is inactive, that it will become
+ * active again once one of the lifecycles it's observing moves to an appropriate state.
+ */
+ @Test
+ public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ // Marking the state to created won't change LiveData to be inactive.
+ reset(mActiveObserversChanged);
+ mRegistry.markState(Lifecycle.State.CREATED);
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+ // After setting a value, the LiveData will stay active because there is still a STARTED
+ // lifecycle being observed. The one Observer associated with the STARTED lifecycle will
+ // also have been called, but the other Observer will not have been called.
+ reset(observer1);
+ reset(observer2);
+ mLiveData.setValue("1");
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2).onChanged("1");
+
+ // Now we set the other Lifecycle to be inactive, live data should become inactive.
+ reset(observer1);
+ reset(observer2);
+ mRegistry2.markState(Lifecycle.State.CREATED);
+ verify(mActiveObserversChanged).onCall(false);
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2, never()).onChanged(anyString());
+
+ // Now we post another value, because both lifecycles are in the Created state, live data
+ // will not dispatch any values
+ reset(mActiveObserversChanged);
+ mLiveData.setValue("2");
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2, never()).onChanged(anyString());
+
+ // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
+ // be made active and it's associated Observer will be called with the new value, but the
+ // Observer associated with the Lifecycle that is still in the Created state won't be
+ // called.
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ verify(mActiveObserversChanged).onCall(true);
+ verify(observer1).onChanged("2");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
@SuppressWarnings("WeakerAccess")
static class PublicLiveData<T> extends LiveData<T> {
// cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveData.java b/android/arch/lifecycle/MediatorLiveData.java
index 672b3a3b..58647394 100644
--- a/android/arch/lifecycle/MediatorLiveData.java
+++ b/android/arch/lifecycle/MediatorLiveData.java
@@ -19,16 +19,49 @@ package android.arch.lifecycle;
import android.arch.core.internal.SafeIterableMap;
import android.support.annotation.CallSuper;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Map;
/**
- * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on
+ * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
* {@code OnChanged} events from them.
* <p>
* This class correctly propagates its active/inactive states down to source {@code LiveData}
* objects.
+ * <p>
+ * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
+ * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
+ * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
+ * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
+ * is called for either of them, we set a new value in {@code liveDataMerger}.
+ *
+ * <pre>
+ * LiveData<Integer> liveData1 = ...;
+ * LiveData<Integer> liveData2 = ...;
+ *
+ * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
+ * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
+ * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
+ * </pre>
+ * <p>
+ * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
+ * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
+ * liveData1} and remove it as a source.
+ * <pre>
+ * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
+ * private int count = 1;
+ *
+ * {@literal @}Override public void onChanged(@Nullable Integer s) {
+ * count++;
+ * liveDataMerger.setValue(s);
+ * if (count > 10) {
+ * liveDataMerger.removeSource(liveData1);
+ * }
+ * }
+ * });
+ * </pre>
*
* @param <T> The type of data hold by this instance
*/
@@ -49,7 +82,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> The type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void addSource(LiveData<S> source, Observer<S> onChanged) {
+ public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
Source<S> e = new Source<>(source, onChanged);
Source<?> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
@@ -71,7 +104,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> the type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void removeSource(LiveData<S> toRemote) {
+ public <S> void removeSource(@NonNull LiveData<S> toRemote) {
Source<?> source = mSources.remove(toRemote);
if (source != null) {
source.unplug();
diff --git a/android/arch/lifecycle/MissingClassTest.java b/android/arch/lifecycle/MissingClassTest.java
new file mode 100644
index 00000000..81a07564
--- /dev/null
+++ b/android/arch/lifecycle/MissingClassTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.PictureInPictureParams;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@SmallTest
+public class MissingClassTest {
+ public static class ObserverWithMissingClasses {
+ @SuppressWarnings("unused")
+ public void newApiMethod(PictureInPictureParams params) {}
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {}
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMissingApi() {
+ new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
+ }
+}
diff --git a/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
new file mode 100644
index 00000000..07a9dc5a
--- /dev/null
+++ b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.arch.lifecycle.testapp.TestEvent;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Runs tests about the state when an activity is partially covered by another activity. Pre
+ * API 24, framework behavior changes so the test rely on whether state is saved or not and makes
+ * assertions accordingly.
+ */
+@SuppressWarnings("unchecked")
+@RunWith(Parameterized.class)
+@LargeTest
+public class PartiallyCoveredActivityTest {
+ private static final List[] IF_SAVED = new List[]{
+ // when overlaid
+ flatMap(CREATE, START, RESUME, PAUSE,
+ singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))),
+ // post dialog dismiss
+ asList(new Pair<>(OWNER_CALLBACK, ON_RESUME),
+ new Pair<>(LIFECYCLE_EVENT, ON_START),
+ new Pair<>(LIFECYCLE_EVENT, ON_RESUME)),
+ // post finish
+ flatMap(PAUSE, STOP, DESTROY)};
+
+ private static final List[] IF_NOT_SAVED = new List[]{
+ // when overlaid
+ flatMap(CREATE, START, RESUME, PAUSE),
+ // post dialog dismiss
+ flatMap(RESUME),
+ // post finish
+ flatMap(PAUSE, STOP, DESTROY)};
+
+ private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;
+ private static final List<Pair<TestEvent, Lifecycle.Event>>[] EXPECTED =
+ sShouldSave ? IF_SAVED : IF_NOT_SAVED;
+
+ @Rule
+ public ActivityTestRule<CollectingSupportActivity> activityRule =
+ new ActivityTestRule<CollectingSupportActivity>(
+ CollectingSupportActivity.class) {
+ @Override
+ protected Intent getActivityIntent() {
+ // helps with less flaky API 16 tests
+ Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
+ CollectingSupportActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return intent;
+ }
+ };
+ private final boolean mDismissDialog;
+
+ @Parameterized.Parameters(name = "dismissDialog_{0}")
+ public static List<Boolean> dismissDialog() {
+ return asList(true, false);
+ }
+
+ public PartiallyCoveredActivityTest(boolean dismissDialog) {
+ mDismissDialog = dismissDialog;
+ }
+
+ @Test
+ public void coveredWithDialog_activity() throws Throwable {
+ final CollectingSupportActivity activity = activityRule.getActivity();
+ runTest(activity);
+ }
+
+ @Test
+ public void coveredWithDialog_fragment() throws Throwable {
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment));
+ runTest(fragment);
+ }
+
+ @Test
+ public void coveredWithDialog_childFragment() throws Throwable {
+ CollectingSupportFragment parentFragment = new CollectingSupportFragment();
+ CollectingSupportFragment childFragment = new CollectingSupportFragment();
+ activityRule.runOnUiThread(() -> {
+ activityRule.getActivity().replaceFragment(parentFragment);
+ parentFragment.replaceFragment(childFragment);
+ });
+ runTest(childFragment);
+ }
+
+ private void runTest(CollectingLifecycleOwner owner) throws Throwable {
+ TestUtils.waitTillResumed(owner, activityRule);
+ FragmentActivity dialog = launchDialog();
+ assertStateSaving();
+ waitForIdle();
+ assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
+ List<Pair<TestEvent, Lifecycle.Event>> expected;
+ if (mDismissDialog) {
+ dialog.finish();
+ TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
+ assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
+ expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
+ } else {
+ expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
+ }
+ CollectingSupportActivity activity = activityRule.getActivity();
+ activityRule.finishActivity();
+ TestUtils.waitTillDestroyed(activity, activityRule);
+ assertThat(owner.copyCollectedEvents(), is(expected));
+ }
+
+ // test sanity
+ private void assertStateSaving() throws ExecutionException, InterruptedException {
+ final CollectingSupportActivity activity = activityRule.getActivity();
+ if (sShouldSave) {
+ // state should be saved. wait for it to be saved
+ assertThat("test sanity",
+ activity.waitForStateSave(20), is(true));
+ assertThat("test sanity", activity.getSupportFragmentManager()
+ .isStateSaved(), is(true));
+ } else {
+ // should should not be saved
+ assertThat("test sanity", activity.getSupportFragmentManager()
+ .isStateSaved(), is(false));
+ }
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private FragmentActivity launchDialog() throws Throwable {
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NavigationDialogActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ FragmentActivity activity = activityRule.getActivity();
+
+ Intent intent = new Intent(activity, NavigationDialogActivity.class);
+ // disabling animations helps with less flaky API 16 tests
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ activity.startActivity(intent);
+ FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+ TestUtils.waitTillResumed(fragmentActivity, activityRule);
+ return fragmentActivity;
+ }
+}
diff --git a/android/arch/lifecycle/ProcessLifecycleOwner.java b/android/arch/lifecycle/ProcessLifecycleOwner.java
index e2a12563..74ea97f7 100644
--- a/android/arch/lifecycle/ProcessLifecycleOwner.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwner.java
@@ -22,6 +22,7 @@ import android.arch.lifecycle.ReportFragment.ActivityInitializationListener;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
+import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
/**
@@ -156,7 +157,7 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- ReportFragment .get(activity).setProcessListener(mInitializationListener);
+ ReportFragment.get(activity).setProcessListener(mInitializationListener);
}
@Override
@@ -171,6 +172,7 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
});
}
+ @NonNull
@Override
public Lifecycle getLifecycle() {
return mRegistry;
diff --git a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
index ac278c0c..6cf80d2d 100644
--- a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
@@ -29,7 +29,7 @@ import android.support.annotation.RestrictTo;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class LifecycleRuntimeTrojanProvider extends ContentProvider {
+public class ProcessLifecycleOwnerInitializer extends ContentProvider {
@Override
public boolean onCreate() {
LifecycleDispatcher.init(getContext());
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index 37bdcdb4..77baf94c 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -31,6 +31,7 @@ import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.testapp.NavigationDialogActivity;
import android.arch.lifecycle.testapp.NavigationTestActivityFirst;
import android.arch.lifecycle.testapp.NavigationTestActivitySecond;
+import android.arch.lifecycle.testapp.NonSupportActivity;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
@@ -95,6 +96,22 @@ public class ProcessOwnerTest {
}
@Test
+ public void testNavigationToNonSupport() throws Throwable {
+ FragmentActivity firstActivity = setupObserverOnResume();
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NonSupportActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ Intent intent = new Intent(firstActivity, NonSupportActivity.class);
+ firstActivity.finish();
+ firstActivity.startActivity(intent);
+ NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity();
+ assertThat("Failed to navigate", secondActivity, notNullValue());
+ checkProcessObserverSilent(secondActivity);
+ }
+
+ @Test
public void testRecreation() throws Throwable {
FragmentActivity activity = setupObserverOnResume();
FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
@@ -164,4 +181,11 @@ public class ProcessOwnerTest {
activityTestRule.runOnUiThread(() ->
ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
}
+
+ private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable {
+ assertThat(activity.awaitResumedState(), is(true));
+ assertThat(mObserver.mChangedState, is(false));
+ activityTestRule.runOnUiThread(() ->
+ ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
+ }
}
diff --git a/android/arch/lifecycle/ReportFragment.java b/android/arch/lifecycle/ReportFragment.java
index 3e4ece82..16a89ce8 100644
--- a/android/arch/lifecycle/ReportFragment.java
+++ b/android/arch/lifecycle/ReportFragment.java
@@ -28,7 +28,6 @@ import android.support.annotation.RestrictTo;
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class ReportFragment extends Fragment {
-
private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";
diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index f0214bfb..f7f9bbe5 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -16,16 +16,35 @@
package android.arch.lifecycle;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
+import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
-import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
class TestUtils {
@@ -61,23 +80,88 @@ class TestUtils {
return result;
}
- static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
+ static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, CREATED);
+ }
+
+ static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, STARTED);
+ }
+
+ static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, RESUMED);
+ }
+
+ static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, DESTROYED);
+ }
+
+ static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
+ Lifecycle.State state)
throws Throwable {
final CountDownLatch latch = new CountDownLatch(1);
activityRule.runOnUiThread(() -> {
- Lifecycle.State currentState = a.getLifecycle().getCurrentState();
- if (currentState == RESUMED) {
+ Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
+ if (currentState == state) {
latch.countDown();
+ } else {
+ owner.getLifecycle().addObserver(new LifecycleObserver() {
+ @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+ public void onStateChanged(LifecycleOwner provider) {
+ if (provider.getLifecycle().getCurrentState() == state) {
+ latch.countDown();
+ provider.getLifecycle().removeObserver(this);
+ }
+ }
+ });
}
- a.getLifecycle().addObserver(new LifecycleObserver() {
- @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
- public void onStateChanged(LifecycleOwner provider) {
- latch.countDown();
- provider.getLifecycle().removeObserver(this);
- }
- });
});
- latch.await();
+ boolean latchResult = latch.await(1, TimeUnit.MINUTES);
+ assertThat("expected " + state + " never happened. Current state:"
+ + owner.getLifecycle().getCurrentState(), latchResult, is(true));
+
+ // wait for another loop to ensure all observers are called
+ activityRule.runOnUiThread(() -> {
+ // do nothing
+ });
}
+ @SafeVarargs
+ static <T> List<T> flatMap(List<T>... items) {
+ ArrayList<T> result = new ArrayList<>();
+ for (List<T> item : items) {
+ result.addAll(item);
+ }
+ return result;
+ }
+
+ /**
+ * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
+ * in the order they should arrive.
+ */
+ @SuppressWarnings("unchecked")
+ static class OrderedTuples {
+ static final List<Pair<TestEvent, Lifecycle.Event>> CREATE =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
+ new Pair(LIFECYCLE_EVENT, ON_CREATE));
+ static final List<Pair<TestEvent, Lifecycle.Event>> START =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
+ new Pair(LIFECYCLE_EVENT, ON_START));
+ static final List<Pair<TestEvent, Lifecycle.Event>> RESUME =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
+ new Pair(LIFECYCLE_EVENT, ON_RESUME));
+ static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
+ new Pair(OWNER_CALLBACK, ON_PAUSE));
+ static final List<Pair<TestEvent, Lifecycle.Event>> STOP =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
+ new Pair(OWNER_CALLBACK, ON_STOP));
+ static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
+ new Pair(OWNER_CALLBACK, ON_DESTROY));
+ }
}
diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index 9ce9cbb7..c735f8ba 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -18,6 +18,7 @@ package android.arch.lifecycle;
import android.arch.core.util.Function;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
@@ -60,7 +61,8 @@ public class Transformations {
* @return a LiveData which emits resulting values
*/
@MainThread
- public static <X, Y> LiveData<Y> map(LiveData<X> source, final Function<X, Y> func) {
+ public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
+ @NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
@@ -120,8 +122,8 @@ public class Transformations {
* @param <Y> a type of resulting LiveData
*/
@MainThread
- public static <X, Y> LiveData<Y> switchMap(LiveData<X> trigger,
- final Function<X, LiveData<Y>> func) {
+ public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
+ @NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 7ef591f3..29cbab8e 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -43,7 +43,8 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
- <T extends ViewModel> T create(Class<T> modelClass);
+ @NonNull
+ <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
private final Factory mFactory;
@@ -70,7 +71,7 @@ public class ViewModelProvider {
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
- public ViewModelProvider(ViewModelStore store, Factory factory) {
+ public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
@@ -88,7 +89,8 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
- public <T extends ViewModel> T get(Class<T> modelClass) {
+ @NonNull
+ public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
@@ -136,8 +138,9 @@ public class ViewModelProvider {
*/
public static class NewInstanceFactory implements Factory {
+ @NonNull
@Override
- public <T extends ViewModel> T create(Class<T> modelClass) {
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index 746162a9..b4b20aa4 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -139,8 +139,9 @@ public class ViewModelProviders {
mApplication = application;
}
+ @NonNull
@Override
- public <T extends ViewModel> T create(Class<T> modelClass) {
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
diff --git a/android/arch/lifecycle/ViewModelStoreOwner.java b/android/arch/lifecycle/ViewModelStoreOwner.java
index 50583056..e26fa325 100644
--- a/android/arch/lifecycle/ViewModelStoreOwner.java
+++ b/android/arch/lifecycle/ViewModelStoreOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* A scope that owns {@link ViewModelStore}.
* <p>
@@ -30,5 +32,6 @@ public interface ViewModelStoreOwner {
*
* @return a {@code ViewModelStore}
*/
+ @NonNull
ViewModelStore getViewModelStore();
}
diff --git a/android/arch/lifecycle/ViewModelStores.java b/android/arch/lifecycle/ViewModelStores.java
index 8c17dd98..d7d769d6 100644
--- a/android/arch/lifecycle/ViewModelStores.java
+++ b/android/arch/lifecycle/ViewModelStores.java
@@ -19,6 +19,7 @@ package android.arch.lifecycle;
import static android.arch.lifecycle.HolderFragment.holderFragmentFor;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
@@ -38,7 +39,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(FragmentActivity activity) {
+ public static ViewModelStore of(@NonNull FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
@@ -49,7 +50,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(Fragment fragment) {
+ public static ViewModelStore of(@NonNull Fragment fragment) {
return holderFragmentFor(fragment).getViewModelStore();
}
}
diff --git a/android/arch/lifecycle/testapp/CollectingActivity.java b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
index 6e243b6c..4213cab9 100644
--- a/android/arch/lifecycle/testapp/CollectingActivity.java
+++ b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
@@ -17,21 +17,20 @@
package android.arch.lifecycle.testapp;
import android.arch.lifecycle.Lifecycle;
-import android.util.Pair;
+import android.arch.lifecycle.LifecycleOwner;
+import android.support.v4.util.Pair;
import java.util.List;
/**
* For activities that collect their events.
*/
-public interface CollectingActivity {
- long TIMEOUT = 5;
-
+public interface CollectingLifecycleOwner extends LifecycleOwner {
/**
- * Return collected events
+ * Return a copy of currently collected events
*
* @return The list of collected events.
* @throws InterruptedException
*/
- List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() throws InterruptedException;
+ List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents();
}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
new file mode 100644
index 00000000..f38d4224
--- /dev/null
+++ b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends FragmentActivity.
+ */
+public class CollectingSupportActivity extends FragmentActivity implements
+ CollectingLifecycleOwner {
+
+ private final List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+ private CountDownLatch mSavedStateLatch = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ FrameLayout layout = new FrameLayout(this);
+ layout.setId(R.id.fragment_container);
+ setContentView(layout);
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ /**
+ * replaces the main content fragment w/ the given fragment.
+ */
+ public void replaceFragment(Fragment fragment) {
+ getSupportFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, fragment)
+ .commitNow();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME));
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE));
+ // helps with less flaky API 16 tests.
+ overridePendingTransition(0, 0);
+ }
+
+ @Override
+ public List<Pair<TestEvent, Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mSavedStateLatch.countDown();
+ }
+
+ /**
+ * Waits for onSaveInstanceState to be called.
+ */
+ public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds)
+ throws InterruptedException {
+ return mSavedStateLatch.await(seconds, TimeUnit.SECONDS);
+ }
+}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
new file mode 100644
index 00000000..9bbbe165
--- /dev/null
+++ b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.annotation.SuppressLint;
+import android.arch.lifecycle.Lifecycle;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A support fragment that collects all of its events.
+ */
+@SuppressLint("ValidFragment")
+public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner {
+ private final List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents =
+ new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ //noinspection ConstantConditions
+ FrameLayout layout = new FrameLayout(container.getContext());
+ layout.setId(R.id.child_fragment_container);
+ return layout;
+ }
+
+ /**
+ * Runs a replace fragment transaction with 'fragment' on this Fragment.
+ */
+ public void replaceFragment(Fragment fragment) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .add(R.id.child_fragment_container, fragment)
+ .commitNow();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ }
+
+ @Override
+ public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
+ }
+}
diff --git a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
index d8f4fb39..cdf577c1 100644
--- a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -16,27 +16,29 @@
package android.arch.lifecycle.testapp;
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import android.os.Bundle;
-import android.util.Pair;
+import android.support.annotation.NonNull;
+import android.support.v4.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
/**
* LifecycleRegistryOwner that extends framework activity.
*/
+@SuppressWarnings("deprecation")
public class FrameworkLifecycleRegistryActivity extends Activity implements
- LifecycleRegistryOwner, CollectingActivity {
+ LifecycleRegistryOwner, CollectingLifecycleOwner {
private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+ @NonNull
@Override
public LifecycleRegistry getLifecycle() {
return mLifecycleRegistry;
@@ -49,49 +51,43 @@ public class FrameworkLifecycleRegistryActivity extends Activity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
getLifecycle().addObserver(mTestObserver);
}
@Override
protected void onStart() {
super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
}
@Override
protected void onResume() {
super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
- finish();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
}
@Override
protected void onDestroy() {
super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
mLatch.countDown();
}
@Override
protected void onStop() {
super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
}
@Override
protected void onPause() {
super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
}
- /**
- * awaits for all events and returns them.
- */
@Override
- public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
- throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
+ public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
}
}
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
deleted file mode 100644
index 5f33c282..00000000
--- a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity for testing full lifecycle
- */
-public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
-
- private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
- private CountDownLatch mLatch = new CountDownLatch(1);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
- finish();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
- mLatch.countDown();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
- }
-
- /**
- * awaits for all events and returns them.
- */
- @Override
- public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
- throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
- }
-}
diff --git a/android/arch/lifecycle/testapp/MainActivity.java b/android/arch/lifecycle/testapp/MainActivity.java
deleted file mode 100644
index b9d59142..00000000
--- a/android/arch/lifecycle/testapp/MainActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Simple test activity
- */
-public class MainActivity extends FragmentActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
- }
-}
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 0ae94033..7d53528f 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -22,4 +22,10 @@ import android.support.v4.app.FragmentActivity;
* an activity with Dialog theme.
*/
public class NavigationDialogActivity extends FragmentActivity {
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // helps with less flaky API 16 tests
+ overridePendingTransition(0, 0);
+ }
}
diff --git a/android/arch/lifecycle/testapp/NonSupportActivity.java b/android/arch/lifecycle/testapp/NonSupportActivity.java
new file mode 100644
index 00000000..835d846a
--- /dev/null
+++ b/android/arch/lifecycle/testapp/NonSupportActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it
+ * should work anyway.
+ */
+public class NonSupportActivity extends Activity {
+
+ private static final int TIMEOUT = 1; //secs
+ private final Lock mLock = new ReentrantLock();
+ private Condition mIsResumedCondition = mLock.newCondition();
+ private boolean mIsResumed = false;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mLock.lock();
+ try {
+ mIsResumed = true;
+ mIsResumedCondition.signalAll();
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mLock.lock();
+ try {
+ mIsResumed = false;
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ /**
+ * awaits resumed state
+ * @return
+ * @throws InterruptedException
+ */
+ public boolean awaitResumedState() throws InterruptedException {
+ mLock.lock();
+ try {
+ while (!mIsResumed) {
+ if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) {
+ return false;
+ }
+ }
+ return true;
+ } finally {
+ mLock.unlock();
+ }
+ }
+}
diff --git a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
deleted file mode 100644
index c46c6d3e..00000000
--- a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * LifecycleRegistryOwner that extends FragmentActivity.
- */
-public class SupportLifecycleRegistryActivity extends FragmentActivity implements
- LifecycleRegistryOwner, CollectingActivity {
- private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
- @Override
- public LifecycleRegistry getLifecycle() {
- return mLifecycleRegistry;
- }
-
- private List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
- private CountDownLatch mLatch = new CountDownLatch(1);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_START));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_RESUME));
- finish();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_DESTROY));
- mLatch.countDown();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_STOP));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_PAUSE));
- }
-
- /**
- * awaits for all events and returns them.
- */
- @Override
- public List<Pair<TestEvent, Event>> waitForCollectedEvents() throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
- }
-}
diff --git a/android/arch/lifecycle/testapp/TestEvent.java b/android/arch/lifecycle/testapp/TestEvent.java
index 0929f84a..788045a2 100644
--- a/android/arch/lifecycle/testapp/TestEvent.java
+++ b/android/arch/lifecycle/testapp/TestEvent.java
@@ -17,6 +17,6 @@
package android.arch.lifecycle.testapp;
public enum TestEvent {
- ACTIVITY_CALLBACK,
- LIFECYCLE_EVENT
+ OWNER_CALLBACK,
+ LIFECYCLE_EVENT,
}
diff --git a/android/arch/lifecycle/testapp/TestObserver.java b/android/arch/lifecycle/testapp/TestObserver.java
index c6112396..00b8e16d 100644
--- a/android/arch/lifecycle/testapp/TestObserver.java
+++ b/android/arch/lifecycle/testapp/TestObserver.java
@@ -28,7 +28,7 @@ import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
-import android.util.Pair;
+import android.support.v4.util.Pair;
import java.util.List;
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 664ab16c..06564907 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -21,7 +21,6 @@ import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -75,7 +74,6 @@ public abstract class BoundedDataSource<Value> extends PositionalDataSource<Valu
if (result.size() != loadSize) {
throw new IllegalStateException("invalid number of items returned.");
}
- Collections.reverse(result);
}
return result;
}
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index afcc208c..be9da200 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -26,21 +26,65 @@ import java.util.List;
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
- /**
- * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
- *
- * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
- * if difficult or undesired to compute.
- */
- public int countItems() {
- return COUNT_UNDEFINED;
- }
-
@Override
boolean isContiguous() {
return true;
}
+ void loadInitial(Key key, int pageSize, boolean enablePlaceholders,
+ PageResult.Receiver<Key, Value> receiver) {
+ NullPaddedList<Value> initial = loadInitial(key, pageSize, enablePlaceholders);
+ if (initial != null) {
+ receiver.onPageResult(new PageResult<>(
+ PageResult.INIT,
+ new Page<Key, Value>(initial.mList),
+ initial.getLeadingNullCount(),
+ initial.getTrailingNullCount(),
+ initial.getPositionOffset()));
+ } else {
+ receiver.onPageResult(new PageResult<Key, Value>(
+ PageResult.INIT, null, 0, 0, 0));
+ }
+ }
+
+ void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ PageResult.Receiver<Key, Value> receiver) {
+ List<Value> list = loadAfter(currentEndIndex, currentEndItem, pageSize);
+
+ Page<Key, Value> page = list != null
+ ? new Page<Key, Value>(list) : null;
+
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.APPEND, page, 0, 0, 0));
+ }
+
+ void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ PageResult.Receiver<Key, Value> receiver) {
+ List<Value> list = loadBefore(currentBeginIndex, currentBeginItem, pageSize);
+
+ Page<Key, Value> page = list != null
+ ? new Page<Key, Value>(list) : null;
+
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.PREPEND, page, 0, 0, 0));
+ }
+
+ /**
+ * Get the key from either the position, or item, or null if position/item invalid.
+ * <p>
+ * Position may not match passed item's position - if trying to query the key from a position
+ * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
+ */
+ abstract Key getKey(int position, Value item);
+
+ @Nullable
+ abstract List<Value> loadAfterImpl(int currentEndIndex,
+ @NonNull Value currentEndItem, int pageSize);
+
+ @Nullable
+ abstract List<Value> loadBeforeImpl(int currentBeginIndex,
+ @NonNull Value currentBeginItem, int pageSize);
+
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@@ -48,21 +92,7 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
public abstract NullPaddedList<Value> loadInitial(
Key key, int initialLoadSize, boolean enablePlaceholders);
- /**
- * Load data after the given position / item.
- * <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase number loaded than reduce.
- *
- * @param currentEndIndex Load items after this index, starting with currentEndIndex + 1.
- * @param currentEndItem Load items after this item, can be used for precise querying based on
- * item contents.
- * @param pageSize Suggested number of items to load.
- * @return List of items, starting at position currentEndIndex + 1. Null if the data source is
- * no longer valid, and should not be queried again.
- *
- * @hide
- */
+ /** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -78,24 +108,7 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
return list;
}
- @Nullable
- abstract List<Value> loadAfterImpl(int currentEndIndex,
- @NonNull Value currentEndItem, int pageSize);
-
- /**
- * Load data before the given position / item.
- * <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase number loaded than reduce.
- *
- * @param currentBeginIndex Load items before this index, starting with currentBeginIndex - 1.
- * @param currentBeginItem Load items after this item, can be used for precise querying based
- * on item contents.
- * @param pageSize Suggested number of items to load.
- * @return List of items, in descending order, starting at position currentBeginIndex - 1.
- *
- * @hide
- */
+ /** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -111,15 +124,4 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
return list;
}
-
- @Nullable
- abstract List<Value> loadBeforeImpl(int currentBeginIndex,
- @NonNull Value currentBeginItem, int pageSize);
-
- /**
- * Get the key from either the position, or item. Position may not match passed item's position,
- * if trying to query the key from a position that isn't yet loaded, so a fallback item must be
- * used.
- */
- abstract Key getKey(int position, Value item);
}
diff --git a/android/arch/paging/ContiguousDiffHelperTest.java b/android/arch/paging/ContiguousDiffHelperTest.java
deleted file mode 100644
index 4f221b34..00000000
--- a/android/arch/paging/ContiguousDiffHelperTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.v7.recyclerview.extensions.DiffCallback;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public class ContiguousDiffHelperTest {
- private interface CallbackValidator {
- void validate(ListUpdateCallback callback);
- }
-
- private static final DiffCallback<String> DIFF_CALLBACK = new DiffCallback<String>() {
- @Override
- public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
- // first char means same item
- return oldItem.charAt(0) == newItem.charAt(0);
- }
-
- @Override
- public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
- return oldItem.equals(newItem);
- }
- };
-
- private void validateTwoListDiff(StringPagedList oldList, StringPagedList newList,
- CallbackValidator callbackValidator) {
- DiffUtil.DiffResult diffResult = ContiguousDiffHelper.computeDiff(oldList, newList,
- DIFF_CALLBACK, false);
-
- ListUpdateCallback listUpdateCallback = Mockito.mock(ListUpdateCallback.class);
- ContiguousDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult);
-
- callbackValidator.validate(listUpdateCallback);
- }
-
- @Test
- public void sameListNoUpdates() {
- validateTwoListDiff(
- new StringPagedList(5, 5, "a", "b", "c"),
- new StringPagedList(5, 5, "a", "b", "c"),
- new CallbackValidator() {
- @Override
- public void validate(ListUpdateCallback callback) {
- verifyZeroInteractions(callback);
- }
- }
- );
- }
-
- @Test
- public void appendFill() {
- validateTwoListDiff(
- new StringPagedList(5, 5, "a", "b"),
- new StringPagedList(5, 4, "a", "b", "c"),
- new CallbackValidator() {
- @Override
- public void validate(ListUpdateCallback callback) {
- verify(callback).onRemoved(11, 1);
- verify(callback).onInserted(7, 1);
- // NOTE: ideally would be onChanged(7, 1, null)
- verifyNoMoreInteractions(callback);
- }
- }
- );
- }
-
- @Test
- public void prependFill() {
- validateTwoListDiff(
- new StringPagedList(5, 5, "b", "c"),
- new StringPagedList(4, 5, "a", "b", "c"),
- new CallbackValidator() {
- @Override
- public void validate(ListUpdateCallback callback) {
- verify(callback).onRemoved(0, 1);
- verify(callback).onInserted(4, 1);
- //NOTE: ideally would be onChanged(4, 1, null);
- verifyNoMoreInteractions(callback);
- }
- }
- );
- }
-
- @Test
- public void change() {
- validateTwoListDiff(
- new StringPagedList(5, 5, "a1", "b1", "c1"),
- new StringPagedList(5, 5, "a2", "b1", "c2"),
- new CallbackValidator() {
- @Override
- public void validate(ListUpdateCallback callback) {
- verify(callback).onChanged(5, 1, null);
- verify(callback).onChanged(7, 1, null);
- verifyNoMoreInteractions(callback);
- }
- }
- );
- }
-}
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index d8907c3b..2a5cd42f 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -16,101 +16,136 @@
package android.arch.paging;
+import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ContiguousPagedList<T> extends NullPaddedList<T> {
-
- private final ContiguousDataSource<?, T> mDataSource;
- private final Executor mMainThreadExecutor;
- private final Executor mBackgroundThreadExecutor;
- private final Config mConfig;
+class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
+ private final ContiguousDataSource<K, V> mDataSource;
private boolean mPrependWorkerRunning = false;
private boolean mAppendWorkerRunning = false;
private int mPrependItemsRequested = 0;
private int mAppendItemsRequested = 0;
- private int mLastLoad = 0;
- private T mLastItem = null;
+ @SuppressWarnings("unchecked")
+ private final PagedStorage<K, V> mKeyedStorage = (PagedStorage<K, V>) mStorage;
+
+ private final PageResult.Receiver<K, V> mReceiver = new PageResult.Receiver<K, V>() {
+ @AnyThread
+ @Override
+ public void postOnPageResult(@NonNull final PageResult<K, V> pageResult) {
+ // NOTE: if we're already on main thread, this can delay page receive by a frame
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ onPageResult(pageResult);
+ }
+ });
+ }
- private AtomicBoolean mDetached = new AtomicBoolean(false);
+ @MainThread
+ @Override
+ public void onPageResult(@NonNull PageResult<K, V> pageResult) {
+ if (pageResult.page == null) {
+ detach();
+ return;
+ }
- private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+ if (isDetached()) {
+ // No op, have detached
+ return;
+ }
- @WorkerThread
- <K> ContiguousPagedList(@NonNull ContiguousDataSource<K, T> dataSource,
+ Page<K, V> page = pageResult.page;
+ if (pageResult.type == PageResult.INIT) {
+ mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
+ pageResult.positionOffset, ContiguousPagedList.this);
+ notifyInserted(0, mKeyedStorage.size());
+ } else if (pageResult.type == PageResult.APPEND) {
+ mKeyedStorage.appendPage(page, ContiguousPagedList.this);
+ } else if (pageResult.type == PageResult.PREPEND) {
+ mKeyedStorage.prependPage(page, ContiguousPagedList.this);
+ }
+ }
+ };
+
+ ContiguousPagedList(
+ @NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- Config config,
- @Nullable K key) {
- super();
-
+ @NonNull Config config,
+ final @Nullable K key) {
+ super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config);
mDataSource = dataSource;
- mMainThreadExecutor = mainThreadExecutor;
- mBackgroundThreadExecutor = backgroundThreadExecutor;
- mConfig = config;
- NullPaddedList<T> initialState = dataSource.loadInitial(
- key, config.mInitialLoadSizeHint, config.mEnablePlaceholders);
-
- if (initialState != null) {
- mPositionOffset = initialState.getPositionOffset();
-
- mLeadingNullCount = initialState.getLeadingNullCount();
- mList = new ArrayList<>(initialState.mList);
- mTrailingNullCount = initialState.getTrailingNullCount();
-
- if (initialState.getLeadingNullCount() == 0
- && initialState.getTrailingNullCount() == 0
- && config.mPrefetchDistance < 1) {
- throw new IllegalArgumentException("Null padding is required to support the 0"
- + " prefetch case - require either null items or prefetching to fetch"
- + " beyond initial load.");
- }
- if (initialState.size() != 0) {
- mLastLoad = mLeadingNullCount + mList.size() / 2;
- mLastItem = mList.get(mList.size() / 2);
- }
- } else {
- mList = new ArrayList<>();
- detach();
- }
- if (mList.size() == 0) {
- // Empty initial state, so don't try and fetch data.
- mPrependWorkerRunning = true;
- mAppendWorkerRunning = true;
- }
+ // blocking init just triggers the initial load on the construction thread -
+ // Could still be posted with callback, if desired.
+ mDataSource.loadInitial(key,
+ mConfig.initialLoadSizeHint,
+ mConfig.enablePlaceholders,
+ mReceiver);
}
+ @MainThread
@Override
- public T get(int index) {
- T item = super.get(index);
- if (item != null) {
- mLastItem = item;
+ void dispatchUpdatesSinceSnapshot(
+ @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
+
+ final PagedStorage<?, V> snapshot = pagedListSnapshot.mStorage;
+
+ final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
+ final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
+
+ final int previousTrailing = snapshot.getTrailingNullCount();
+ final int previousLeading = snapshot.getLeadingNullCount();
+
+ // Validate that the snapshot looks like a previous version of this list - if it's not,
+ // we can't be sure we'll dispatch callbacks safely
+ if (newlyAppended < 0
+ || newlyPrepended < 0
+ || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
+ || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
+ || (mStorage.getStorageCount()
+ != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
+ throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+ + " to be a snapshot of this PagedList");
+ }
+
+ if (newlyAppended != 0) {
+ final int changedCount = Math.min(previousTrailing, newlyAppended);
+ final int addedCount = newlyAppended - changedCount;
+
+ final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
+ if (changedCount != 0) {
+ callback.onChanged(endPosition, changedCount);
+ }
+ if (addedCount != 0) {
+ callback.onInserted(endPosition + changedCount, addedCount);
+ }
+ }
+ if (newlyPrepended != 0) {
+ final int changedCount = Math.min(previousLeading, newlyPrepended);
+ final int addedCount = newlyPrepended - changedCount;
+
+ if (changedCount != 0) {
+ callback.onChanged(previousLeading, changedCount);
+ }
+ if (addedCount != 0) {
+ callback.onInserted(0, addedCount);
+ }
}
- return item;
}
+ @MainThread
@Override
- public void loadAround(int index) {
- mLastLoad = index + mPositionOffset;
-
- int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount);
- int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size());
+ protected void loadAroundInternal(int index) {
+ int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
+ int appendItems = index + mConfig.prefetchDistance
+ - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
@@ -123,21 +158,6 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
}
- @Override
- public int getLoadedCount() {
- return mList.size();
- }
-
- @Override
- public int getLeadingNullCount() {
- return mLeadingNullCount;
- }
-
- @Override
- public int getTrailingNullCount() {
- return mTrailingNullCount;
- }
-
@MainThread
private void schedulePrepend() {
if (mPrependWorkerRunning) {
@@ -145,29 +165,17 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
mPrependWorkerRunning = true;
- final int position = mLeadingNullCount + mPositionOffset;
- final T item = mList.get(0);
+ final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
+
+ // safe to access first item here - mStorage can't be empty if we're prepending
+ final V item = mStorage.getFirstContiguousItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (mDetached.get()) {
+ if (isDetached()) {
return;
}
-
- final List<T> data = mDataSource.loadBefore(position, item, mConfig.mPageSize);
- if (data != null) {
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- prependImpl(data);
- }
- });
- } else {
- detach();
- }
+ mDataSource.loadBefore(position, item, mConfig.pageSize, mReceiver);
}
});
}
@@ -179,56 +187,44 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
mAppendWorkerRunning = true;
- final int position = mLeadingNullCount + mList.size() - 1 + mPositionOffset;
- final T item = mList.get(mList.size() - 1);
+ final int position = mStorage.getLeadingNullCount()
+ + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
+
+ // safe to access first item here - mStorage can't be empty if we're appending
+ final V item = mStorage.getLastContiguousItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (mDetached.get()) {
+ if (isDetached()) {
return;
}
-
- final List<T> data = mDataSource.loadAfter(position, item, mConfig.mPageSize);
- if (data != null) {
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- appendImpl(data);
- }
- });
- } else {
- detach();
- }
+ mDataSource.loadAfter(position, item, mConfig.pageSize, mReceiver);
}
});
}
- @MainThread
- private void prependImpl(List<T> before) {
- final int count = before.size();
- if (count == 0) {
- // Nothing returned from source, stop loading in this direction
- return;
- }
-
- Collections.reverse(before);
- mList.addAll(0, before);
-
- final int changedCount = Math.min(mLeadingNullCount, count);
- final int addedCount = count - changedCount;
+ @Override
+ boolean isContiguous() {
+ return true;
+ }
- if (changedCount != 0) {
- mLeadingNullCount -= changedCount;
- }
- mPositionOffset -= addedCount;
- mNumberPrepended += count;
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return mDataSource.getKey(mLastLoad, mLastItem);
+ }
+ @MainThread
+ @Override
+ public void onInitialized(int count) {
+ notifyInserted(0, count);
+ }
- // only try to post more work after fully prepended (with offsets / null counts updated)
- mPrependItemsRequested -= count;
+ @MainThread
+ @Override
+ public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
+ // consider whether to post more work, now that a page is fully prepended
+ mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
mPrependWorkerRunning = false;
if (mPrependItemsRequested > 0) {
// not done prepending, keep going
@@ -236,39 +232,16 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
// finally dispatch callbacks, after prepend may have already been scheduled
- for (WeakReference<Callback> weakRef : mCallbacks) {
- Callback callback = weakRef.get();
- if (callback != null) {
- if (changedCount != 0) {
- callback.onChanged(mLeadingNullCount, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(0, addedCount);
- }
- }
- }
+ notifyChanged(leadingNulls, changedCount);
+ notifyInserted(0, addedCount);
}
@MainThread
- private void appendImpl(List<T> after) {
- final int count = after.size();
- if (count == 0) {
- // Nothing returned from source, stop loading in this direction
- return;
- }
-
- mList.addAll(after);
-
- final int changedCount = Math.min(mTrailingNullCount, count);
- final int addedCount = count - changedCount;
-
- if (changedCount != 0) {
- mTrailingNullCount -= changedCount;
- }
- mNumberAppended += count;
+ @Override
+ public void onPageAppended(int endPosition, int changedCount, int addedCount) {
+ // consider whether to post more work, now that a page is fully appended
- // only try to post more work after fully appended (with null counts updated)
- mAppendItemsRequested -= count;
+ mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
mAppendWorkerRunning = false;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
@@ -276,100 +249,19 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
// finally dispatch callbacks, after append may have already been scheduled
- for (WeakReference<Callback> weakRef : mCallbacks) {
- Callback callback = weakRef.get();
- if (callback != null) {
- final int endPosition = mLeadingNullCount + mList.size() - count;
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
- }
- }
- }
- }
-
- @Override
- public boolean isImmutable() {
- // TODO: return true if had nulls, and now getLoadedCount() == size(). Is that safe?
- // Currently we don't prevent DataSources from returning more items than their null counts
- return isDetached();
- }
-
- @Override
- public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
- @NonNull Callback callback) {
- NullPaddedList<T> snapshot = (NullPaddedList<T>) previousSnapshot;
- if (snapshot != this && snapshot != null) {
- final int newlyAppended = mNumberAppended - snapshot.getNumberAppended();
- final int newlyPrepended = mNumberPrepended - snapshot.getNumberPrepended();
-
- final int previousTrailing = snapshot.getTrailingNullCount();
- final int previousLeading = snapshot.getLeadingNullCount();
-
- // Validate that the snapshot looks like a previous version of this list - if it's not,
- // we can't be sure we'll dispatch callbacks safely
- if (newlyAppended < 0
- || newlyPrepended < 0
- || mTrailingNullCount != Math.max(previousTrailing - newlyAppended, 0)
- || mLeadingNullCount != Math.max(previousLeading - newlyPrepended, 0)
- || snapshot.getLoadedCount() + newlyAppended + newlyPrepended != mList.size()) {
- throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
- + " to be a snapshot of this list");
- }
-
- if (newlyAppended != 0) {
- final int changedCount = Math.min(previousTrailing, newlyAppended);
- final int addedCount = newlyAppended - changedCount;
-
- final int endPosition =
- snapshot.getLeadingNullCount() + snapshot.getLoadedCount();
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
- }
- }
- if (newlyPrepended != 0) {
- final int changedCount = Math.min(previousLeading, newlyPrepended);
- final int addedCount = newlyPrepended - changedCount;
-
- if (changedCount != 0) {
- callback.onChanged(previousLeading, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(0, addedCount);
- }
- }
- }
- mCallbacks.add(new WeakReference<>(callback));
- }
-
- @Override
- public void removeWeakCallback(@NonNull Callback callback) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- Callback currentCallback = mCallbacks.get(i).get();
- if (currentCallback == null || currentCallback == callback) {
- mCallbacks.remove(i);
- }
- }
+ notifyChanged(endPosition, changedCount);
+ notifyInserted(endPosition + changedCount, addedCount);
}
+ @MainThread
@Override
- public boolean isDetached() {
- return mDetached.get();
+ public void onPagePlaceholderInserted(int pageIndex) {
+ throw new IllegalStateException("Tiled callback on ContiguousPagedList");
}
- @SuppressWarnings("WeakerAccess")
- public void detach() {
- mDetached.set(true);
- }
-
- @Nullable
+ @MainThread
@Override
- public Object getLastKey() {
- return mDataSource.getKey(mLastLoad, mLastItem);
+ public void onPageInserted(int start, int count) {
+ throw new IllegalStateException("Tiled callback on ContiguousPagedList");
}
}
diff --git a/android/arch/paging/ContiguousPagedListTest.java b/android/arch/paging/ContiguousPagedListTest.java
deleted file mode 100644
index ee7ea6a4..00000000
--- a/android/arch/paging/ContiguousPagedListTest.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.support.annotation.Nullable;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-@RunWith(Parameterized.class)
-public class ContiguousPagedListTest {
-
- @Parameterized.Parameters(name = "counted:{0}")
- public static List<Object[]> parameters() {
- return Arrays.asList(new Object[][]{{true}, {false}});
- }
-
- public ContiguousPagedListTest(boolean counted) {
- mCounted = counted;
- }
-
- private final boolean mCounted;
- private TestExecutor mMainThread = new TestExecutor();
- private TestExecutor mBackgroundThread = new TestExecutor();
-
- private static final ArrayList<Item> ITEMS = new ArrayList<>();
-
- static {
- for (int i = 0; i < 100; i++) {
- ITEMS.add(new Item(i));
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- private static class Item {
- private Item(int position) {
- this.position = position;
- this.name = "Item " + position;
- }
-
- public final int position;
- public final String name;
-
- @Override
- public String toString() {
- return name;
- }
- }
-
- private class TestSource extends PositionalDataSource<Item> {
- @Override
- public int countItems() {
- if (mCounted) {
- return ITEMS.size();
- } else {
- return COUNT_UNDEFINED;
- }
- }
-
- private List<Item> getClampedRange(int startInc, int endExc, boolean reverse) {
- startInc = Math.max(0, startInc);
- endExc = Math.min(ITEMS.size(), endExc);
- List<Item> list = ITEMS.subList(startInc, endExc);
- if (reverse) {
- Collections.reverse(list);
- }
- return list;
- }
-
- @Nullable
- @Override
- public List<Item> loadAfter(int startIndex, int pageSize) {
- return getClampedRange(startIndex, startIndex + pageSize, false);
- }
-
- @Nullable
- @Override
- public List<Item> loadBefore(int startIndex, int pageSize) {
- return getClampedRange(startIndex - pageSize + 1, startIndex + 1, true);
- }
- }
-
- private void verifyRange(int start, int count, NullPaddedList<Item> actual) {
- if (mCounted) {
- int expectedLeading = start;
- int expectedTrailing = ITEMS.size() - start - count;
- assertEquals(ITEMS.size(), actual.size());
- assertEquals(ITEMS.size() - expectedLeading - expectedTrailing,
- actual.getLoadedCount());
- assertEquals(expectedLeading, actual.getLeadingNullCount());
- assertEquals(expectedTrailing, actual.getTrailingNullCount());
-
- for (int i = 0; i < actual.getLoadedCount(); i++) {
- assertSame(ITEMS.get(i + start), actual.get(i + start));
- }
- } else {
- assertEquals(count, actual.size());
- assertEquals(actual.size(), actual.getLoadedCount());
- assertEquals(0, actual.getLeadingNullCount());
- assertEquals(0, actual.getTrailingNullCount());
-
- for (int i = 0; i < actual.getLoadedCount(); i++) {
- assertSame(ITEMS.get(i + start), actual.get(i));
- }
- }
- }
-
- private void verifyCallback(PagedList.Callback callback, int countedPosition,
- int uncountedPosition) {
- if (mCounted) {
- verify(callback).onChanged(countedPosition, 20);
- } else {
- verify(callback).onInserted(uncountedPosition, 20);
- }
- }
-
- @Test
- public void initialLoad() {
- verifyRange(30, 40,
- new TestSource().loadInitial(50, 40, true));
-
- verifyRange(0, 10,
- new TestSource().loadInitial(5, 10, true));
-
- verifyRange(90, 10,
- new TestSource().loadInitial(95, 10, true));
- }
-
-
- private ContiguousPagedList<Item> createCountedPagedList(
- PagedList.Config config, int initialPosition) {
- TestSource source = new TestSource();
- return new ContiguousPagedList<>(
- source, mMainThread, mBackgroundThread,
- config,
- initialPosition);
- }
-
- private ContiguousPagedList<Item> createCountedPagedList(int initialPosition) {
- return createCountedPagedList(
- new PagedList.Config.Builder()
- .setInitialLoadSizeHint(40)
- .setPageSize(20)
- .setPrefetchDistance(20)
- .build(),
- initialPosition);
- }
-
- @Test
- public void append() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(0, 40, pagedList);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(35);
- drain();
-
- verifyRange(0, 60, pagedList);
- verifyCallback(callback, 40, 40);
- verifyNoMoreInteractions(callback);
- }
-
-
- @Test
- public void prepend() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(60, 40, pagedList);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(mCounted ? 65 : 5);
- drain();
-
- verifyRange(40, 60, pagedList);
- verifyCallback(callback, 40, 0);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void outwards() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(50);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(30, 40, pagedList);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(mCounted ? 65 : 35);
- drain();
-
- verifyRange(30, 60, pagedList);
- verifyCallback(callback, 70, 40);
- verifyNoMoreInteractions(callback);
-
- pagedList.loadAround(mCounted ? 35 : 5);
- drain();
-
- verifyRange(10, 80, pagedList);
- verifyCallback(callback, 10, 0);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void multiAppend() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(0, 40, pagedList);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(55);
- drain();
-
- verifyRange(0, 80, pagedList);
- verifyCallback(callback, 40, 40);
- verifyCallback(callback, 60, 60);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void distantPrefetch() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(
- new PagedList.Config.Builder()
- .setInitialLoadSizeHint(10)
- .setPageSize(10)
- .setPrefetchDistance(30)
- .build(),
- 0);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(0, 10, pagedList);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(5);
- drain();
-
- verifyRange(0, 40, pagedList);
-
- pagedList.loadAround(6);
- drain();
-
- // although our prefetch window moves forward, no new load triggered
- verifyRange(0, 40, pagedList);
- }
-
- @Test
- public void appendCallbackAddedLate() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
- verifyRange(0, 40, pagedList);
-
- pagedList.loadAround(35);
- drain();
- verifyRange(0, 60, pagedList);
-
- // snapshot at 60 items
- NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
- verifyRange(0, 60, snapshot);
-
-
- pagedList.loadAround(55);
- drain();
- verifyRange(0, 80, pagedList);
- verifyRange(0, 60, snapshot);
-
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(snapshot, callback);
- verifyCallback(callback, 60, 60);
- verifyNoMoreInteractions(callback);
- }
-
-
- @Test
- public void prependCallbackAddedLate() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
- verifyRange(60, 40, pagedList);
-
- pagedList.loadAround(mCounted ? 65 : 5);
- drain();
- verifyRange(40, 60, pagedList);
-
- // snapshot at 60 items
- NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
- verifyRange(40, 60, snapshot);
-
-
- pagedList.loadAround(mCounted ? 45 : 5);
- drain();
- verifyRange(20, 80, pagedList);
- verifyRange(40, 60, snapshot);
-
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(snapshot, callback);
- verifyCallback(callback, 40, 0);
- verifyNoMoreInteractions(callback);
- }
-
- private void drain() {
- boolean executed;
- do {
- executed = mBackgroundThread.executeAll();
- executed |= mMainThread.executeAll();
- } while (executed);
- }
-}
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 48fbec5f..524e570a 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -17,6 +17,7 @@
package android.arch.paging;
import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -60,15 +61,6 @@ public abstract class DataSource<Key, Value> {
public static int COUNT_UNDEFINED = -1;
/**
- * Number of items that this DataSource can provide in total, or {@link #COUNT_UNDEFINED}.
- *
- * @return number of items that this DataSource can provide in total, or
- * {@link #COUNT_UNDEFINED} if expensive or undesired to compute.
- */
- @WorkerThread
- public abstract int countItems();
-
- /**
* Returns true if the data source guaranteed to produce a contiguous set of items,
* never producing gaps.
*/
@@ -111,7 +103,7 @@ public abstract class DataSource<Key, Value> {
*/
@AnyThread
@SuppressWarnings("WeakerAccess")
- public void addInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
+ public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
mOnInvalidatedCallbacks.add(onInvalidatedCallback);
}
@@ -122,7 +114,7 @@ public abstract class DataSource<Key, Value> {
*/
@AnyThread
@SuppressWarnings("WeakerAccess")
- public void removeInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
+ public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
}
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 8cf6829c..0d452946 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -103,10 +103,6 @@ import java.util.List;
* @param <Value> Type of items being loaded by the DataSource.
*/
public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
- @Override
- public final int countItems() {
- return 0; // method not called, can't be overridden
- }
@Nullable
@Override
@@ -118,7 +114,14 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
@Override
List<Value> loadBeforeImpl(
int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize) {
- return loadBefore(getKey(currentBeginItem), pageSize);
+ List<Value> list = loadBefore(getKey(currentBeginItem), pageSize);
+
+ if (list != null && list.size() > 1) {
+ // TODO: move out of keyed entirely, into the DB DataSource.
+ list = new ArrayList<>(list);
+ Collections.reverse(list);
+ }
+ return list;
}
@Nullable
@@ -191,6 +194,8 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @WorkerThread
+ @Override
public NullPaddedList<Value> loadInitial(
@Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
if (isInvalid()) {
diff --git a/android/arch/paging/ListDataSource.java b/android/arch/paging/ListDataSource.java
deleted file mode 100644
index f3e83d03..00000000
--- a/android/arch/paging/ListDataSource.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import java.util.List;
-
-public class ListDataSource<T> extends TiledDataSource<T> {
- private List<T> mList;
-
- ListDataSource(List<T> data) {
- mList = data;
- }
-
- @Override
- public int countItems() {
- return mList.size();
- }
-
- @Override
- public List<T> loadRange(int startPosition, int count) {
- int endExclusive = Math.min(mList.size(), startPosition + count);
- return mList.subList(startPosition, endExclusive);
- }
-}
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index b7c68dd6..07dd84bf 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,5 +16,133 @@
package android.arch.paging;
-abstract public class LivePagedListProvider<K, T> {
-} \ No newline at end of file
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.lifecycle.ComputableLiveData;
+import android.arch.lifecycle.LiveData;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+
+/**
+ * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
+ * <p>
+ * Return type for data-loading system of an application or library to produce a
+ * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
+ * consumer.
+ * <p>
+ * If you're using Room, it can generate a LivePagedListProvider from a query:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ * public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
+ * }</pre>
+ * In the above sample, {@code Integer} is used because it is the {@code Key} type of
+ * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET},
+ * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer}
+ * here lets you pass an initial loading position as an integer.
+ * <p>
+ * In the future, Room plans to offer other key types to support paging content with a
+ * {@link KeyedDataSource}.
+ *
+ * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
+ * you're using TiledDataSource.
+ * @param <Value> Data type produced by the DataSource, and held by the PagedLists.
+ *
+ * @see PagedListAdapter
+ * @see DataSource
+ * @see PagedList
+ */
+public abstract class LivePagedListProvider<Key, Value> {
+
+ /**
+ * Construct a new data source to be wrapped in a new PagedList, which will be returned
+ * through the LiveData.
+ *
+ * @return The data source.
+ */
+ @WorkerThread
+ protected abstract DataSource<Key, Value> createDataSource();
+
+ /**
+ * Creates a LiveData of PagedLists, given the page size.
+ * <p>
+ * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
+ * {@link android.support.v7.widget.RecyclerView}.
+ *
+ * @param initialLoadKey Initial key used to load initial data from the data source.
+ * @param pageSize Page size defining how many items are loaded from a data source at a time.
+ * Recommended to be multiple times the size of item displayed at once.
+ *
+ * @return The LiveData of PagedLists.
+ */
+ @AnyThread
+ @NonNull
+ public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
+ return create(initialLoadKey,
+ new PagedList.Config.Builder()
+ .setPageSize(pageSize)
+ .build());
+ }
+
+ /**
+ * Creates a LiveData of PagedLists, given the PagedList.Config.
+ * <p>
+ * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
+ * {@link android.support.v7.widget.RecyclerView}.
+ *
+ * @param initialLoadKey Initial key to pass to the data source to initialize data with.
+ * @param config PagedList.Config to use with created PagedLists. This specifies how the
+ * lists will load data.
+ *
+ * @return The LiveData of PagedLists.
+ */
+ @AnyThread
+ @NonNull
+ public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
+ final PagedList.Config config) {
+ return new ComputableLiveData<PagedList<Value>>() {
+ @Nullable
+ private PagedList<Value> mList;
+ @Nullable
+ private DataSource<Key, Value> mDataSource;
+
+ private final DataSource.InvalidatedCallback mCallback =
+ new DataSource.InvalidatedCallback() {
+ @Override
+ public void onInvalidated() {
+ invalidate();
+ }
+ };
+
+ @Override
+ protected PagedList<Value> compute() {
+ @Nullable Key initializeKey = initialLoadKey;
+ if (mList != null) {
+ //noinspection unchecked
+ initializeKey = (Key) mList.getLastKey();
+ }
+
+ do {
+ if (mDataSource != null) {
+ mDataSource.removeInvalidatedCallback(mCallback);
+ }
+
+ mDataSource = createDataSource();
+ mDataSource.addInvalidatedCallback(mCallback);
+
+ mList = new PagedList.Builder<Key, Value>()
+ .setDataSource(mDataSource)
+ .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor())
+ .setBackgroundThreadExecutor(
+ ArchTaskExecutor.getIOThreadExecutor())
+ .setConfig(config)
+ .setInitialKey(initializeKey)
+ .build();
+ } while (mList.isDetached());
+ return mList;
+ }
+ }.getLiveData();
+ }
+}
diff --git a/android/arch/paging/NullPaddedList.java b/android/arch/paging/NullPaddedList.java
index 43000302..c7b0b231 100644
--- a/android/arch/paging/NullPaddedList.java
+++ b/android/arch/paging/NullPaddedList.java
@@ -16,11 +16,9 @@
package android.arch.paging;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
-import java.util.ArrayList;
+import java.util.AbstractList;
import java.util.List;
/**
@@ -31,18 +29,11 @@ import java.util.List;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class NullPaddedList<Type> extends PagedList<Type> {
+public class NullPaddedList<Type> extends AbstractList<Type> {
List<Type> mList;
- int mTrailingNullCount;
- int mLeadingNullCount;
- int mPositionOffset;
-
- // track the items prepended/appended since the PagedList was initialized
- int mNumberPrepended;
- int mNumberAppended;
-
- NullPaddedList() {
- }
+ private int mTrailingNullCount;
+ private int mLeadingNullCount;
+ private int mPositionOffset;
@Override
public String toString() {
@@ -91,20 +82,6 @@ public class NullPaddedList<Type> extends PagedList<Type> {
mPositionOffset = positionOffset;
}
- /**
- * Create a copy of the passed NullPaddedList.
- *
- * @param other Other list to copy.
- */
- NullPaddedList(NullPaddedList<Type> other) {
- mLeadingNullCount = other.getLeadingNullCount();
- mList = other.isImmutable() ? other.mList : new ArrayList<>(other.mList);
- mTrailingNullCount = other.getTrailingNullCount();
-
- mNumberPrepended = other.getNumberPrepended();
- mNumberAppended = other.getNumberAppended();
- }
-
// --------------- PagedList API ---------------
@Override
@@ -124,46 +101,12 @@ public class NullPaddedList<Type> extends PagedList<Type> {
}
@Override
- public void loadAround(int index) {
- // do nothing - immutable, so no fetching will be done
- }
-
- @Override
public final int size() {
return getLoadedCount() + getLeadingNullCount() + getTrailingNullCount();
}
- public boolean isImmutable() {
- return true;
- }
-
- @Override
- public PagedList<Type> snapshot() {
- if (isImmutable()) {
- return this;
- }
- return new NullPaddedList<>(this);
- }
-
- @Override
- boolean isContiguous() {
- return true;
- }
-
- @Override
- public void addWeakCallback(@Nullable PagedList<Type> previousSnapshot,
- @NonNull Callback callback) {
- // no op, immutable
- }
-
- @Override
- public void removeWeakCallback(Callback callback) {
- // no op, immutable
- }
-
// --------------- Contiguous API ---------------
- @Override
public int getPositionOffset() {
return mPositionOffset;
}
@@ -194,12 +137,4 @@ public class NullPaddedList<Type> extends PagedList<Type> {
public int getTrailingNullCount() {
return mTrailingNullCount;
}
-
- int getNumberPrepended() {
- return mNumberPrepended;
- }
-
- int getNumberAppended() {
- return mNumberAppended;
- }
}
diff --git a/android/arch/paging/NullPaddedListTest.java b/android/arch/paging/NullPaddedListTest.java
deleted file mode 100644
index 0c38485c..00000000
--- a/android/arch/paging/NullPaddedListTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class NullPaddedListTest {
- @Test
- public void simple() {
- List<String> data = Arrays.asList("A", "B", "C", "D", "E", "F");
- NullPaddedList<String> list = new NullPaddedList<>(
- 2, data.subList(2, 4), 2);
-
- assertNull(list.get(0));
- assertNull(list.get(1));
- assertSame(data.get(2), list.get(2));
- assertSame(data.get(3), list.get(3));
- assertNull(list.get(4));
- assertNull(list.get(5));
-
- assertEquals(6, list.size());
- assertEquals(2, list.getLeadingNullCount());
- assertEquals(2, list.getTrailingNullCount());
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getEmpty() {
- NullPaddedList<String> list = new NullPaddedList<>(0, new ArrayList<String>(), 0);
- list.get(0);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getNegative() {
- NullPaddedList<String> list = new NullPaddedList<>(0, Arrays.asList("a", "b"), 0);
- list.get(-1);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getPastEnd() {
- NullPaddedList<String> list = new NullPaddedList<>(0, Arrays.asList("a", "b"), 0);
- list.get(2);
- }
-}
diff --git a/android/arch/paging/Page.java b/android/arch/paging/Page.java
new file mode 100644
index 00000000..e9890ed4
--- /dev/null
+++ b/android/arch/paging/Page.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Immutable class representing a page of data loaded from a DataSource.
+ * <p>
+ * Optionally stores before/after keys for cases where they cannot be computed, but the DataSource
+ * can provide them as part of loading a page.
+ * <p>
+ * A page's list must never be modified.
+ */
+class Page<K, V> {
+ @SuppressWarnings("WeakerAccess")
+ @Nullable
+ public final K beforeKey;
+ @NonNull
+ public final List<V> items;
+ @SuppressWarnings("WeakerAccess")
+ @Nullable
+ public K afterKey;
+
+ Page(@NonNull List<V> items) {
+ this(null, items, null);
+ }
+
+ Page(@Nullable K beforeKey, @NonNull List<V> items, @Nullable K afterKey) {
+ this.beforeKey = beforeKey;
+ this.items = items;
+ this.afterKey = afterKey;
+ }
+}
diff --git a/android/arch/paging/PageArrayList.java b/android/arch/paging/PageArrayList.java
deleted file mode 100644
index b90d055a..00000000
--- a/android/arch/paging/PageArrayList.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class PageArrayList<T> extends PagedList<T> {
- // partial list of pages, doesn't include pages below the lowest accessed, or above the highest
- final ArrayList<List<T>> mPages;
-
- // to access page at index N, do mPages.get(N - mPageIndexOffset), but do bounds checking first!
- int mPageIndexOffset;
-
- final int mPageSize;
- final int mCount;
- final int mMaxPageCount;
-
- PageArrayList(int pageSize, int count) {
- mPages = new ArrayList<>();
- mPageSize = pageSize;
- mCount = count;
- mMaxPageCount = (mCount + mPageSize - 1) / mPageSize;
- }
-
- private PageArrayList(PageArrayList<T> other) {
- mPages = other.isImmutable() ? other.mPages : new ArrayList<>(other.mPages);
- mPageIndexOffset = other.mPageIndexOffset;
- mPageSize = other.mPageSize;
- mCount = other.size();
- mMaxPageCount = other.mMaxPageCount;
- }
-
- @Override
- public T get(int index) {
- if (index < 0 || index >= mCount) {
- throw new IllegalArgumentException();
- }
-
- int localPageIndex = getLocalPageIndex(index);
-
- List<T> page = getPage(localPageIndex);
-
- if (page == null) {
- // page empty
- return null;
- }
-
- return page.get(index % mPageSize);
- }
-
- @Nullable
- private List<T> getPage(int localPageIndex) {
- if (localPageIndex < 0 || localPageIndex >= mPages.size()) {
- // page not present
- return null;
- }
-
- return mPages.get(localPageIndex);
- }
-
- private int getLocalPageIndex(int index) {
- return index / mPageSize - mPageIndexOffset;
- }
-
- @Override
- public void loadAround(int index) {
- // do nothing - immutable, so no fetching will be done
- }
-
- @Override
- public int size() {
- return mCount;
- }
-
- @Override
- public boolean isImmutable() {
- return true;
- }
-
- boolean hasPage(int pageIndex) {
- final int localPageIndex = pageIndex - mPageIndexOffset;
- List<T> page = getPage(localPageIndex);
- return page != null && page.size() != 0;
- }
-
- @Override
- public PagedList<T> snapshot() {
- if (isImmutable()) {
- return this;
- }
- return new PageArrayList<>(this);
- }
-
- @Override
- boolean isContiguous() {
- return false;
- }
-
- @Override
- public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
- @NonNull Callback callback) {
- // no op, immutable
- }
-
- @Override
- public void removeWeakCallback(Callback callback) {
- // no op, immutable
- }
-}
diff --git a/android/arch/paging/PageArrayListTest.java b/android/arch/paging/PageArrayListTest.java
deleted file mode 100644
index 135e640d..00000000
--- a/android/arch/paging/PageArrayListTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class PageArrayListTest {
- @Test
- public void simple() {
- List<String> data = Arrays.asList("A", "B", "C", "D", "E", "F");
- PageArrayList<String> list = new PageArrayList<>(2, data.size());
-
- assertEquals(2, list.mPageSize);
- assertEquals(data.size(), list.size());
- assertEquals(3, list.mMaxPageCount);
-
- for (int i = 0; i < data.size(); i++) {
- assertEquals(null, list.get(i));
- }
- for (int i = 0; i < data.size(); i += list.mPageSize) {
- list.mPages.add(data.subList(i, i + 2));
- }
- for (int i = 0; i < data.size(); i++) {
- assertEquals(data.get(i), list.get(i));
- }
- }
-}
diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java
new file mode 100644
index 00000000..a4090f61
--- /dev/null
+++ b/android/arch/paging/PageResult.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging;
+
+import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+
+class PageResult<K, V> {
+ static final int INIT = 0;
+
+ // contiguous results
+ static final int APPEND = 1;
+ static final int PREPEND = 2;
+
+ // non-contiguous, tile result
+ static final int TILE = 3;
+
+ public final int type;
+ public final Page<K, V> page;
+ @SuppressWarnings("WeakerAccess")
+ public final int leadingNulls;
+ @SuppressWarnings("WeakerAccess")
+ public final int trailingNulls;
+ @SuppressWarnings("WeakerAccess")
+ public final int positionOffset;
+
+ PageResult(int type, Page<K, V> page, int leadingNulls, int trailingNulls, int positionOffset) {
+ this.type = type;
+ this.page = page;
+ this.leadingNulls = leadingNulls;
+ this.trailingNulls = trailingNulls;
+ this.positionOffset = positionOffset;
+ }
+
+ interface Receiver<K, V> {
+ @AnyThread
+ void postOnPageResult(@NonNull PageResult<K, V> pageResult);
+ @MainThread
+ void onPageResult(@NonNull PageResult<K, V> pageResult);
+ }
+}
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 6a31b689..51f524af 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -20,9 +20,12 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+import java.lang.ref.WeakReference;
import java.util.AbstractList;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Lazy loading list that pages in content from a {@link DataSource}.
@@ -90,9 +93,30 @@ import java.util.concurrent.Executor;
* @param <T> The type of the entries in the list.
*/
public abstract class PagedList<T> extends AbstractList<T> {
- // Since we currently rely on implementation details of two implementations,
- // prevent external subclassing
- PagedList() {
+ @NonNull
+ final Executor mMainThreadExecutor;
+ @NonNull
+ final Executor mBackgroundThreadExecutor;
+ @NonNull
+ final Config mConfig;
+ @NonNull
+ final PagedStorage<?, T> mStorage;
+
+ int mLastLoad = 0;
+ T mLastItem = null;
+
+ private final AtomicBoolean mDetached = new AtomicBoolean(false);
+
+ protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+
+ PagedList(@NonNull PagedStorage<?, T> storage,
+ @NonNull Executor mainThreadExecutor,
+ @NonNull Executor backgroundThreadExecutor,
+ @NonNull Config config) {
+ mStorage = storage;
+ mMainThreadExecutor = mainThreadExecutor;
+ mBackgroundThreadExecutor = backgroundThreadExecutor;
+ mConfig = config;
}
/**
@@ -117,7 +141,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
@NonNull Executor backgroundThreadExecutor,
@NonNull Config config,
@Nullable K key) {
- if (dataSource.isContiguous() || !config.mEnablePlaceholders) {
+ if (dataSource.isContiguous() || !config.enablePlaceholders) {
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous();
@@ -280,7 +304,13 @@ public abstract class PagedList<T> extends AbstractList<T> {
*/
@Override
@Nullable
- public abstract T get(int index);
+ public T get(int index) {
+ T item = mStorage.get(index);
+ if (item != null) {
+ mLastItem = item;
+ }
+ return item;
+ }
/**
@@ -288,7 +318,10 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @param index Index at which to load.
*/
- public abstract void loadAround(int index);
+ public void loadAround(int index) {
+ mLastLoad = index + getPositionOffset();
+ loadAroundInternal(index);
+ }
/**
@@ -297,7 +330,9 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @return Current total size of the list.
*/
@Override
- public abstract int size();
+ public int size() {
+ return mStorage.size();
+ }
/**
* Returns whether the list is immutable. Immutable lists may not become mutable again, and may
@@ -305,19 +340,39 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @return True if the PagedList is immutable.
*/
- public abstract boolean isImmutable();
+ @SuppressWarnings("WeakerAccess")
+ public boolean isImmutable() {
+ return isDetached();
+ }
/**
* Returns an immutable snapshot of the PagedList. If this PagedList is already
* immutable, it will be returned.
*
- * @return Immutable snapshot of PagedList, which may be the PagedList itself.
+ * @return Immutable snapshot of PagedList data.
*/
- public abstract List<T> snapshot();
+ @NonNull
+ public List<T> snapshot() {
+ if (isImmutable()) {
+ return this;
+ }
+
+ return new SnapshotPagedList<>(this);
+ }
abstract boolean isContiguous();
/**
+ * Return the Config used to construct this PagedList.
+ *
+ * @return the Config of this PagedList
+ */
+ @NonNull
+ public Config getConfig() {
+ return mConfig;
+ }
+
+ /**
* Return the key for the position passed most recently to {@link #loadAround(int)}.
* <p>
* When a PagedList is invalidated, you can pass the key returned by this function to initialize
@@ -328,9 +383,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @return Key of position most recently passed to {@link #loadAround(int)}.
*/
@Nullable
- public Object getLastKey() {
- return null;
- }
+ public abstract Object getLastKey();
/**
* True if the PagedList has detached the DataSource it was loading from, and will no longer
@@ -338,8 +391,9 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @return True if the data source is detached.
*/
+ @SuppressWarnings("WeakerAccess")
public boolean isDetached() {
- return true;
+ return mDetached.get();
}
/**
@@ -349,7 +403,9 @@ public abstract class PagedList<T> extends AbstractList<T> {
* signal to stop loading. The PagedList will continue to present existing data, but will not
* initiate new loads.
*/
+ @SuppressWarnings("WeakerAccess")
public void detach() {
+ mDetached.set(true);
}
/**
@@ -361,7 +417,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
* If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0.
*/
public int getPositionOffset() {
- return 0;
+ return mStorage.getPositionOffset();
}
/**
@@ -385,16 +441,69 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @param callback Callback to dispatch to.
* @see #removeWeakCallback(Callback)
*/
- public abstract void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
- @NonNull Callback callback);
+ @SuppressWarnings("WeakerAccess")
+ public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
+ if (previousSnapshot != null && previousSnapshot != this) {
+ PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
+ //noinspection unchecked
+ dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
+ }
+
+ // first, clean up any empty weak refs
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback currentCallback = mCallbacks.get(i).get();
+ if (currentCallback == null) {
+ mCallbacks.remove(i);
+ }
+ }
+ // then add the new one
+ mCallbacks.add(new WeakReference<>(callback));
+ }
/**
* Removes a previously added callback.
*
* @param callback Callback, previously added.
- * @see #addWeakCallback(PagedList, Callback)
+ * @see #addWeakCallback(List, Callback)
*/
- public abstract void removeWeakCallback(Callback callback);
+ @SuppressWarnings("WeakerAccess")
+ public void removeWeakCallback(@NonNull Callback callback) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback currentCallback = mCallbacks.get(i).get();
+ if (currentCallback == null || currentCallback == callback) {
+ // found callback, or empty weak ref
+ mCallbacks.remove(i);
+ }
+ }
+ }
+
+ void notifyInserted(int position, int count) {
+ if (count != 0) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback callback = mCallbacks.get(i).get();
+ if (callback != null) {
+ callback.onInserted(position, count);
+ }
+ }
+ }
+ }
+
+ void notifyChanged(int position, int count) {
+ if (count != 0) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback callback = mCallbacks.get(i).get();
+
+ if (callback != null) {
+ callback.onChanged(position, count);
+ }
+ }
+ }
+ }
+
+ abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
+ @NonNull Callback callback);
+
+ abstract void loadAroundInternal(int index);
/**
* Callback signaling when content is loaded into the list.
@@ -442,17 +551,41 @@ public abstract class PagedList<T> extends AbstractList<T> {
* {@link Builder#setPageSize(int)}, which defines number of items loaded at a time}.
*/
public static class Config {
- final int mPageSize;
- final int mPrefetchDistance;
- final boolean mEnablePlaceholders;
- final int mInitialLoadSizeHint;
+ /**
+ * Size of each page loaded by the PagedList.
+ */
+ public final int pageSize;
+
+ /**
+ * Prefetch distance which defines how far ahead to load.
+ * <p>
+ * If this value is set to 50, the paged list will attempt to load 50 items in advance of
+ * data that's already been accessed.
+ *
+ * @see PagedList#loadAround(int)
+ */
+ @SuppressWarnings("WeakerAccess")
+ public final int prefetchDistance;
+
+ /**
+ * Defines whether the PagedList may display null placeholders, if the DataSource provides
+ * them.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public final boolean enablePlaceholders;
+
+ /**
+ * Size hint for initial load of PagedList, often larger than a regular page.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public final int initialLoadSizeHint;
private Config(int pageSize, int prefetchDistance,
boolean enablePlaceholders, int initialLoadSizeHint) {
- mPageSize = pageSize;
- mPrefetchDistance = prefetchDistance;
- mEnablePlaceholders = enablePlaceholders;
- mInitialLoadSizeHint = initialLoadSizeHint;
+ this.pageSize = pageSize;
+ this.prefetchDistance = prefetchDistance;
+ this.enablePlaceholders = enablePlaceholders;
+ this.initialLoadSizeHint = initialLoadSizeHint;
}
/**
@@ -545,10 +678,15 @@ public abstract class PagedList<T> extends AbstractList<T> {
* Defines how many items to load when first load occurs, if you are using a
* {@link KeyedDataSource}.
* <p>
- * If you are using an {@link TiledDataSource}, this value is currently ignored.
- * Otherwise, this value will be passed to
- * {@link KeyedDataSource#loadInitial(int)} to load a (typically) larger amount
- * of data on first load.
+ * This value is typically larger than page size, so on first load data there's a large
+ * enough range of content loaded to cover small scrolls.
+ * <p>
+ * If used with a {@link TiledDataSource}, this value is rounded to the nearest number
+ * of pages, with a minimum of two pages, and loaded with a single call to
+ * {@link TiledDataSource#loadRange(int, int)}.
+ * <p>
+ * If used with a {@link KeyedDataSource}, this value will be passed to
+ * {@link KeyedDataSource#loadInitial(int)}.
* <p>
* If not set, defaults to three times page size.
*
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index c7b61d9f..abcff415 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -25,8 +25,6 @@ import android.support.v7.util.DiffUtil;
import android.support.v7.util.ListUpdateCallback;
import android.support.v7.widget.RecyclerView;
-import java.util.List;
-
/**
* Helper object for mapping a {@link PagedList} into a
* {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}.
@@ -120,15 +118,15 @@ import java.util.List;
* @param <T> Type of the PagedLists this helper will receive.
*/
public class PagedListAdapterHelper<T> {
+ // updateCallback notifications must only be notified *after* new data and item count are stored
+ // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data
private final ListUpdateCallback mUpdateCallback;
private final ListAdapterConfig<T> mConfig;
- // true if our listener is detached from mList, because it's been snapshotted
- private boolean mUpdateScheduled;
-
private boolean mIsContiguous;
- private PagedList<T> mList;
+ private PagedList<T> mPagedList;
+ private PagedList<T> mSnapshot;
// Max generation of currently scheduled runnable
private int mMaxScheduledGeneration;
@@ -182,12 +180,17 @@ public class PagedListAdapterHelper<T> {
@SuppressWarnings("WeakerAccess")
@Nullable
public T getItem(int index) {
- if (mList == null) {
- throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid");
+ if (mPagedList == null) {
+ if (mSnapshot == null) {
+ throw new IndexOutOfBoundsException(
+ "Item count is zero, getItem() call is invalid");
+ } else {
+ return mSnapshot.get(index);
+ }
}
- mList.loadAround(index);
- return mList.get(index);
+ mPagedList.loadAround(index);
+ return mPagedList.get(index);
}
/**
@@ -198,7 +201,11 @@ public class PagedListAdapterHelper<T> {
*/
@SuppressWarnings("WeakerAccess")
public int getItemCount() {
- return mList == null ? 0 : mList.size();
+ if (mPagedList != null) {
+ return mPagedList.size();
+ }
+
+ return mSnapshot == null ? 0 : mSnapshot.size();
}
/**
@@ -212,7 +219,7 @@ public class PagedListAdapterHelper<T> {
*/
public void setList(final PagedList<T> pagedList) {
if (pagedList != null) {
- if (mList == null) {
+ if (mPagedList == null && mSnapshot == null) {
mIsContiguous = pagedList.isContiguous();
} else {
if (pagedList.isContiguous() != mIsContiguous) {
@@ -222,7 +229,7 @@ public class PagedListAdapterHelper<T> {
}
}
- if (pagedList == mList) {
+ if (pagedList == mPagedList) {
// nothing to do
return;
}
@@ -231,49 +238,55 @@ public class PagedListAdapterHelper<T> {
final int runGeneration = ++mMaxScheduledGeneration;
if (pagedList == null) {
- mUpdateCallback.onRemoved(0, mList.size());
- mList.removeWeakCallback(mPagedListCallback);
- mList = null;
+ int removedCount = getItemCount();
+ if (mPagedList != null) {
+ mPagedList.removeWeakCallback(mPagedListCallback);
+ mPagedList = null;
+ } else if (mSnapshot != null) {
+ mSnapshot = null;
+ }
+ // dispatch update callback after updating mPagedList/mSnapshot
+ mUpdateCallback.onRemoved(0, removedCount);
return;
}
- if (mList == null) {
+ if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
- mUpdateCallback.onInserted(0, pagedList.size());
- mList = pagedList;
+ mPagedList = pagedList;
pagedList.addWeakCallback(null, mPagedListCallback);
+
+ // dispatch update callback after updating mPagedList/mSnapshot
+ mUpdateCallback.onInserted(0, pagedList.size());
return;
}
- if (!mList.isImmutable()) {
+ if (mPagedList != null) {
// first update scheduled on this list, so capture mPages as a snapshot, removing
// callbacks so we don't have resolve updates against a moving target
- mList.removeWeakCallback(mPagedListCallback);
- mList = (PagedList<T>) mList.snapshot();
+ mPagedList.removeWeakCallback(mPagedListCallback);
+ mSnapshot = (PagedList<T>) mPagedList.snapshot();
+ mPagedList = null;
+ }
+
+ if (mSnapshot == null || mPagedList != null) {
+ throw new IllegalStateException("must be in snapshot state to diff");
}
- final PagedList<T> oldSnapshot = mList;
- final List<T> newSnapshot = pagedList.snapshot();
- mUpdateScheduled = true;
+ final PagedList<T> oldSnapshot = mSnapshot;
+ final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
- if (mIsContiguous) {
- result = ContiguousDiffHelper.computeDiff(
- (NullPaddedList<T>) oldSnapshot, (NullPaddedList<T>) newSnapshot,
- mConfig.getDiffCallback(), true);
- } else {
- result = SparseDiffHelper.computeDiff(
- (PageArrayList<T>) oldSnapshot, (PageArrayList<T>) newSnapshot,
- mConfig.getDiffCallback(), true);
- }
+ result = PagedStorageDiffHelper.computeDiff(
+ oldSnapshot.mStorage,
+ newSnapshot.mStorage,
+ mConfig.getDiffCallback());
mConfig.getMainThreadExecutor().execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
- mUpdateScheduled = false;
latchPagedList(pagedList, newSnapshot, result);
}
}
@@ -283,16 +296,21 @@ public class PagedListAdapterHelper<T> {
}
private void latchPagedList(
- PagedList<T> newList, List<T> diffSnapshot,
+ PagedList<T> newList, PagedList<T> diffSnapshot,
DiffUtil.DiffResult diffResult) {
- if (mIsContiguous) {
- ContiguousDiffHelper.dispatchDiff(mUpdateCallback,
- (NullPaddedList<T>) mList, (ContiguousPagedList<T>) newList, diffResult);
- } else {
- SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult);
+ if (mSnapshot == null || mPagedList != null) {
+ throw new IllegalStateException("must be in snapshot state to apply diff");
}
- mList = newList;
- newList.addWeakCallback((PagedList<T>) diffSnapshot, mPagedListCallback);
+
+ PagedList<T> previousSnapshot = mSnapshot;
+ mPagedList = newList;
+ mSnapshot = null;
+
+ // dispatch update callback after updating mPagedList/mSnapshot
+ PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
+ previousSnapshot.mStorage, newList.mStorage, diffResult);
+
+ newList.addWeakCallback(diffSnapshot, mPagedListCallback);
}
/**
@@ -307,6 +325,9 @@ public class PagedListAdapterHelper<T> {
@SuppressWarnings("WeakerAccess")
@Nullable
public PagedList<T> getCurrentList() {
- return mList;
+ if (mSnapshot != null) {
+ return mSnapshot;
+ }
+ return mPagedList;
}
}
diff --git a/android/arch/paging/PagedListAdapterHelperTest.java b/android/arch/paging/PagedListAdapterHelperTest.java
deleted file mode 100644
index 3518540c..00000000
--- a/android/arch/paging/PagedListAdapterHelperTest.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.v7.recyclerview.extensions.DiffCallback;
-import android.support.v7.recyclerview.extensions.ListAdapterConfig;
-import android.support.v7.util.ListUpdateCallback;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public class PagedListAdapterHelperTest {
- private TestExecutor mMainThread = new TestExecutor();
- private TestExecutor mDiffThread = new TestExecutor();
- private TestExecutor mPageLoadingThread = new TestExecutor();
-
- private static final ArrayList<String> ALPHABET_LIST = new ArrayList<>();
- static {
- for (int i = 0; i < 26; i++) {
- ALPHABET_LIST.add("" + 'a' + i);
- }
- }
-
- private static final DiffCallback<String> STRING_DIFF_CALLBACK = new DiffCallback<String>() {
- @Override
- public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
- return oldItem.equals(newItem);
- }
-
- @Override
- public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
- return oldItem.equals(newItem);
- }
- };
-
- private static final ListUpdateCallback IGNORE_CALLBACK = new ListUpdateCallback() {
- @Override
- public void onInserted(int position, int count) {
- }
-
- @Override
- public void onRemoved(int position, int count) {
- }
-
- @Override
- public void onMoved(int fromPosition, int toPosition) {
- }
-
- @Override
- public void onChanged(int position, int count, Object payload) {
- }
- };
-
-
- private <T> PagedListAdapterHelper<T> createHelper(
- ListUpdateCallback listUpdateCallback, DiffCallback<T> diffCallback) {
- return new PagedListAdapterHelper<T>(listUpdateCallback,
- new ListAdapterConfig.Builder<T>()
- .setDiffCallback(diffCallback)
- .setMainThreadExecutor(mMainThread)
- .setBackgroundThreadExecutor(mDiffThread)
- .build());
- }
-
- private <V> PagedList<V> createPagedListFromListAndPos(
- PagedList.Config config, List<V> data, int initialKey) {
- return new PagedList.Builder<Integer, V>()
- .setInitialKey(initialKey)
- .setConfig(config)
- .setMainThreadExecutor(mMainThread)
- .setBackgroundThreadExecutor(mPageLoadingThread)
- .setDataSource(new ListDataSource<>(data))
- .build();
- }
-
- @Test
- public void initialState() {
- ListUpdateCallback callback = mock(ListUpdateCallback.class);
- PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
- assertEquals(null, helper.getCurrentList());
- assertEquals(0, helper.getItemCount());
- verifyZeroInteractions(callback);
- }
-
- @Test
- public void setFullList() {
- ListUpdateCallback callback = mock(ListUpdateCallback.class);
- PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
- helper.setList(new StringPagedList(0, 0, "a", "b"));
-
- assertEquals(2, helper.getItemCount());
- assertEquals("a", helper.getItem(0));
- assertEquals("b", helper.getItem(1));
-
- verify(callback).onInserted(0, 2);
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getEmpty() {
- PagedListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
- helper.getItem(0);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getNegative() {
- PagedListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
- helper.setList(new StringPagedList(0, 0, "a", "b"));
- helper.getItem(-1);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getPastEnd() {
- PagedListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
- helper.setList(new StringPagedList(0, 0, "a", "b"));
- helper.getItem(2);
- }
-
- @Test
- public void simpleStatic() {
- ListUpdateCallback callback = mock(ListUpdateCallback.class);
- PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
- assertEquals(0, helper.getItemCount());
-
- helper.setList(new StringPagedList(2, 2, "a", "b"));
-
- verify(callback).onInserted(0, 6);
- verifyNoMoreInteractions(callback);
- assertEquals(6, helper.getItemCount());
-
- assertNull(helper.getItem(0));
- assertNull(helper.getItem(1));
- assertEquals("a", helper.getItem(2));
- assertEquals("b", helper.getItem(3));
- assertNull(helper.getItem(4));
- assertNull(helper.getItem(5));
- }
-
- @Test
- public void pagingInContent() {
- PagedList.Config config = new PagedList.Config.Builder()
- .setInitialLoadSizeHint(4)
- .setPageSize(2)
- .setPrefetchDistance(2)
- .build();
-
- final ListUpdateCallback callback = mock(ListUpdateCallback.class);
- PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
- helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2));
- verify(callback).onInserted(0, ALPHABET_LIST.size());
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
-
- // get without triggering prefetch...
- helper.getItem(1);
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
-
- // get triggering prefetch...
- helper.getItem(2);
- verifyNoMoreInteractions(callback);
- drain();
- verify(callback).onChanged(4, 2, null);
- verifyNoMoreInteractions(callback);
-
- // get with no data loaded nearby...
- helper.getItem(12);
- verifyNoMoreInteractions(callback);
- drain();
- verify(callback).onChanged(10, 2, null);
- verify(callback).onChanged(12, 2, null);
- verify(callback).onChanged(14, 2, null);
- verifyNoMoreInteractions(callback);
-
- // finally, clear
- helper.setList(null);
- verify(callback).onRemoved(0, 26);
- drain();
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void simpleSwap() {
- // Page size large enough to load
- PagedList.Config config = new PagedList.Config.Builder()
- .setPageSize(50)
- .build();
-
- final ListUpdateCallback callback = mock(ListUpdateCallback.class);
- PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
- // initial list missing one item (immediate)
- helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 25), 0));
- verify(callback).onInserted(0, 25);
- verifyNoMoreInteractions(callback);
- assertEquals(helper.getItemCount(), 25);
- drain();
- verifyNoMoreInteractions(callback);
-
- // pass second list with full data
- helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 0));
- verifyNoMoreInteractions(callback);
- drain();
- verify(callback).onInserted(25, 1);
- verifyNoMoreInteractions(callback);
- assertEquals(helper.getItemCount(), 26);
-
- // finally, clear (immediate)
- helper.setList(null);
- verify(callback).onRemoved(0, 26);
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void newPageWhileDiffing() {
- PagedList.Config config = new PagedList.Config.Builder()
- .setInitialLoadSizeHint(4)
- .setPageSize(2)
- .setPrefetchDistance(2)
- .build();
-
- final ListUpdateCallback callback = mock(ListUpdateCallback.class);
- PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
- helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2));
- verify(callback).onInserted(0, ALPHABET_LIST.size());
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
- assertNotNull(helper.getCurrentList());
- assertFalse(helper.getCurrentList().isImmutable());
-
- // trigger page loading
- helper.getItem(10);
- helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2));
- verifyNoMoreInteractions(callback);
-
- // drain page fetching, but list became immutable, page will be ignored
- drainExceptDiffThread();
- verifyNoMoreInteractions(callback);
- assertNotNull(helper.getCurrentList());
- assertTrue(helper.getCurrentList().isImmutable());
-
- // finally full drain, which signals nothing, since 1st pagedlist == 2nd pagedlist
- drain();
- verifyNoMoreInteractions(callback);
- assertNotNull(helper.getCurrentList());
- assertFalse(helper.getCurrentList().isImmutable());
- }
-
- private void drainExceptDiffThread() {
- boolean executed;
- do {
- executed = mPageLoadingThread.executeAll();
- executed |= mMainThread.executeAll();
- } while (executed);
- }
-
- private void drain() {
- boolean executed;
- do {
- executed = mPageLoadingThread.executeAll();
- executed |= mDiffThread.executeAll();
- executed |= mMainThread.executeAll();
- } while (executed);
- }
-}
diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java
new file mode 100644
index 00000000..7f91290d
--- /dev/null
+++ b/android/arch/paging/PagedStorage.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging;
+
+import android.support.annotation.NonNull;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+final class PagedStorage<K, V> extends AbstractList<V> {
+ // Always set
+ private int mLeadingNullCount;
+ /**
+ * List of pages in storage.
+ *
+ * Two storage modes:
+ *
+ * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled().
+ * Safe to access any item in any page.
+ *
+ * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
+ * mPages may have nulls, or placeholder (empty) pages while content is loading.
+ */
+ private final ArrayList<Page<K, V>> mPages;
+ private int mTrailingNullCount;
+
+ private int mPositionOffset;
+ /**
+ * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in
+ * {@link #mPages} may be null, but this value still counts them.
+ */
+ private int mStorageCount;
+
+ // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set
+ private int mPageSize;
+
+ private int mNumberPrepended;
+ private int mNumberAppended;
+
+ // only used in tiling case
+ private Page<K, V> mPlaceholderPage;
+
+ PagedStorage() {
+ mLeadingNullCount = 0;
+ mPages = new ArrayList<>();
+ mTrailingNullCount = 0;
+ mPositionOffset = 0;
+ mStorageCount = 0;
+ mPageSize = 1;
+ mNumberPrepended = 0;
+ mNumberAppended = 0;
+ }
+
+ PagedStorage(int leadingNulls, Page<K, V> page, int trailingNulls) {
+ this();
+ init(leadingNulls, page, trailingNulls, 0);
+ }
+
+ private PagedStorage(PagedStorage<K, V> other) {
+ mLeadingNullCount = other.mLeadingNullCount;
+ mPages = new ArrayList<>(other.mPages);
+ mTrailingNullCount = other.mTrailingNullCount;
+ mPositionOffset = other.mPositionOffset;
+ mStorageCount = other.mStorageCount;
+ mPageSize = other.mPageSize;
+ mNumberPrepended = other.mNumberPrepended;
+ mNumberAppended = other.mNumberAppended;
+
+ // preserve placeholder page so we can locate placeholder pages if needed later
+ mPlaceholderPage = other.mPlaceholderPage;
+ }
+
+ PagedStorage<K, V> snapshot() {
+ return new PagedStorage<>(this);
+ }
+
+ private void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset) {
+ mLeadingNullCount = leadingNulls;
+ mPages.clear();
+ mPages.add(page);
+ mTrailingNullCount = trailingNulls;
+
+ mPositionOffset = positionOffset;
+ mStorageCount = page.items.size();
+
+ // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
+ // even if it will break if nulls convert.
+ mPageSize = page.items.size();
+
+ mNumberPrepended = 0;
+ mNumberAppended = 0;
+ }
+
+ void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset,
+ @NonNull Callback callback) {
+ init(leadingNulls, page, trailingNulls, positionOffset);
+ callback.onInitialized(size());
+ }
+
+ @Override
+ public V get(int i) {
+ if (i < 0 || i >= size()) {
+ throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
+ }
+
+ // is it definitely outside 'mPages'?
+ int localIndex = i - mLeadingNullCount;
+ if (localIndex < 0 || localIndex >= mStorageCount) {
+ return null;
+ }
+
+ int localPageIndex;
+ int pageInternalIndex;
+
+ if (isTiled()) {
+ // it's inside mPages, and we're tiled. Jump to correct tile.
+ localPageIndex = localIndex / mPageSize;
+ pageInternalIndex = localIndex % mPageSize;
+ } else {
+ // it's inside mPages, but page sizes aren't regular. Walk to correct tile.
+ // Pages can only be null while tiled, so accessing page count is safe.
+ pageInternalIndex = localIndex;
+ final int localPageCount = mPages.size();
+ for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
+ int pageSize = mPages.get(localPageIndex).items.size();
+ if (pageSize > pageInternalIndex) {
+ // stop, found the page
+ break;
+ }
+ pageInternalIndex -= pageSize;
+ }
+ }
+
+ Page<?, V> page = mPages.get(localPageIndex);
+ if (page == null || page.items.size() == 0) {
+ // can only occur in tiled case, with untouched inner/placeholder pages
+ return null;
+ }
+ return page.items.get(pageInternalIndex);
+ }
+
+ /**
+ * Returns true if all pages are the same size, except for the last, which may be smaller
+ */
+ boolean isTiled() {
+ return mPageSize > 0;
+ }
+
+ int getLeadingNullCount() {
+ return mLeadingNullCount;
+ }
+
+ int getTrailingNullCount() {
+ return mTrailingNullCount;
+ }
+
+ int getStorageCount() {
+ return mStorageCount;
+ }
+
+ int getNumberAppended() {
+ return mNumberAppended;
+ }
+
+ int getNumberPrepended() {
+ return mNumberPrepended;
+ }
+
+ int getPageCount() {
+ return mPages.size();
+ }
+
+ interface Callback {
+ void onInitialized(int count);
+ void onPagePrepended(int leadingNulls, int changed, int added);
+ void onPageAppended(int endPosition, int changed, int added);
+ void onPagePlaceholderInserted(int pageIndex);
+ void onPageInserted(int start, int count);
+ }
+
+ int getPositionOffset() {
+ return mPositionOffset;
+ }
+
+ @Override
+ public int size() {
+ return mLeadingNullCount + mStorageCount + mTrailingNullCount;
+ }
+
+ int computeLeadingNulls() {
+ int total = mLeadingNullCount;
+ final int pageCount = mPages.size();
+ for (int i = 0; i < pageCount; i++) {
+ Page page = mPages.get(i);
+ if (page != null && page != mPlaceholderPage) {
+ break;
+ }
+ total += mPageSize;
+ }
+ return total;
+ }
+
+ int computeTrailingNulls() {
+ int total = mTrailingNullCount;
+ for (int i = mPages.size() - 1; i >= 0; i--) {
+ Page page = mPages.get(i);
+ if (page != null && page != mPlaceholderPage) {
+ break;
+ }
+ total += mPageSize;
+ }
+ return total;
+ }
+
+ // ---------------- Contiguous API -------------------
+
+ V getFirstContiguousItem() {
+ // safe to access first page's first item here:
+ // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+ return mPages.get(0).items.get(0);
+ }
+
+ V getLastContiguousItem() {
+ // safe to access last page's last item here:
+ // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+ Page<K, V> page = mPages.get(mPages.size() - 1);
+ return page.items.get(page.items.size() - 1);
+ }
+
+ public void prependPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
+ final int count = page.items.size();
+ if (count == 0) {
+ // Nothing returned from source, stop loading in this direction
+ return;
+ }
+ if (mPageSize > 0 && count != mPageSize) {
+ if (mPages.size() == 1 && count > mPageSize) {
+ // prepending to a single item - update current page size to that of 'inner' page
+ mPageSize = count;
+ } else {
+ // no longer tiled
+ mPageSize = -1;
+ }
+ }
+
+ mPages.add(0, page);
+ mStorageCount += count;
+
+ final int changedCount = Math.min(mLeadingNullCount, count);
+ final int addedCount = count - changedCount;
+
+ if (changedCount != 0) {
+ mLeadingNullCount -= changedCount;
+ }
+ mPositionOffset -= addedCount;
+ mNumberPrepended += count;
+
+ callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
+ }
+
+ public void appendPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
+ final int count = page.items.size();
+ if (count == 0) {
+ // Nothing returned from source, stop loading in this direction
+ return;
+ }
+
+ if (mPageSize > 0) {
+ // if the previous page was smaller than mPageSize,
+ // or if this page is larger than the previous, disable tiling
+ if (mPages.get(mPages.size() - 1).items.size() != mPageSize
+ || count > mPageSize) {
+ mPageSize = -1;
+ }
+ }
+
+ mPages.add(page);
+ mStorageCount += count;
+
+ final int changedCount = Math.min(mTrailingNullCount, count);
+ final int addedCount = count - changedCount;
+
+ if (changedCount != 0) {
+ mTrailingNullCount -= changedCount;
+ }
+ mNumberAppended += count;
+ callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
+ changedCount, addedCount);
+ }
+
+ // ------------------ Non-Contiguous API (tiling required) ----------------------
+
+ public void insertPage(int position, @NonNull Page<K, V> page, Callback callback) {
+ final int newPageSize = page.items.size();
+ if (newPageSize != mPageSize) {
+ // differing page size is OK in 2 cases, when the page is being added:
+ // 1) to the end (in which case, ignore new smaller size)
+ // 2) only the last page has been added so far (in which case, adopt new bigger size)
+
+ int size = size();
+ boolean addingLastPage = position == (size - size % mPageSize)
+ && newPageSize < mPageSize;
+ boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1
+ && newPageSize > mPageSize;
+
+ // OK only if existing single page, and it's the last one
+ if (!onlyEndPagePresent && !addingLastPage) {
+ throw new IllegalArgumentException("page introduces incorrect tiling");
+ }
+ if (onlyEndPagePresent) {
+ mPageSize = newPageSize;
+ }
+ }
+
+ int pageIndex = position / mPageSize;
+
+ allocatePageRange(pageIndex, pageIndex);
+
+ int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
+
+ Page<K, V> oldPage = mPages.get(localPageIndex);
+ if (oldPage != null && oldPage != mPlaceholderPage) {
+ throw new IllegalArgumentException(
+ "Invalid position " + position + ": data already loaded");
+ }
+ mPages.set(localPageIndex, page);
+ callback.onPageInserted(position, page.items.size());
+ }
+
+ private Page<K, V> getPlaceholderPage() {
+ if (mPlaceholderPage == null) {
+ @SuppressWarnings("unchecked")
+ List<V> list = Collections.emptyList();
+ mPlaceholderPage = new Page<>(null, list, null);
+ }
+ return mPlaceholderPage;
+ }
+
+ private void allocatePageRange(final int minimumPage, final int maximumPage) {
+ int leadingNullPages = mLeadingNullCount / mPageSize;
+
+ if (minimumPage < leadingNullPages) {
+ for (int i = 0; i < leadingNullPages - minimumPage; i++) {
+ mPages.add(0, null);
+ }
+ int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize;
+ mStorageCount += newStorageAllocated;
+ mLeadingNullCount -= newStorageAllocated;
+
+ leadingNullPages = minimumPage;
+ }
+ if (maximumPage >= leadingNullPages + mPages.size()) {
+ int newStorageAllocated = Math.min(mTrailingNullCount,
+ (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize);
+ for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) {
+ mPages.add(mPages.size(), null);
+ }
+ mStorageCount += newStorageAllocated;
+ mTrailingNullCount -= newStorageAllocated;
+ }
+ }
+
+ public void allocatePlaceholders(int index, int prefetchDistance,
+ int pageSize, Callback callback) {
+ if (pageSize != mPageSize) {
+ if (pageSize < mPageSize) {
+ throw new IllegalArgumentException("Page size cannot be reduced");
+ }
+ if (mPages.size() != 1 || mTrailingNullCount != 0) {
+ // not in single, last page allocated case - can't change page size
+ throw new IllegalArgumentException(
+ "Page size can change only if last page is only one present");
+ }
+ mPageSize = pageSize;
+ }
+
+ final int maxPageCount = (size() + mPageSize - 1) / mPageSize;
+ int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0);
+ int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1);
+
+ allocatePageRange(minimumPage, maximumPage);
+ int leadingNullPages = mLeadingNullCount / mPageSize;
+ for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
+ int localPageIndex = pageIndex - leadingNullPages;
+ if (mPages.get(localPageIndex) == null) {
+ mPages.set(localPageIndex, getPlaceholderPage());
+ callback.onPagePlaceholderInserted(pageIndex);
+ }
+ }
+ }
+
+ public boolean hasPage(int pageSize, int index) {
+ // NOTE: we pass pageSize here to avoid in case mPageSize
+ // not fully initialized (when last page only one loaded)
+ int leadingNullPages = mLeadingNullCount / pageSize;
+
+ if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) {
+ return false;
+ }
+
+ Page<K, V> page = mPages.get(index - leadingNullPages);
+
+ return page != null && page != mPlaceholderPage;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount
+ + ", storage " + mStorageCount
+ + ", trailing " + getTrailingNullCount());
+
+ for (int i = 0; i < mPages.size(); i++) {
+ ret.append(" ").append(mPages.get(i));
+ }
+ return ret.toString();
+ }
+}
diff --git a/android/arch/paging/ContiguousDiffHelper.java b/android/arch/paging/PagedStorageDiffHelper.java
index 7dd194b2..6fc70390 100644
--- a/android/arch/paging/ContiguousDiffHelper.java
+++ b/android/arch/paging/PagedStorageDiffHelper.java
@@ -16,36 +16,31 @@
package android.arch.paging;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.support.v7.recyclerview.extensions.DiffCallback;
import android.support.v7.util.DiffUtil;
import android.support.v7.util.ListUpdateCallback;
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ContiguousDiffHelper {
- private ContiguousDiffHelper() {
+class PagedStorageDiffHelper {
+ private PagedStorageDiffHelper() {
}
- @NonNull
static <T> DiffUtil.DiffResult computeDiff(
- final NullPaddedList<T> oldList, final NullPaddedList<T> newList,
- final DiffCallback<T> diffCallback, boolean detectMoves) {
+ final PagedStorage<?, T> oldList,
+ final PagedStorage<?, T> newList,
+ final DiffCallback<T> diffCallback) {
+ final int oldOffset = oldList.computeLeadingNulls();
+ final int newOffset = newList.computeLeadingNulls();
+
+ final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls();
+ final int newSize = newList.size() - newOffset - newList.computeTrailingNulls();
- if (!oldList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
- if (!newList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
return DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.mList.get(oldItemPosition);
- T newItem = newList.mList.get(newItemPosition);
+ T oldItem = oldList.get(oldItemPosition + oldOffset);
+ T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == null || newItem == null) {
return null;
}
@@ -54,21 +49,22 @@ class ContiguousDiffHelper {
@Override
public int getOldListSize() {
- return oldList.mList.size();
+ return oldSize;
}
@Override
public int getNewListSize() {
- return newList.mList.size();
+ return newSize;
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.mList.get(oldItemPosition);
- T newItem = newList.mList.get(newItemPosition);
+ T oldItem = oldList.get(oldItemPosition + oldOffset);
+ T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == newItem) {
return true;
}
+ //noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}
@@ -77,18 +73,19 @@ class ContiguousDiffHelper {
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.mList.get(oldItemPosition);
- T newItem = newList.mList.get(newItemPosition);
+ T oldItem = oldList.get(oldItemPosition + oldOffset);
+ T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == newItem) {
return true;
}
+ //noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}
return diffCallback.areContentsTheSame(oldItem, newItem);
}
- }, detectMoves);
+ }, true);
}
private static class OffsettingListUpdateCallback implements ListUpdateCallback {
@@ -134,21 +131,25 @@ class ContiguousDiffHelper {
* immediately after dispatching this diff.
*/
static <T> void dispatchDiff(ListUpdateCallback callback,
- final NullPaddedList<T> oldList, final NullPaddedList<T> newList,
+ final PagedStorage<?, T> oldList,
+ final PagedStorage<?, T> newList,
final DiffUtil.DiffResult diffResult) {
- if (oldList.getLeadingNullCount() == 0
- && oldList.getTrailingNullCount() == 0
- && newList.getLeadingNullCount() == 0
- && newList.getTrailingNullCount() == 0) {
+ final int trailingOld = oldList.computeTrailingNulls();
+ final int trailingNew = newList.computeTrailingNulls();
+ final int leadingOld = oldList.computeLeadingNulls();
+ final int leadingNew = newList.computeLeadingNulls();
+
+ if (trailingOld == 0
+ && trailingNew == 0
+ && leadingOld == 0
+ && leadingNew == 0) {
// Simple case, dispatch & return
diffResult.dispatchUpdatesTo(callback);
return;
}
// First, remove or insert trailing nulls
- final int trailingOld = oldList.getTrailingNullCount();
- final int trailingNew = newList.getTrailingNullCount();
if (trailingOld > trailingNew) {
int count = trailingOld - trailingNew;
callback.onRemoved(oldList.size() - count, count);
@@ -157,8 +158,6 @@ class ContiguousDiffHelper {
}
// Second, remove or insert leading nulls
- final int leadingOld = oldList.getLeadingNullCount();
- final int leadingNew = newList.getLeadingNullCount();
if (leadingOld > leadingNew) {
callback.onRemoved(0, leadingOld - leadingNew);
} else if (leadingOld < leadingNew) {
diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java
index deb51e94..c538cb60 100644
--- a/android/arch/paging/PositionalDataSource.java
+++ b/android/arch/paging/PositionalDataSource.java
@@ -42,6 +42,17 @@ import java.util.List;
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
+
+ /**
+ * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
+ *
+ * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
+ * if difficult or undesired to compute.
+ */
+ public int countItems() {
+ return COUNT_UNDEFINED;
+ }
+
@Nullable
@Override
List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) {
@@ -55,16 +66,7 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I
return loadBefore(currentBeginIndex - 1, pageSize);
}
-
- /**
- * Load initial data, starting after the passed position.
- *
- * @param position Index just before the data to be loaded.
- * @param initialLoadSize Suggested number of items to load.
- * @return List of initial items, representing data starting at position. Null if the
- * DataSource is no longer valid, and should not be queried again.
- * @hide
- */
+ /** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -118,6 +120,9 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I
@Override
Integer getKey(int position, Value item) {
+ if (position < 0) {
+ return null;
+ }
return position;
}
}
diff --git a/android/arch/paging/SnapshotPagedList.java b/android/arch/paging/SnapshotPagedList.java
new file mode 100644
index 00000000..7e965a0f
--- /dev/null
+++ b/android/arch/paging/SnapshotPagedList.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+class SnapshotPagedList<T> extends PagedList<T> {
+ private final boolean mContiguous;
+ private final Object mLastKey;
+
+ SnapshotPagedList(@NonNull PagedList<T> pagedList) {
+ super(pagedList.mStorage.snapshot(),
+ pagedList.mMainThreadExecutor,
+ pagedList.mBackgroundThreadExecutor,
+ pagedList.mConfig);
+ mContiguous = pagedList.isContiguous();
+ mLastKey = pagedList.getLastKey();
+ }
+
+ @Override
+ public boolean isImmutable() {
+ return true;
+ }
+
+ @Override
+ public boolean isDetached() {
+ return true;
+ }
+
+ @Override
+ boolean isContiguous() {
+ return mContiguous;
+ }
+
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return mLastKey;
+ }
+
+ @Override
+ void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot,
+ @NonNull Callback callback) {
+ }
+
+ @Override
+ void loadAroundInternal(int index) {
+ }
+}
diff --git a/android/arch/paging/SparseDiffHelper.java b/android/arch/paging/SparseDiffHelper.java
deleted file mode 100644
index fe478973..00000000
--- a/android/arch/paging/SparseDiffHelper.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.v7.recyclerview.extensions.DiffCallback;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class SparseDiffHelper {
- private SparseDiffHelper() {
- }
-
- @NonNull
- static <T> DiffUtil.DiffResult computeDiff(
- final PageArrayList<T> oldList, final PageArrayList<T> newList,
- final DiffCallback<T> diffCallback, boolean detectMoves) {
-
- if (!oldList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
- if (!newList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
- return DiffUtil.calculateDiff(new DiffUtil.Callback() {
- @Nullable
- @Override
- public Object getChangePayload(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition);
- T newItem = newList.get(newItemPosition);
- if (oldItem == null || newItem == null) {
- return null;
- }
- return diffCallback.getChangePayload(oldItem, newItem);
- }
-
- @Override
- public int getOldListSize() {
- return oldList.size();
- }
-
- @Override
- public int getNewListSize() {
- return newList.size();
- }
-
- @Override
- public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition);
- T newItem = newList.get(newItemPosition);
- if (oldItem == newItem) {
- return true;
- }
- if (oldItem == null || newItem == null) {
- return false;
- }
- return diffCallback.areItemsTheSame(oldItem, newItem);
- }
-
- @Override
- public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition);
- T newItem = newList.get(newItemPosition);
- if (oldItem == newItem) {
- return true;
- }
- if (oldItem == null || newItem == null) {
- return false;
- }
-
- return diffCallback.areContentsTheSame(oldItem, newItem);
- }
- }, detectMoves);
- }
-
- static <T> void dispatchDiff(ListUpdateCallback callback,
- final DiffUtil.DiffResult diffResult) {
- // Simple case, dispatch & return
- diffResult.dispatchUpdatesTo(callback);
- }
-}
diff --git a/android/arch/paging/StringPagedList.java b/android/arch/paging/StringPagedList.java
deleted file mode 100644
index 5318d38c..00000000
--- a/android/arch/paging/StringPagedList.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import java.util.Arrays;
-
-public class StringPagedList extends NullPaddedList<String> {
- public StringPagedList(int leadingNulls, int trailingNulls, String... items) {
- super(leadingNulls, Arrays.asList(items), trailingNulls);
- }
-}
diff --git a/android/arch/paging/TestExecutor.java b/android/arch/paging/TestExecutor.java
deleted file mode 100644
index 30809c3e..00000000
--- a/android/arch/paging/TestExecutor.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.Executor;
-
-public class TestExecutor implements Executor {
- private Queue<Runnable> mTasks = new LinkedList<>();
-
- @Override
- public void execute(@NonNull Runnable command) {
- mTasks.add(command);
- }
-
- boolean executeAll() {
- boolean consumed = !mTasks.isEmpty();
- Runnable task;
- while ((task = mTasks.poll()) != null) {
- task.run();
- }
- return consumed;
- }
-}
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 36be423d..61dead3a 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -19,6 +19,7 @@ package android.arch.paging;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+import java.util.Collections;
import java.util.List;
/**
@@ -92,7 +93,6 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
* @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
*/
@WorkerThread
- @Override
public abstract int countItems();
@Override
@@ -118,7 +118,61 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
@WorkerThread
public abstract List<Type> loadRange(int startPosition, int count);
- final List<Type> loadRangeWrapper(int startPosition, int count) {
+ /**
+ * blocking, and splits pages
+ */
+ void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
+ PageResult.Receiver<Integer, Type> receiver) {
+
+ if (itemCount == 0) {
+ // no data to load, just immediately return empty
+ receiver.onPageResult(new PageResult<>(
+ PageResult.INIT, new Page<Integer, Type>(Collections.<Type>emptyList()),
+ 0, 0, startPosition));
+ return;
+ }
+
+
+ List<Type> list = loadRangeWrapper(startPosition, count);
+
+ count = Math.min(count, itemCount - startPosition);
+
+ if (list == null) {
+ // invalid data, pass to receiver
+ receiver.onPageResult(new PageResult<Integer, Type>(
+ PageResult.INIT, null, 0, 0, startPosition));
+ return;
+ }
+
+ if (list.size() != count) {
+ throw new IllegalStateException("Invalid list, requested size: " + count
+ + ", returned size: " + list.size());
+ }
+
+ // emit the results as multiple pages
+ int pageCount = (count + (pageSize - 1)) / pageSize;
+ for (int i = 0; i < pageCount; i++) {
+ int beginInclusive = i * pageSize;
+ int endExclusive = Math.min(count, (i + 1) * pageSize);
+
+ Page<Integer, Type> page = new Page<>(list.subList(beginInclusive, endExclusive));
+
+ int leadingNulls = startPosition + beginInclusive;
+ int trailingNulls = itemCount - leadingNulls - page.items.size();
+ receiver.onPageResult(new PageResult<>(
+ PageResult.INIT, page, leadingNulls, trailingNulls, 0));
+ }
+ }
+
+ void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) {
+ List<Type> list = loadRangeWrapper(startPosition, count);
+
+ Page<Integer, Type> page = list != null ? new Page<Integer, Type>(list) : null;
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.TILE, page, startPosition, 0, 0));
+ }
+
+ private List<Type> loadRangeWrapper(int startPosition, int count) {
if (isInvalid()) {
return null;
}
diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java
index a2fc064b..934a0dd0 100644
--- a/android/arch/paging/TiledPagedList.java
+++ b/android/arch/paging/TiledPagedList.java
@@ -16,219 +16,173 @@
package android.arch.paging;
+import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
-import java.lang.ref.WeakReference;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class TiledPagedList<T> extends PageArrayList<T> {
+class TiledPagedList<T> extends PagedList<T>
+ implements PagedStorage.Callback {
private final TiledDataSource<T> mDataSource;
- private final Executor mMainThreadExecutor;
- private final Executor mBackgroundThreadExecutor;
- private final Config mConfig;
- @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
- private final List<T> mLoadingPlaceholder = new AbstractList<T>() {
- @Override
- public T get(int i) {
- return null;
- }
+ @SuppressWarnings("unchecked")
+ private final PagedStorage<Integer, T> mKeyedStorage = (PagedStorage<Integer, T>) mStorage;
+ private final PageResult.Receiver<Integer, T> mReceiver =
+ new PageResult.Receiver<Integer, T>() {
+ @AnyThread
@Override
- public int size() {
- return 0;
+ public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) {
+ // NOTE: if we're already on main thread, this can delay page receive by a frame
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ onPageResult(pageResult);
+ }
+ });
}
- };
- private int mLastLoad = -1;
+ @MainThread
+ @Override
+ public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
+ if (pageResult.page == null) {
+ detach();
+ return;
+ }
- private AtomicBoolean mDetached = new AtomicBoolean(false);
+ if (isDetached()) {
+ // No op, have detached
+ return;
+ }
- private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+ if (mStorage.getPageCount() == 0) {
+ mKeyedStorage.init(
+ pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
+ pageResult.positionOffset, TiledPagedList.this);
+ } else {
+ mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page,
+ TiledPagedList.this);
+ }
+ }
+ };
@WorkerThread
TiledPagedList(@NonNull TiledDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- Config config,
+ @NonNull Config config,
int position) {
- super(config.mPageSize, dataSource.countItems());
-
+ super(new PagedStorage<Integer, T>(),
+ mainThreadExecutor, backgroundThreadExecutor, config);
mDataSource = dataSource;
- mMainThreadExecutor = mainThreadExecutor;
- mBackgroundThreadExecutor = backgroundThreadExecutor;
- mConfig = config;
-
- position = Math.min(Math.max(0, position), mCount);
-
- int firstPage = position / mPageSize;
- List<T> firstPageData = dataSource.loadRangeWrapper(firstPage * mPageSize, mPageSize);
- if (firstPageData != null) {
- mPageIndexOffset = firstPage;
- mPages.add(firstPageData);
- mLastLoad = position;
- } else {
- detach();
- return;
- }
- int secondPage = (position % mPageSize < mPageSize / 2) ? firstPage - 1 : firstPage + 1;
- if (secondPage < 0 || secondPage > mMaxPageCount) {
- // no second page to load
- return;
- }
- List<T> secondPageData = dataSource.loadRangeWrapper(secondPage * mPageSize, mPageSize);
- if (secondPageData != null) {
- boolean before = secondPage < firstPage;
- mPages.add(before ? 0 : 1, secondPageData);
- if (before) {
- mPageIndexOffset--;
- }
- return;
- }
- detach();
- }
+ final int pageSize = mConfig.pageSize;
- @Override
- public void loadAround(int index) {
- mLastLoad = index;
-
- int minimumPage = Math.max((index - mConfig.mPrefetchDistance) / mPageSize, 0);
- int maximumPage = Math.min((index + mConfig.mPrefetchDistance) / mPageSize,
- mMaxPageCount - 1);
+ final int itemCount = mDataSource.countItems();
+ final int firstLoadSize = Math.min(itemCount,
+ (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize);
+ final int firstLoadPosition = computeFirstLoadPosition(
+ position, firstLoadSize, pageSize, itemCount);
- if (minimumPage < mPageIndexOffset) {
- for (int i = 0; i < mPageIndexOffset - minimumPage; i++) {
- mPages.add(0, null);
- }
- mPageIndexOffset = minimumPage;
- }
- if (maximumPage >= mPageIndexOffset + mPages.size()) {
- for (int i = mPages.size(); i <= maximumPage - mPageIndexOffset; i++) {
- mPages.add(mPages.size(), null);
- }
- }
- for (int i = minimumPage; i <= maximumPage; i++) {
- scheduleLoadPage(i);
- }
+ mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize,
+ itemCount, mReceiver);
}
- private void scheduleLoadPage(final int pageIndex) {
- final int localPageIndex = pageIndex - mPageIndexOffset;
+ static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
+ int idealStart = position - firstLoadSize / 2;
- if (mPages.get(localPageIndex) != null) {
- // page is present in list, and non-null - don't need to load
- return;
- }
- mPages.set(localPageIndex, mLoadingPlaceholder);
+ int roundedPageStart = Math.round(idealStart / pageSize) * pageSize;
- mBackgroundThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- final List<T> data = mDataSource.loadRangeWrapper(
- pageIndex * mPageSize, mPageSize);
- if (data != null) {
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- loadPageImpl(pageIndex, data);
- }
- });
- } else {
- detach();
- }
- }
- });
+ // minimum start position is 0
+ roundedPageStart = Math.max(0, roundedPageStart);
- }
+ // maximum start pos is that which will encompass end of list
+ int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
+ roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
- private void loadPageImpl(int pageIndex, List<T> data) {
- int localPageIndex = pageIndex - mPageIndexOffset;
+ return roundedPageStart;
+ }
- if (mPages.get(localPageIndex) != mLoadingPlaceholder) {
- throw new IllegalStateException("Data inserted before requested.");
- }
- mPages.set(localPageIndex, data);
- for (WeakReference<Callback> weakRef : mCallbacks) {
- Callback callback = weakRef.get();
- if (callback != null) {
- callback.onChanged(pageIndex * mPageSize, data.size());
- }
- }
+ @Override
+ boolean isContiguous() {
+ return false;
}
+ @Nullable
@Override
- public boolean isImmutable() {
- // TODO: consider counting loaded pages, return true if mLoadedPages == mMaxPageCount
- // Note: could at some point want to support growing past max count, or grow dynamically
- return isDetached();
+ public Object getLastKey() {
+ return mLastLoad;
}
@Override
- public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
+ protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
@NonNull Callback callback) {
- PageArrayList<T> snapshot = (PageArrayList<T>) previousSnapshot;
- if (snapshot != this && snapshot != null) {
- // loop through each page and signal the callback for any pages that are present now,
- // but not in the snapshot.
- for (int i = 0; i < mPages.size(); i++) {
- int pageIndex = i + mPageIndexOffset;
- int pageCount = 0;
- // count number of consecutive pages that were added since the snapshot...
- while (pageCount < mPages.size()
- && hasPage(pageIndex + pageCount)
- && !snapshot.hasPage(pageIndex + pageCount)) {
- pageCount++;
- }
- // and signal them all at once to the callback
- if (pageCount > 0) {
- callback.onChanged(pageIndex * mPageSize, mPageSize * pageCount);
- i += pageCount - 1;
- }
+ //noinspection UnnecessaryLocalVariable
+ final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage;
+
+ // loop through each page and signal the callback for any pages that are present now,
+ // but not in the snapshot.
+ final int pageSize = mConfig.pageSize;
+ final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
+ final int pageCount = mStorage.getPageCount();
+ for (int i = 0; i < pageCount; i++) {
+ int pageIndex = i + leadingNullPages;
+ int updatedPages = 0;
+ // count number of consecutive pages that were added since the snapshot...
+ while (updatedPages < mStorage.getPageCount()
+ && mStorage.hasPage(pageSize, pageIndex + updatedPages)
+ && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
+ updatedPages++;
+ }
+ // and signal them all at once to the callback
+ if (updatedPages > 0) {
+ callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
+ i += updatedPages - 1;
}
}
- mCallbacks.add(new WeakReference<>(callback));
}
@Override
- public void removeWeakCallback(@NonNull Callback callback) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- Callback currentCallback = mCallbacks.get(i).get();
- if (currentCallback == null || currentCallback == callback) {
- mCallbacks.remove(i);
- }
- }
+ protected void loadAroundInternal(int index) {
+ mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this);
}
@Override
- public boolean isDetached() {
- return mDetached.get();
+ public void onInitialized(int count) {
+ notifyInserted(0, count);
}
@Override
- public void detach() {
- mDetached.set(true);
+ public void onPagePrepended(int leadingNulls, int changed, int added) {
+ throw new IllegalStateException("Contiguous callback on TiledPagedList");
}
- @Nullable
@Override
- public Object getLastKey() {
- return mLastLoad;
+ public void onPageAppended(int endPosition, int changed, int added) {
+ throw new IllegalStateException("Contiguous callback on TiledPagedList");
+ }
+
+ @Override
+ public void onPagePlaceholderInserted(final int pageIndex) {
+ // placeholder means initialize a load
+ mBackgroundThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (isDetached()) {
+ return;
+ }
+ final int pageSize = mConfig.pageSize;
+ mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver);
+ }
+ });
+ }
+
+ @Override
+ public void onPageInserted(int start, int count) {
+ notifyChanged(start, count);
}
}
diff --git a/android/arch/paging/TiledPagedListTest.java b/android/arch/paging/TiledPagedListTest.java
deleted file mode 100644
index 4ad02e12..00000000
--- a/android/arch/paging/TiledPagedListTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class TiledPagedListTest {
-
- private TestExecutor mMainThread = new TestExecutor();
- private TestExecutor mBackgroundThread = new TestExecutor();
-
- private static final ArrayList<Item> ITEMS = new ArrayList<>();
-
- static {
- for (int i = 0; i < 45; i++) {
- ITEMS.add(new Item(i));
- }
- }
-
- // use a page size that's not an even divisor of ITEMS.size() to test end conditions
- private static final int PAGE_SIZE = 10;
-
- private static class Item {
- private Item(int position) {
- this.position = position;
- this.name = "Item " + position;
- }
-
- @SuppressWarnings("WeakerAccess")
- public final int position;
- public final String name;
-
- @Override
- public String toString() {
- return name;
- }
- }
-
- private static class TestTiledSource extends TiledDataSource<Item> {
- @Override
- public int countItems() {
- return ITEMS.size();
- }
-
- @Override
- public List<Item> loadRange(int startPosition, int count) {
- int endPosition = Math.min(ITEMS.size(), startPosition + count);
- return ITEMS.subList(startPosition, endPosition);
- }
- }
-
- private void verifyRange(PageArrayList<Item> list, Integer... loadedPages) {
- List<Integer> loadedPageList = Arrays.asList(loadedPages);
- assertEquals(ITEMS.size(), list.size());
- for (int i = 0; i < list.size(); i++) {
- if (loadedPageList.contains(i / PAGE_SIZE)) {
- assertSame(ITEMS.get(i), list.get(i));
- } else {
- assertEquals(null, list.get(i));
- }
- }
- }
- private TiledPagedList<Item> createTiledPagedList(int loadPosition) {
- return createTiledPagedList(loadPosition, PAGE_SIZE);
- }
-
- private TiledPagedList<Item> createTiledPagedList(int loadPosition, int prefetchDistance) {
- TestTiledSource source = new TestTiledSource();
- return new TiledPagedList<>(
- source, mMainThread, mBackgroundThread,
- new PagedList.Config.Builder()
- .setPageSize(PAGE_SIZE)
- .setPrefetchDistance(prefetchDistance)
- .build(),
- loadPosition);
- }
-
- @Test
- public void initialLoad() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0);
- verifyRange(pagedList, 0);
- }
-
- @Test
- public void initialLoad_end() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44);
- verifyRange(pagedList, 3, 4);
- }
-
- @Test
- public void initialLoad_multiple() {
- TiledPagedList<Item> pagedList = createTiledPagedList(9);
- verifyRange(pagedList, 0, 1);
- }
-
- @Test
- public void initialLoad_offset() {
- TiledPagedList<Item> pagedList = createTiledPagedList(41);
- verifyRange(pagedList, 3, 4);
- }
-
- @Test
- public void append() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(5);
- drain();
-
- verifyRange(pagedList, 0, 1);
- verify(callback).onChanged(10, 10);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void prepend() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 3, 4);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(35);
- drain();
-
- verifyRange(pagedList, 2, 3, 4);
- verify(callback).onChanged(20, 10);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void loadWithGap() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0);
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(44);
- drain();
-
- verifyRange(pagedList, 0, 3, 4);
- verify(callback).onChanged(30, 10);
- verify(callback).onChanged(40, 5);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void tinyPrefetchTest() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0); // just 4 loaded
- verifyZeroInteractions(callback);
-
- pagedList.loadAround(23);
- drain();
-
- verifyRange(pagedList, 0, 2);
- verify(callback).onChanged(20, 10);
- verifyNoMoreInteractions(callback);
-
- pagedList.loadAround(44);
- drain();
-
- verifyRange(pagedList, 0, 2, 4);
- verify(callback).onChanged(40, 5);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void appendCallbackAddedLate() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 0);
- verifyRange(pagedList, 0);
-
- pagedList.loadAround(15);
- drain();
- verifyRange(pagedList, 0, 1);
-
- // snapshot at 20 items
- PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
- verifyRange(snapshot, 0, 1);
-
-
- pagedList.loadAround(25);
- pagedList.loadAround(35);
- drain();
- verifyRange(pagedList, 0, 1, 2, 3);
- verifyRange(snapshot, 0, 1);
-
- PagedList.Callback callback = mock(
- PagedList.Callback.class);
- pagedList.addWeakCallback(snapshot, callback);
- verify(callback).onChanged(20, 20);
- verifyNoMoreInteractions(callback);
- }
-
-
- @Test
- public void prependCallbackAddedLate() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44, 0);
- verifyRange(pagedList, 3, 4);
-
- pagedList.loadAround(25);
- drain();
- verifyRange(pagedList, 2, 3, 4);
-
- // snapshot at 30 items
- PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
- verifyRange(snapshot, 2, 3, 4);
-
-
- pagedList.loadAround(15);
- pagedList.loadAround(5);
- drain();
- verifyRange(pagedList, 0, 1, 2, 3, 4);
- verifyRange(snapshot, 2, 3, 4);
-
- PagedList.Callback callback = mock(PagedList.Callback.class);
- pagedList.addWeakCallback(snapshot, callback);
- verify(callback).onChanged(0, 20);
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void placeholdersDisabled() {
- // disable placeholders with config, so we create a contiguous version of the pagedlist
- PagedList<Item> pagedList = new PagedList.Builder<Integer, Item>()
- .setDataSource(new TestTiledSource())
- .setMainThreadExecutor(mMainThread)
- .setBackgroundThreadExecutor(mBackgroundThread)
- .setConfig(new PagedList.Config.Builder()
- .setPageSize(PAGE_SIZE)
- .setPrefetchDistance(PAGE_SIZE)
- .setInitialLoadSizeHint(PAGE_SIZE)
- .setEnablePlaceholders(false)
- .build())
- .setInitialKey(20)
- .build();
-
- assertTrue(pagedList.isContiguous());
-
- ContiguousPagedList<Item> contiguousPagedList = (ContiguousPagedList<Item>) pagedList;
- assertEquals(0, contiguousPagedList.getLeadingNullCount());
- assertEquals(PAGE_SIZE, contiguousPagedList.mList.size());
- assertEquals(0, contiguousPagedList.getTrailingNullCount());
- }
-
- private void drain() {
- boolean executed;
- do {
- executed = mBackgroundThread.executeAll();
- executed |= mMainThread.executeAll();
- } while (executed);
- }
-}
diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java
index 45ec0289..b31dc13a 100644
--- a/android/arch/persistence/room/InvalidationTracker.java
+++ b/android/arch/persistence/room/InvalidationTracker.java
@@ -219,7 +219,7 @@ public class InvalidationTracker {
*
* @param observer The observer which listens the database for changes.
*/
- public void addObserver(Observer observer) {
+ public void addObserver(@NonNull Observer observer) {
final String[] tableNames = observer.mTables;
int[] tableIds = new int[tableNames.length];
final int size = tableNames.length;
@@ -265,7 +265,7 @@ public class InvalidationTracker {
* @param observer The observer to remove.
*/
@SuppressWarnings("WeakerAccess")
- public void removeObserver(final Observer observer) {
+ public void removeObserver(@NonNull final Observer observer) {
ObserverWrapper wrapper;
synchronized (mObserverMap) {
wrapper = mObserverMap.remove(observer);
diff --git a/android/arch/persistence/room/Relation.java b/android/arch/persistence/room/Relation.java
index 72066992..d55bbfe8 100644
--- a/android/arch/persistence/room/Relation.java
+++ b/android/arch/persistence/room/Relation.java
@@ -28,6 +28,8 @@ import java.lang.annotation.Target;
* <pre>
* {@literal @}Entity
* public class Pet {
+ * {@literal @} PrimaryKey
+ * int id;
* int userId;
* String name;
* // other fields
@@ -41,8 +43,8 @@ import java.lang.annotation.Target;
*
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT id, name from User WHERE age &gt; :minAge")
- * public List&lt;UserNameAndAllPets&gt; loadUserAndPets(int minAge);
+ * {@literal @}Query("SELECT id, name from User")
+ * public List&lt;UserNameAndAllPets&gt; loadUserAndPets();
* }
* </pre>
* <p>
@@ -63,16 +65,16 @@ import java.lang.annotation.Target;
* {@literal @}Embedded
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
- * public List<PetNameAndId> pets;
+ * public List&lt;PetNameAndId&gt; pets;
* }
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT * from User WHERE age &gt; :minAge")
- * public List&lt;UserAllPets&gt; loadUserAndPets(int minAge);
+ * {@literal @}Query("SELECT * from User")
+ * public List&lt;UserAllPets&gt; loadUserAndPets();
* }
* </pre>
* <p>
- * In the example above, {@code PetNameAndId} is a regular but all of fields are fetched
+ * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched
* from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>).
* {@code PetNameAndId} could also define its own relations all of which would also be fetched
* automatically.
@@ -85,7 +87,7 @@ import java.lang.annotation.Target;
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class,
* projection = {"name"})
- * public List<String> petNames;
+ * public List&lt;String&gt; petNames;
* }
* </pre>
* <p>
@@ -93,7 +95,7 @@ import java.lang.annotation.Target;
* cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity}
* setups. You can read more about it in the main Room documentation. When loading data, you can
* simply work around this limitation by creating Pojo classes that extend the {@link Entity}.
- *
+ * <p>
* Note that the {@code @Relation} annotated field cannot be a constructor parameter, it must be
* public or have a public setter.
*/
diff --git a/android/arch/persistence/room/Room.java b/android/arch/persistence/room/Room.java
index 8ce4be0c..2850b55e 100644
--- a/android/arch/persistence/room/Room.java
+++ b/android/arch/persistence/room/Room.java
@@ -43,6 +43,7 @@ public class Room {
* @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
*/
@SuppressWarnings("WeakerAccess")
+ @NonNull
public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
@NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
//noinspection ConstantConditions
@@ -65,6 +66,7 @@ public class Room {
* @param <T> The type of the database class.
* @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
*/
+ @NonNull
public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
@NonNull Context context, @NonNull Class<T> klass) {
return new RoomDatabase.Builder<>(context, klass, null);
diff --git a/android/arch/persistence/room/RoomDatabase.java b/android/arch/persistence/room/RoomDatabase.java
index cdad868d..8c940246 100644
--- a/android/arch/persistence/room/RoomDatabase.java
+++ b/android/arch/persistence/room/RoomDatabase.java
@@ -49,7 +49,7 @@ import java.util.concurrent.locks.ReentrantLock;
*
* @see Database
*/
-@SuppressWarnings({"unused", "WeakerAccess"})
+//@SuppressWarnings({"unused", "WeakerAccess"})
public abstract class RoomDatabase {
private static final String DB_IMPL_SUFFIX = "_Impl";
// set by the generated open helper.
@@ -153,7 +153,9 @@ public abstract class RoomDatabase {
*
* @hide
*/
+ @SuppressWarnings("WeakerAccess")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ // used in generated code
public void assertNotMainThread() {
if (mAllowMainThreadQueries) {
return;
@@ -298,6 +300,7 @@ public abstract class RoomDatabase {
* @return True if there is an active transaction in current thread, false otherwise.
* @see SupportSQLiteDatabase#inTransaction()
*/
+ @SuppressWarnings("WeakerAccess")
public boolean inTransaction() {
return mOpenHelper.getWritableDatabase().inTransaction();
}
@@ -307,7 +310,6 @@ public abstract class RoomDatabase {
*
* @param <T> The type of the abstract database class.
*/
- @SuppressWarnings("unused")
public static class Builder<T extends RoomDatabase> {
private final Class<T> mDatabaseClass;
private final String mName;
@@ -337,7 +339,8 @@ public abstract class RoomDatabase {
* @param factory The factory to use to access the database.
* @return this
*/
- public Builder<T> openHelperFactory(SupportSQLiteOpenHelper.Factory factory) {
+ @NonNull
+ public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
mFactory = factory;
return this;
}
@@ -361,6 +364,7 @@ public abstract class RoomDatabase {
* changes.
* @return this
*/
+ @NonNull
public Builder<T> addMigrations(Migration... migrations) {
mMigrationContainer.addMigrations(migrations);
return this;
@@ -378,6 +382,7 @@ public abstract class RoomDatabase {
*
* @return this
*/
+ @NonNull
public Builder<T> allowMainThreadQueries() {
mAllowMainThreadQueries = true;
return this;
@@ -400,6 +405,7 @@ public abstract class RoomDatabase {
*
* @return this
*/
+ @NonNull
public Builder<T> fallbackToDestructiveMigration() {
mRequireMigration = false;
return this;
@@ -411,6 +417,7 @@ public abstract class RoomDatabase {
* @param callback The callback.
* @return this
*/
+ @NonNull
public Builder<T> addCallback(@NonNull Callback callback) {
if (mCallbacks == null) {
mCallbacks = new ArrayList<>();
@@ -427,6 +434,7 @@ public abstract class RoomDatabase {
*
* @return A new database instance.
*/
+ @NonNull
public T build() {
//noinspection ConstantConditions
if (mContext == null) {
@@ -493,6 +501,7 @@ public abstract class RoomDatabase {
* @return An ordered list of {@link Migration} objects that should be run to migrate
* between the given versions. If a migration path cannot be found, returns {@code null}.
*/
+ @SuppressWarnings("WeakerAccess")
@Nullable
public List<Migration> findMigrationPath(int start, int end) {
if (start == end) {
diff --git a/android/arch/persistence/room/RoomWarnings.java b/android/arch/persistence/room/RoomWarnings.java
index c64be967..f05e6be2 100644
--- a/android/arch/persistence/room/RoomWarnings.java
+++ b/android/arch/persistence/room/RoomWarnings.java
@@ -125,4 +125,12 @@ public class RoomWarnings {
* annotation.
*/
public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+
+ /**
+ * Reported when a @Query method returns a Pojo that has relations but the method is not
+ * annotated with @Transaction. Relations are run as separate queries and if the query is not
+ * run inside a transaction, it might return inconsistent results from the database.
+ */
+ public static final String RELATION_QUERY_WITHOUT_TRANSACTION =
+ "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
}
diff --git a/android/arch/persistence/room/Transaction.java b/android/arch/persistence/room/Transaction.java
index 914e4f41..3b6ede9c 100644
--- a/android/arch/persistence/room/Transaction.java
+++ b/android/arch/persistence/room/Transaction.java
@@ -22,9 +22,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Marks a method in an abstract {@link Dao} class as a transaction method.
+ * Marks a method in a {@link Dao} class as a transaction method.
* <p>
- * The derived implementation of the method will execute the super method in a database transaction.
+ * When used on a non-abstract method of an abstract {@link Dao} class,
+ * the derived implementation of the method will execute the super method in a database transaction.
* All the parameters and return types are preserved. The transaction will be marked as successful
* unless an exception is thrown in the method body.
* <p>
@@ -44,6 +45,38 @@ import java.lang.annotation.Target;
* }
* }
* </pre>
+ * <p>
+ * When used on a {@link Query} method that has a {@code Select} statement, the generated code for
+ * the Query will be run in a transaction. There are 2 main cases where you may want to do that:
+ * <ol>
+ * <li>If the result of the query is fairly big, it is better to run it inside a transaction
+ * to receive a consistent result. Otherwise, if the query result does not fit into a single
+ * {@link android.database.CursorWindow CursorWindow}, the query result may be corrupted due to
+ * changes in the database in between cursor window swaps.
+ * <li>If the result of the query is a Pojo with {@link Relation} fields, these fields are
+ * queried separately. To receive consistent results between these queries, you probably want
+ * to run them in a single transaction.
+ * </ol>
+ * Example:
+ * <pre>
+ * class ProductWithReviews extends Product {
+ * {@literal @}Relation(parentColumn = "id", entityColumn = "productId", entity = Review.class)
+ * public List&lt;Review> reviews;
+ * }
+ * {@literal @}Dao
+ * public interface ProductDao {
+ * {@literal @}Transaction {@literal @}Query("SELECT * from products")
+ * public List&lt;ProductWithReviews> loadAll();
+ * }
+ * </pre>
+ * If the query is an async query (e.g. returns a {@link android.arch.lifecycle.LiveData LiveData}
+ * or RxJava Flowable, the transaction is properly handled when the query is run, not when the
+ * method is called.
+ * <p>
+ * Putting this annotation on an {@link Insert}, {@link Update} or {@link Delete} method has no
+ * impact because they are always run inside a transaction. Similarly, if it is annotated with
+ * {@link Query} but runs an update or delete statement, it is automatically wrapped in a
+ * transaction.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
index 818c46b4..cdd464e4 100644
--- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -86,6 +86,7 @@ public class RoomPagedListActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
PagedList<Customer> list = mAdapter.getCurrentList();
if (list == null) {
// Can't find anything to restore
diff --git a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
index 9d402370..b5df914a 100644
--- a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
+++ b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
@@ -59,7 +59,7 @@ public interface CustomerDao {
// Keyed
- @Query("SELECT * from customer ORDER BY mLastName ASC LIMIT :limit")
+ @Query("SELECT * from customer ORDER BY mLastName DESC LIMIT :limit")
List<Customer> customerNameInitial(int limit);
@Query("SELECT * from customer WHERE mLastName < :key ORDER BY mLastName DESC LIMIT :limit")
diff --git a/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java b/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
deleted file mode 100644
index 3cbffc8b..00000000
--- a/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.db;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-
-public class JDBCOpenHelper implements SupportSQLiteOpenHelper {
- @Override
- public String getDatabaseName() {
- return null;
- }
-
- @Override
- public void setWriteAheadLoggingEnabled(boolean enabled) {
-
- }
-
- @Override
- public SupportSQLiteDatabase getWritableDatabase() {
- return null;
- }
-
- @Override
- public SupportSQLiteDatabase getReadableDatabase() {
- return null;
- }
-
- @Override
- public void close() {
-
- }
-}
diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
index 84f20ec5..33f40183 100644
--- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -17,20 +17,17 @@
package android.arch.persistence.room.integration.testapp.test;
import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
import android.arch.persistence.room.InvalidationTracker;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.integration.testapp.TestDatabase;
import android.arch.persistence.room.integration.testapp.dao.UserDao;
import android.arch.persistence.room.integration.testapp.vo.User;
import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -38,17 +35,13 @@ import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Tests invalidation tracking.
@@ -56,138 +49,97 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class InvalidationTest {
+ @Rule
+ public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule();
private UserDao mUserDao;
private TestDatabase mDb;
@Before
- public void createDb() {
+ public void createDb() throws TimeoutException, InterruptedException {
Context context = InstrumentationRegistry.getTargetContext();
mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
mUserDao = mDb.getUserDao();
- }
-
- @Before
- public void setSingleThreadedIO() {
- ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
- ExecutorService mIOExecutor = Executors.newSingleThreadExecutor();
- Handler mHandler = new Handler(Looper.getMainLooper());
-
- @Override
- public void executeOnDiskIO(Runnable runnable) {
- mIOExecutor.execute(runnable);
- }
-
- @Override
- public void postToMainThread(Runnable runnable) {
- mHandler.post(runnable);
- }
-
- @Override
- public boolean isMainThread() {
- return Thread.currentThread() == Looper.getMainLooper().getThread();
- }
- });
+ drain();
}
@After
- public void clearExecutor() {
- ArchTaskExecutor.getInstance().setDelegate(null);
+ public void closeDb() throws TimeoutException, InterruptedException {
+ mDb.close();
+ drain();
}
- private void waitUntilIOThreadIsIdle() {
- FutureTask<Void> future = new FutureTask<>(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- return null;
- }
- });
- ArchTaskExecutor.getInstance().executeOnDiskIO(future);
- //noinspection TryWithIdenticalCatches
- try {
- future.get();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
+ private void drain() throws TimeoutException, InterruptedException {
+ executorRule.drainTasks(1, TimeUnit.MINUTES);
}
@Test
- public void testInvalidationOnUpdate() throws InterruptedException {
+ public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException {
User user = TestUtil.createUser(3);
mUserDao.insert(user);
- LatchObserver observer = new LatchObserver(1, "User");
+ LoggingObserver observer = new LoggingObserver("User");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.updateById(3, "foo2");
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testInvalidationOnDelete() throws InterruptedException {
+ public void testInvalidationOnDelete() throws InterruptedException, TimeoutException {
User user = TestUtil.createUser(3);
mUserDao.insert(user);
- LatchObserver observer = new LatchObserver(1, "User");
+ LoggingObserver observer = new LoggingObserver("User");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.delete(user);
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testInvalidationOnInsert() throws InterruptedException {
- LatchObserver observer = new LatchObserver(1, "User");
+ public void testInvalidationOnInsert() throws InterruptedException, TimeoutException {
+ LoggingObserver observer = new LoggingObserver("User");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.insert(TestUtil.createUser(3));
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testDontInvalidateOnLateInsert() throws InterruptedException {
- LatchObserver observer = new LatchObserver(1, "User");
+ public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException {
+ LoggingObserver observer = new LoggingObserver("User");
mUserDao.insert(TestUtil.createUser(3));
- waitUntilIOThreadIsIdle();
+ drain();
mDb.getInvalidationTracker().addObserver(observer);
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(false));
+ drain();
+ assertThat(observer.getInvalidatedTables(), nullValue());
}
@Test
- public void testMultipleTables() throws InterruptedException {
- LatchObserver observer = new LatchObserver(1, "User", "Pet");
+ public void testMultipleTables() throws InterruptedException, TimeoutException {
+ LoggingObserver observer = new LoggingObserver("User", "Pet");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.insert(TestUtil.createUser(3));
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
- private static class LatchObserver extends InvalidationTracker.Observer {
- CountDownLatch mLatch;
-
+ private static class LoggingObserver extends InvalidationTracker.Observer {
private Set<String> mInvalidatedTables;
- LatchObserver(int permits, String... tables) {
+ LoggingObserver(String... tables) {
super(tables);
- mLatch = new CountDownLatch(permits);
- }
-
- boolean await() throws InterruptedException {
- return mLatch.await(5, TimeUnit.SECONDS);
}
@Override
public void onInvalidated(@NonNull Set<String> tables) {
mInvalidatedTables = tables;
- mLatch.countDown();
}
Set<String> getInvalidatedTables() {
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
index e11117e4..2735c05a 100644
--- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
@@ -166,17 +166,13 @@ public class QueryDataSourceTest extends TestDatabaseTest {
p = dataSource.loadBefore(15, list.get(0), 10);
assertNotNull(p);
- for (User u : p) {
- list.add(0, u);
- }
+ list.addAll(0, p);
assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray());
p = dataSource.loadBefore(5, list.get(0), 10);
assertNotNull(p);
- for (User u : p) {
- list.add(0, u);
- }
+ list.addAll(0, p);
assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray());
}
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
new file mode 100644
index 00000000..854c8627
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
+import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.PagedList;
+import android.arch.paging.TiledDataSource;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.Ignore;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Relation;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomWarnings;
+import android.arch.persistence.room.Transaction;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import io.reactivex.schedulers.Schedulers;
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class QueryTransactionTest {
+ @Rule
+ public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule();
+ private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0);
+ private TransactionDb mDb;
+ private final boolean mUseTransactionDao;
+ private Entity1Dao mDao;
+ private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest
+ .TestLifecycleOwner();
+
+ @NonNull
+ @Parameterized.Parameters(name = "useTransaction_{0}")
+ public static Boolean[] getParams() {
+ return new Boolean[]{false, true};
+ }
+
+ public QueryTransactionTest(boolean useTransactionDao) {
+ mUseTransactionDao = useTransactionDao;
+ }
+
+ @Before
+ public void initDb() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+ }
+ });
+
+ resetTransactionCount();
+ mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+ TransactionDb.class).build();
+ mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
+ drain();
+ }
+
+ @After
+ public void closeDb() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
+ }
+ });
+ drain();
+ mDb.close();
+ }
+
+ @Test
+ public void readList() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ List<Entity1> allEntities = mDao.allEntities();
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void liveData() {
+ LiveData<List<Entity1>> listLiveData = mDao.liveData();
+ observeForever(listLiveData);
+ drain();
+ assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList()));
+
+ resetTransactionCount();
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+
+ //noinspection ConstantConditions
+ assertThat(listLiveData.getValue().size(), is(1));
+ int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+ assertTransactionCount(listLiveData.getValue(), expectedTransactionCount);
+ }
+
+ @Test
+ public void flowable() {
+ Flowable<List<Entity1>> flowable = mDao.flowable();
+ TestSubscriber<List<Entity1>> subscriber = observe(flowable);
+ drain();
+ assertThat(subscriber.values().size(), is(1));
+
+ resetTransactionCount();
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+
+ List<Entity1> allEntities = subscriber.values().get(1);
+ assertThat(allEntities.size(), is(1));
+ int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void maybe() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ Maybe<List<Entity1>> listMaybe = mDao.maybe();
+ TestObserver<List<Entity1>> observer = observe(listMaybe);
+ drain();
+ List<Entity1> allEntities = observer.values().get(0);
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void single() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ Single<List<Entity1>> listMaybe = mDao.single();
+ TestObserver<List<Entity1>> observer = observe(listMaybe);
+ drain();
+ List<Entity1> allEntities = observer.values().get(0);
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void relation() {
+ mDao.insert(new Entity1(1, "foo"));
+ mDao.insert(new Child(1, 1));
+ mDao.insert(new Child(2, 1));
+ resetTransactionCount();
+
+ List<Entity1WithChildren> result = mDao.withRelation();
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ assertTransactionCountWithChildren(result, expectedTransactionCount);
+ }
+
+ @Test
+ public void pagedList() {
+ LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10);
+ observeForever(pagedList);
+ drain();
+ assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
+
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+ //noinspection ConstantConditions
+ assertThat(pagedList.getValue().size(), is(1));
+ assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 2 : 1);
+
+ mDao.insert(new Entity1(2, "bar"));
+ drain();
+ assertThat(pagedList.getValue().size(), is(2));
+ assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 4 : 2);
+ }
+
+ @Test
+ public void dataSource() {
+ mDao.insert(new Entity1(2, "bar"));
+ drain();
+ resetTransactionCount();
+ TiledDataSource<Entity1> dataSource = mDao.dataSource();
+ dataSource.loadRange(0, 10);
+ assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
+ }
+
+ private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) {
+ assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+ assertThat(allEntities.isEmpty(), is(false));
+ for (Entity1 entity1 : allEntities) {
+ assertThat(entity1.transactionId, is(expectedTransactionCount));
+ }
+ }
+
+ private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities,
+ int expectedTransactionCount) {
+ assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+ assertThat(allEntities.isEmpty(), is(false));
+ for (Entity1WithChildren entity1 : allEntities) {
+ assertThat(entity1.transactionId, is(expectedTransactionCount));
+ assertThat(entity1.children, notNullValue());
+ assertThat(entity1.children.isEmpty(), is(false));
+ for (Child child : entity1.children) {
+ assertThat(child.transactionId, is(expectedTransactionCount));
+ }
+ }
+ }
+
+ private void resetTransactionCount() {
+ sStartedTransactionCount.set(0);
+ }
+
+ private void drain() {
+ try {
+ countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new AssertionError("interrupted", e);
+ } catch (TimeoutException e) {
+ throw new AssertionError("drain timed out", e);
+ }
+ }
+
+ private <T> TestSubscriber<T> observe(final Flowable<T> flowable) {
+ TestSubscriber<T> subscriber = new TestSubscriber<>();
+ flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(subscriber);
+ return subscriber;
+ }
+
+ private <T> TestObserver<T> observe(final Maybe<T> maybe) {
+ TestObserver<T> observer = new TestObserver<>();
+ maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(observer);
+ return observer;
+ }
+
+ private <T> TestObserver<T> observe(final Single<T> single) {
+ TestObserver<T> observer = new TestObserver<>();
+ single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(observer);
+ return observer;
+ }
+
+ private <T> void observeForever(final LiveData<T> liveData) {
+ FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ liveData.observe(mLifecycleOwner, new Observer<T>() {
+ @Override
+ public void onChanged(@Nullable T t) {
+
+ }
+ });
+ return null;
+ }
+ });
+ ArchTaskExecutor.getMainThreadExecutor().execute(futureTask);
+ try {
+ futureTask.get();
+ } catch (InterruptedException e) {
+ throw new AssertionError("interrupted", e);
+ } catch (ExecutionException e) {
+ throw new AssertionError("execution error", e);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class Entity1WithChildren extends Entity1 {
+ @Relation(entity = Child.class, parentColumn = "id",
+ entityColumn = "entity1Id")
+ public List<Child> children;
+
+ Entity1WithChildren(int id, String value) {
+ super(id, value);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @Entity
+ static class Child {
+ @PrimaryKey(autoGenerate = true)
+ public int id;
+ public int entity1Id;
+ @Ignore
+ public final int transactionId = sStartedTransactionCount.get();
+
+ Child(int id, int entity1Id) {
+ this.id = id;
+ this.entity1Id = entity1Id;
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @Entity
+ static class Entity1 {
+ @PrimaryKey(autoGenerate = true)
+ public int id;
+ public String value;
+ @Ignore
+ public final int transactionId = sStartedTransactionCount.get();
+
+ Entity1(int id, String value) {
+ this.id = id;
+ this.value = value;
+ }
+ }
+
+ // we don't support dao inheritance for queries so for now, go with this
+ interface Entity1Dao {
+ String SELECT_ALL = "select * from Entity1";
+
+ List<Entity1> allEntities();
+
+ Flowable<List<Entity1>> flowable();
+
+ Maybe<List<Entity1>> maybe();
+
+ Single<List<Entity1>> single();
+
+ LiveData<List<Entity1>> liveData();
+
+ List<Entity1WithChildren> withRelation();
+
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ TiledDataSource<Entity1> dataSource();
+
+ @Insert
+ void insert(Entity1 entity1);
+
+ @Insert
+ void insert(Child entity1);
+ }
+
+ @Dao
+ interface EntityDao extends Entity1Dao {
+ @Override
+ @Query(SELECT_ALL)
+ List<Entity1> allEntities();
+
+ @Override
+ @Query(SELECT_ALL)
+ Flowable<List<Entity1>> flowable();
+
+ @Override
+ @Query(SELECT_ALL)
+ LiveData<List<Entity1>> liveData();
+
+ @Override
+ @Query(SELECT_ALL)
+ Maybe<List<Entity1>> maybe();
+
+ @Override
+ @Query(SELECT_ALL)
+ Single<List<Entity1>> single();
+
+ @Override
+ @Query(SELECT_ALL)
+ @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
+ List<Entity1WithChildren> withRelation();
+
+ @Override
+ @Query(SELECT_ALL)
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ @Override
+ @Query(SELECT_ALL)
+ TiledDataSource<Entity1> dataSource();
+ }
+
+ @Dao
+ interface TransactionDao extends Entity1Dao {
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ List<Entity1> allEntities();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Flowable<List<Entity1>> flowable();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ LiveData<List<Entity1>> liveData();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Maybe<List<Entity1>> maybe();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Single<List<Entity1>> single();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ List<Entity1WithChildren> withRelation();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ TiledDataSource<Entity1> dataSource();
+ }
+
+ @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false)
+ abstract static class TransactionDb extends RoomDatabase {
+ abstract EntityDao dao();
+
+ abstract TransactionDao transactionDao();
+
+ @Override
+ public void beginTransaction() {
+ super.beginTransaction();
+ sStartedTransactionCount.incrementAndGet();
+ }
+ }
+}
diff --git a/android/arch/persistence/room/migration/Migration.java b/android/arch/persistence/room/migration/Migration.java
index 907e624b..d69ea0dc 100644
--- a/android/arch/persistence/room/migration/Migration.java
+++ b/android/arch/persistence/room/migration/Migration.java
@@ -17,6 +17,7 @@
package android.arch.persistence.room.migration;
import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.support.annotation.NonNull;
/**
* Base class for a database migration.
@@ -58,5 +59,5 @@ public abstract class Migration {
*
* @param database The database instance
*/
- public abstract void migrate(SupportSQLiteDatabase database);
+ public abstract void migrate(@NonNull SupportSQLiteDatabase database);
}
diff --git a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
index 1467a4f0..d72cf8cb 100644
--- a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
+++ b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
@@ -16,13 +16,18 @@
package android.arch.persistence.room.migration.bundle;
+import android.support.annotation.RestrictTo;
+
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Holds the information about a foreign key reference.
+ *
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class ForeignKeyBundle {
@SerializedName("table")
private String mTable;
diff --git a/android/arch/persistence/room/paging/LimitOffsetDataSource.java b/android/arch/persistence/room/paging/LimitOffsetDataSource.java
index 800514cc..2f9a8882 100644
--- a/android/arch/persistence/room/paging/LimitOffsetDataSource.java
+++ b/android/arch/persistence/room/paging/LimitOffsetDataSource.java
@@ -49,10 +49,13 @@ public abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> {
private final RoomDatabase mDb;
@SuppressWarnings("FieldCanBeLocal")
private final InvalidationTracker.Observer mObserver;
+ private final boolean mInTransaction;
- protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, String... tables) {
+ protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
+ boolean inTransaction, String... tables) {
mDb = db;
mSourceQuery = query;
+ mInTransaction = inTransaction;
mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
mObserver = new InvalidationTracker.Observer(tables) {
@@ -98,13 +101,30 @@ public abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> {
sqLiteQuery.copyArgumentsFrom(mSourceQuery);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
- Cursor cursor = mDb.query(sqLiteQuery);
-
- try {
- return convertRows(cursor);
- } finally {
- cursor.close();
- sqLiteQuery.release();
+ if (mInTransaction) {
+ mDb.beginTransaction();
+ Cursor cursor = null;
+ try {
+ cursor = mDb.query(sqLiteQuery);
+ List<T> rows = convertRows(cursor);
+ mDb.setTransactionSuccessful();
+ return rows;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ mDb.endTransaction();
+ sqLiteQuery.release();
+ }
+ } else {
+ Cursor cursor = mDb.query(sqLiteQuery);
+ //noinspection TryFinallyCanBeTryWithResources
+ try {
+ return convertRows(cursor);
+ } finally {
+ cursor.close();
+ sqLiteQuery.release();
+ }
}
}
}
diff --git a/android/arch/persistence/room/util/StringUtil.java b/android/arch/persistence/room/util/StringUtil.java
index bee05ddd..d01e3c53 100644
--- a/android/arch/persistence/room/util/StringUtil.java
+++ b/android/arch/persistence/room/util/StringUtil.java
@@ -17,6 +17,7 @@
package android.arch.persistence.room.util;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
import android.util.Log;
import java.util.ArrayList;
@@ -24,10 +25,14 @@ import java.util.List;
import java.util.StringTokenizer;
/**
+ * @hide
+ *
* String utilities for Room
*/
-@SuppressWarnings("WeakerAccess")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class StringUtil {
+
+ @SuppressWarnings("unused")
public static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* Returns a new StringBuilder to be used while producing SQL queries.
diff --git a/android/bluetooth/BluetoothAdapter.java b/android/bluetooth/BluetoothAdapter.java
index 84765f6d..578a5b8b 100644
--- a/android/bluetooth/BluetoothAdapter.java
+++ b/android/bluetooth/BluetoothAdapter.java
@@ -1134,8 +1134,32 @@ public final class BluetoothAdapter {
}
/**
- * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of
- * the local Bluetooth adapter.
+ * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+ * adapter.
+ *
+ * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public BluetoothClass getBluetoothClass() {
+ if (getState() != STATE_ON) return null;
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) return mService.getBluetoothClass();
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+ * adapter.
+ *
+ * <p>Note: This value persists across system reboot.
*
* @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to.
* @return true if successful, false if unsuccessful.
@@ -2104,8 +2128,8 @@ public final class BluetoothAdapter {
} else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener);
return true;
- } else if (profile == BluetoothProfile.INPUT_DEVICE) {
- BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener);
+ } else if (profile == BluetoothProfile.HID_HOST) {
+ BluetoothHidHost iDev = new BluetoothHidHost(context, listener);
return true;
} else if (profile == BluetoothProfile.PAN) {
BluetoothPan pan = new BluetoothPan(context, listener);
@@ -2128,8 +2152,8 @@ public final class BluetoothAdapter {
} else if (profile == BluetoothProfile.MAP_CLIENT) {
BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
return true;
- } else if (profile == BluetoothProfile.INPUT_HOST) {
- BluetoothInputHost iHost = new BluetoothInputHost(context, listener);
+ } else if (profile == BluetoothProfile.HID_DEVICE) {
+ BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
return true;
} else {
return false;
@@ -2167,8 +2191,8 @@ public final class BluetoothAdapter {
BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy;
avrcp.close();
break;
- case BluetoothProfile.INPUT_DEVICE:
- BluetoothInputDevice iDev = (BluetoothInputDevice) proxy;
+ case BluetoothProfile.HID_HOST:
+ BluetoothHidHost iDev = (BluetoothHidHost) proxy;
iDev.close();
break;
case BluetoothProfile.PAN:
@@ -2207,9 +2231,9 @@ public final class BluetoothAdapter {
BluetoothMapClient mapClient = (BluetoothMapClient) proxy;
mapClient.close();
break;
- case BluetoothProfile.INPUT_HOST:
- BluetoothInputHost iHost = (BluetoothInputHost) proxy;
- iHost.close();
+ case BluetoothProfile.HID_DEVICE:
+ BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy;
+ hidDevice.close();
break;
}
}
@@ -2277,6 +2301,8 @@ public final class BluetoothAdapter {
*
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
public boolean enableNoAutoConnect() {
if (isEnabled()) {
if (DBG) Log.d(TAG, "enableNoAutoConnect(): BT already enabled!");
diff --git a/android/bluetooth/BluetoothDevice.java b/android/bluetooth/BluetoothDevice.java
index d982bb7f..ad7a93cd 100644
--- a/android/bluetooth/BluetoothDevice.java
+++ b/android/bluetooth/BluetoothDevice.java
@@ -1098,6 +1098,8 @@ public final class BluetoothDevice implements Parcelable {
* @return true on success, false on error
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
public boolean cancelBondProcess() {
final IBluetooth service = sService;
if (service == null) {
@@ -1125,6 +1127,8 @@ public final class BluetoothDevice implements Parcelable {
* @return true on success, false on error
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
public boolean removeBond() {
final IBluetooth service = sService;
if (service == null) {
@@ -1174,6 +1178,7 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH)
public boolean isConnected() {
final IBluetooth service = sService;
if (service == null) {
@@ -1197,6 +1202,7 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH)
public boolean isEncrypted() {
final IBluetooth service = sService;
if (service == null) {
@@ -1444,6 +1450,8 @@ public final class BluetoothDevice implements Parcelable {
* @return Whether the value has been successfully set.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setPhonebookAccessPermission(int value) {
final IBluetooth service = sService;
if (service == null) {
diff --git a/android/bluetooth/BluetoothHeadset.java b/android/bluetooth/BluetoothHeadset.java
index 85550c77..1241f230 100644
--- a/android/bluetooth/BluetoothHeadset.java
+++ b/android/bluetooth/BluetoothHeadset.java
@@ -16,8 +16,10 @@
package android.bluetooth;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
@@ -416,6 +418,8 @@ public final class BluetoothHeadset implements BluetoothProfile {
* @return false on immediate error, true otherwise
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHeadset service = mService;
@@ -456,6 +460,8 @@ public final class BluetoothHeadset implements BluetoothProfile {
* @return false on immediate error, true otherwise
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothHeadset service = mService;
@@ -543,6 +549,8 @@ public final class BluetoothHeadset implements BluetoothProfile {
* @return true if priority is set, false on error
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
final IBluetoothHeadset service = mService;
diff --git a/android/bluetooth/BluetoothInputHost.java b/android/bluetooth/BluetoothHidDevice.java
index e18d9d1b..179f36da 100644
--- a/android/bluetooth/BluetoothInputHost.java
+++ b/android/bluetooth/BluetoothHidDevice.java
@@ -33,9 +33,9 @@ import java.util.List;
/**
* @hide
*/
-public final class BluetoothInputHost implements BluetoothProfile {
+public final class BluetoothHidDevice implements BluetoothProfile {
- private static final String TAG = BluetoothInputHost.class.getSimpleName();
+ private static final String TAG = BluetoothHidDevice.class.getSimpleName();
/**
* Intent used to broadcast the change in connection state of the Input
@@ -57,7 +57,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
- "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED";
+ "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
/**
* Constants representing device subclass.
@@ -113,7 +113,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
private ServiceListener mServiceListener;
- private volatile IBluetoothInputHost mService;
+ private volatile IBluetoothHidDevice mService;
private BluetoothAdapter mAdapter;
@@ -205,23 +205,23 @@ public final class BluetoothInputHost implements BluetoothProfile {
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d(TAG, "onServiceConnected()");
- mService = IBluetoothInputHost.Stub.asInterface(service);
+ mService = IBluetoothHidDevice.Stub.asInterface(service);
if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
- BluetoothInputHost.this);
+ mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE,
+ BluetoothHidDevice.this);
}
}
public void onServiceDisconnected(ComponentName className) {
Log.d(TAG, "onServiceDisconnected()");
mService = null;
if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
}
}
};
- BluetoothInputHost(Context context, ServiceListener listener) {
- Log.v(TAG, "BluetoothInputHost");
+ BluetoothHidDevice(Context context, ServiceListener listener) {
+ Log.v(TAG, "BluetoothHidDevice");
mContext = context;
mServiceListener = listener;
@@ -240,7 +240,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
}
boolean doBind() {
- Intent intent = new Intent(IBluetoothInputHost.class.getName());
+ Intent intent = new Intent(IBluetoothHidDevice.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
@@ -285,7 +285,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
public List<BluetoothDevice> getConnectedDevices() {
Log.v(TAG, "getConnectedDevices()");
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
return service.getConnectedDevices();
@@ -306,7 +306,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
return service.getDevicesMatchingConnectionStates(states);
@@ -327,7 +327,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
public int getConnectionState(BluetoothDevice device) {
Log.v(TAG, "getConnectionState(): device=" + device);
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
return service.getConnectionState(device);
@@ -367,7 +367,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
return false;
}
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
BluetoothHidDeviceAppConfiguration config =
@@ -401,7 +401,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.unregisterApp(config);
@@ -426,7 +426,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.sendReport(device, id, data);
@@ -454,7 +454,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.replyReport(device, type, id, data);
@@ -480,7 +480,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.reportError(device, error);
@@ -504,7 +504,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.unplug(device);
@@ -529,7 +529,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.connect(device);
@@ -553,7 +553,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.disconnect(device);
diff --git a/android/bluetooth/BluetoothInputDevice.java b/android/bluetooth/BluetoothHidHost.java
index 32615761..8ad0f9d0 100644
--- a/android/bluetooth/BluetoothInputDevice.java
+++ b/android/bluetooth/BluetoothHidHost.java
@@ -35,16 +35,16 @@ import java.util.List;
* This class provides the public APIs to control the Bluetooth Input
* Device Profile.
*
- * <p>BluetoothInputDevice is a proxy object for controlling the Bluetooth
+ * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
- * the BluetoothInputDevice proxy object.
+ * the BluetoothHidHost proxy object.
*
* <p>Each method is protected with its appropriate permission.
*
* @hide
*/
-public final class BluetoothInputDevice implements BluetoothProfile {
- private static final String TAG = "BluetoothInputDevice";
+public final class BluetoothHidHost implements BluetoothProfile {
+ private static final String TAG = "BluetoothHidHost";
private static final boolean DBG = true;
private static final boolean VDBG = false;
@@ -177,52 +177,52 @@ public final class BluetoothInputDevice implements BluetoothProfile {
* @hide
*/
public static final String EXTRA_PROTOCOL_MODE =
- "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE";
+ "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE";
/**
* @hide
*/
public static final String EXTRA_REPORT_TYPE =
- "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE";
+ "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE";
/**
* @hide
*/
public static final String EXTRA_REPORT_ID =
- "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID";
+ "android.bluetooth.BluetoothHidHost.extra.REPORT_ID";
/**
* @hide
*/
public static final String EXTRA_REPORT_BUFFER_SIZE =
- "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE";
+ "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE";
/**
* @hide
*/
- public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT";
+ public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT";
/**
* @hide
*/
- public static final String EXTRA_STATUS = "android.bluetooth.BluetoothInputDevice.extra.STATUS";
+ public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS";
/**
* @hide
*/
public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
- "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
+ "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS";
/**
* @hide
*/
public static final String EXTRA_IDLE_TIME =
- "android.bluetooth.BluetoothInputDevice.extra.IDLE_TIME";
+ "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
private Context mContext;
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
- private volatile IBluetoothInputDevice mService;
+ private volatile IBluetoothHidHost mService;
private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
new IBluetoothStateChangeCallback.Stub() {
@@ -254,10 +254,10 @@ public final class BluetoothInputDevice implements BluetoothProfile {
};
/**
- * Create a BluetoothInputDevice proxy object for interacting with the local
+ * Create a BluetoothHidHost proxy object for interacting with the local
* Bluetooth Service which handles the InputDevice profile
*/
- /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
+ /*package*/ BluetoothHidHost(Context context, ServiceListener l) {
mContext = context;
mServiceListener = l;
mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -275,7 +275,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
}
boolean doBind() {
- Intent intent = new Intent(IBluetoothInputDevice.class.getName());
+ Intent intent = new Intent(IBluetoothHidHost.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
@@ -331,7 +331,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.connect(device);
@@ -371,7 +371,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.disconnect(device);
@@ -390,7 +390,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
@Override
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled()) {
try {
return service.getConnectedDevices();
@@ -409,7 +409,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled()) {
try {
return service.getDevicesMatchingConnectionStates(states);
@@ -428,7 +428,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
@Override
public int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getConnectionState(device);
@@ -458,7 +458,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
if (priority != BluetoothProfile.PRIORITY_OFF
&& priority != BluetoothProfile.PRIORITY_ON) {
@@ -490,7 +490,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getPriority(device);
@@ -506,11 +506,11 @@ public final class BluetoothInputDevice implements BluetoothProfile {
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
- mService = IBluetoothInputDevice.Stub.asInterface(Binder.allowBlocking(service));
+ mService = IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE,
- BluetoothInputDevice.this);
+ mServiceListener.onServiceConnected(BluetoothProfile.HID_HOST,
+ BluetoothHidHost.this);
}
}
@@ -518,7 +518,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
if (DBG) Log.d(TAG, "Proxy object disconnected");
mService = null;
if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HID_HOST);
}
}
};
@@ -542,7 +542,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean virtualUnplug(BluetoothDevice device) {
if (DBG) log("virtualUnplug(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.virtualUnplug(device);
@@ -568,7 +568,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean getProtocolMode(BluetoothDevice device) {
if (VDBG) log("getProtocolMode(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getProtocolMode(device);
@@ -592,7 +592,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
if (DBG) log("setProtocolMode(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.setProtocolMode(device, protocolMode);
@@ -623,7 +623,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
+ "bufferSize=" + bufferSize);
}
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getReport(device, reportType, reportId, bufferSize);
@@ -649,7 +649,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean setReport(BluetoothDevice device, byte reportType, String report) {
if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.setReport(device, reportType, report);
@@ -674,7 +674,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean sendData(BluetoothDevice device, String report) {
if (DBG) log("sendData(" + device + "), report=" + report);
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.sendData(device, report);
@@ -698,7 +698,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean getIdleTime(BluetoothDevice device) {
if (DBG) log("getIdletime(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getIdleTime(device);
@@ -723,7 +723,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.setIdleTime(device, idleTime);
diff --git a/android/bluetooth/BluetoothProfile.java b/android/bluetooth/BluetoothProfile.java
index bc8fa846..46a230b5 100644
--- a/android/bluetooth/BluetoothProfile.java
+++ b/android/bluetooth/BluetoothProfile.java
@@ -73,11 +73,11 @@ public interface BluetoothProfile {
public static final int HEALTH = 3;
/**
- * Input Device Profile
+ * HID Host
*
* @hide
*/
- public static final int INPUT_DEVICE = 4;
+ public static final int HID_HOST = 4;
/**
* PAN Profile
@@ -152,11 +152,11 @@ public interface BluetoothProfile {
public static final int MAP_CLIENT = 18;
/**
- * Input Host
+ * HID Device
*
* @hide
*/
- public static final int INPUT_HOST = 19;
+ public static final int HID_DEVICE = 19;
/**
* Max profile ID. This value should be updated whenever a new profile is added to match
diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java
index 5b2bf456..cdeaea3e 100644
--- a/android/content/ContentProvider.java
+++ b/android/content/ContentProvider.java
@@ -2099,8 +2099,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public static Uri maybeAddUserId(Uri uri, int userId) {
if (uri == null) return null;
if (userId != UserHandle.USER_CURRENT
- && (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
- || ContentResolver.SCHEME_SLICE.equals(uri.getScheme()))) {
+ && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
if (!uriHasUserId(uri)) {
//We don't add the user Id if there's already one
Uri.Builder builder = uri.buildUpon();
diff --git a/android/content/ContentResolver.java b/android/content/ContentResolver.java
index 02e70f55..9ccc552f 100644
--- a/android/content/ContentResolver.java
+++ b/android/content/ContentResolver.java
@@ -47,8 +47,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.slice.Slice;
-import android.slice.SliceProvider;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -180,8 +178,6 @@ public abstract class ContentResolver {
public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED =
new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
- /** @hide */
- public static final String SCHEME_SLICE = "slice";
public static final String SCHEME_CONTENT = "content";
public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
public static final String SCHEME_FILE = "file";
@@ -1722,36 +1718,6 @@ public abstract class ContentResolver {
}
/**
- * Turns a slice Uri into slice content.
- *
- * @param uri The URI to a slice provider
- * @return The Slice provided by the app or null if none is given.
- * @see Slice
- * @hide
- */
- public final @Nullable Slice bindSlice(@NonNull Uri uri) {
- Preconditions.checkNotNull(uri, "uri");
- IContentProvider provider = acquireProvider(uri);
- if (provider == null) {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- try {
- Bundle extras = new Bundle();
- extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
- final Bundle res = provider.call(mPackageName, SliceProvider.METHOD_SLICE, null,
- extras);
- Bundle.setDefusable(res, true);
- return res.getParcelable(SliceProvider.EXTRA_SLICE);
- } catch (RemoteException e) {
- // Arbitrary and not worth documenting, as Activity
- // Manager will kill this process shortly anyway.
- return null;
- } finally {
- releaseProvider(provider);
- }
- }
-
- /**
* Returns the content provider for the given content URI.
*
* @param uri The URI to a content provider
@@ -1759,7 +1725,7 @@ public abstract class ContentResolver {
* @hide
*/
public final IContentProvider acquireProvider(Uri uri) {
- if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) {
+ if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
final String auth = uri.getAuthority();
diff --git a/android/content/Context.java b/android/content/Context.java
index 20fbf046..c165fb3e 100644
--- a/android/content/Context.java
+++ b/android/content/Context.java
@@ -3604,7 +3604,6 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.text.ClipboardManager} for accessing and modifying
* {@link android.content.ClipboardManager} for accessing and modifying
* the contents of the global clipboard.
*
diff --git a/android/content/Intent.java b/android/content/Intent.java
index c9ad9519..e47de752 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -53,6 +53,7 @@ import android.provider.OpenableColumns;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.XmlUtils;
@@ -9371,6 +9372,57 @@ public class Intent implements Parcelable, Cloneable {
}
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
+ boolean extras, boolean clip) {
+ long token = proto.start(fieldId);
+ if (mAction != null) {
+ proto.write(IntentProto.ACTION, mAction);
+ }
+ if (mCategories != null) {
+ for (String category : mCategories) {
+ proto.write(IntentProto.CATEGORIES, category);
+ }
+ }
+ if (mData != null) {
+ proto.write(IntentProto.DATA, secure ? mData.toSafeString() : mData.toString());
+ }
+ if (mType != null) {
+ proto.write(IntentProto.TYPE, mType);
+ }
+ if (mFlags != 0) {
+ proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags));
+ }
+ if (mPackage != null) {
+ proto.write(IntentProto.PACKAGE, mPackage);
+ }
+ if (comp && mComponent != null) {
+ proto.write(IntentProto.COMPONENT, mComponent.flattenToShortString());
+ }
+ if (mSourceBounds != null) {
+ proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString());
+ }
+ if (mClipData != null) {
+ StringBuilder b = new StringBuilder();
+ if (clip) {
+ mClipData.toShortString(b);
+ } else {
+ mClipData.toShortStringShortItems(b, false);
+ }
+ proto.write(IntentProto.CLIP_DATA, b.toString());
+ }
+ if (extras && mExtras != null) {
+ proto.write(IntentProto.EXTRAS, mExtras.toShortString());
+ }
+ if (mContentUserHint != 0) {
+ proto.write(IntentProto.CONTENT_USER_HINT, mContentUserHint);
+ }
+ if (mSelector != null) {
+ proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
+ }
+ proto.end(token);
+ }
+
/**
* Call {@link #toUri} with 0 flags.
* @deprecated Use {@link #toUri} instead.
diff --git a/android/content/IntentFilter.java b/android/content/IntentFilter.java
index c9bce530..a957aed8 100644
--- a/android/content/IntentFilter.java
+++ b/android/content/IntentFilter.java
@@ -26,6 +26,7 @@ import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.XmlUtils;
@@ -918,6 +919,15 @@ public class IntentFilter implements Parcelable {
dest.writeInt(mPort);
}
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ // The original host information is already contained in host and wild, no output now.
+ proto.write(AuthorityEntryProto.HOST, mHost);
+ proto.write(AuthorityEntryProto.WILD, mWild);
+ proto.write(AuthorityEntryProto.PORT, mPort);
+ proto.end(token);
+ }
+
public String getHost() {
return mOrigHost;
}
@@ -1739,6 +1749,59 @@ public class IntentFilter implements Parcelable {
}
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ if (mActions.size() > 0) {
+ Iterator<String> it = mActions.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.ACTIONS, it.next());
+ }
+ }
+ if (mCategories != null) {
+ Iterator<String> it = mCategories.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.CATEGORIES, it.next());
+ }
+ }
+ if (mDataSchemes != null) {
+ Iterator<String> it = mDataSchemes.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.DATA_SCHEMES, it.next());
+ }
+ }
+ if (mDataSchemeSpecificParts != null) {
+ Iterator<PatternMatcher> it = mDataSchemeSpecificParts.iterator();
+ while (it.hasNext()) {
+ it.next().writeToProto(proto, IntentFilterProto.DATA_SCHEME_SPECS);
+ }
+ }
+ if (mDataAuthorities != null) {
+ Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+ while (it.hasNext()) {
+ it.next().writeToProto(proto, IntentFilterProto.DATA_AUTHORITIES);
+ }
+ }
+ if (mDataPaths != null) {
+ Iterator<PatternMatcher> it = mDataPaths.iterator();
+ while (it.hasNext()) {
+ it.next().writeToProto(proto, IntentFilterProto.DATA_PATHS);
+ }
+ }
+ if (mDataTypes != null) {
+ Iterator<String> it = mDataTypes.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.DATA_TYPES, it.next());
+ }
+ }
+ if (mPriority != 0 || mHasPartialTypes) {
+ proto.write(IntentFilterProto.PRIORITY, mPriority);
+ proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, mHasPartialTypes);
+ }
+ proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+ proto.end(token);
+ }
+
public void dump(Printer du, String prefix) {
StringBuilder sb = new StringBuilder(256);
if (mActions.size() > 0) {
diff --git a/android/content/pm/FeatureInfo.java b/android/content/pm/FeatureInfo.java
index 9ee6fa24..ff9fd8ec 100644
--- a/android/content/pm/FeatureInfo.java
+++ b/android/content/pm/FeatureInfo.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
/**
* Definition of a single optional hardware or software feature of an Android
@@ -113,6 +114,18 @@ public class FeatureInfo implements Parcelable {
dest.writeInt(flags);
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ if (name != null) {
+ proto.write(FeatureInfoProto.NAME, name);
+ }
+ proto.write(FeatureInfoProto.VERSION, version);
+ proto.write(FeatureInfoProto.GLES_VERSION, getGlEsVersion());
+ proto.write(FeatureInfoProto.FLAGS, flags);
+ proto.end(token);
+ }
+
public static final Creator<FeatureInfo> CREATOR = new Creator<FeatureInfo>() {
@Override
public FeatureInfo createFromParcel(Parcel source) {
diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java
index aa9562ff..b94a410b 100644
--- a/android/content/pm/LauncherApps.java
+++ b/android/content/pm/LauncherApps.java
@@ -20,8 +20,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
-import android.annotation.SystemService;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@@ -37,10 +37,10 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.AdaptiveIconDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -282,12 +282,27 @@ public class LauncherApps {
public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
/**
- * Does not retrieve CHOOSER only shortcuts.
- * TODO: Add another flag for MATCH_ALL_PINNED
+ * @hide include all pinned shortcuts by any launchers, not just by the caller,
+ * in the result.
+ * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS}
+ * permission, this flag will be ignored.
+ */
+ @TestApi
+ public static final int FLAG_MATCH_ALL_PINNED = 1 << 10;
+
+ /**
+ * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
* @hide
*/
public static final int FLAG_MATCH_ALL_KINDS =
- FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+
+ /**
+ * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED
+ * @hide
+ */
+ public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
+ FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED;
/** @hide kept for unit tests */
@Deprecated
@@ -319,6 +334,7 @@ public class LauncherApps {
FLAG_MATCH_PINNED,
FLAG_MATCH_MANIFEST,
FLAG_GET_KEY_FIELDS_ONLY,
+ FLAG_MATCH_MANIFEST,
})
@Retention(RetentionPolicy.SOURCE)
public @interface QueryFlags {}
@@ -678,6 +694,21 @@ public class LauncherApps {
}
}
+ private List<ShortcutInfo> maybeUpdateDisabledMessage(List<ShortcutInfo> shortcuts) {
+ if (shortcuts == null) {
+ return null;
+ }
+ for (int i = shortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = shortcuts.get(i);
+ final String message = ShortcutInfo.getDisabledReasonForRestoreIssue(mContext,
+ si.getDisabledReason());
+ if (message != null) {
+ si.setDisabledMessage(message);
+ }
+ }
+ return shortcuts;
+ }
+
/**
* Returns {@link ShortcutInfo}s that match {@code query}.
*
@@ -698,10 +729,16 @@ public class LauncherApps {
@NonNull UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
- return mService.getShortcuts(mContext.getPackageName(),
+ // Note this is the only case we need to update the disabled message for shortcuts
+ // that weren't restored.
+ // The restore problem messages are only shown by the user, and publishers will never
+ // see them. The only other API that the launcher gets shortcuts is the shortcut
+ // changed callback, but that only returns shortcuts with the "key" information, so
+ // that won't return disabled message.
+ return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
query.mQueryFlags, user)
- .getList();
+ .getList());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index be7f921e..143c51da 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -467,6 +467,7 @@ public abstract class PackageManagerInternal {
/** Updates the flags for the given permission. */
public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
@NonNull String packageName, int flagMask, int flagValues, int userId);
- /** temporary until mPermissionTrees is moved to PermissionManager */
- public abstract Object enforcePermissionTreeTEMP(@NonNull String permName, int callingUid);
+ /** Returns a PermissionGroup. */
+ public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
+ @NonNull String groupName);
}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index 6c7c8a07..ad36139a 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -3711,17 +3711,15 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_IS_GAME;
}
- if (false) {
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
- false)) {
- ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
- // A heavy-weight application can not be in a custom process.
- // We can do direct compare because we intern all strings.
- if (ai.processName != null && ai.processName != ai.packageName) {
- outError[0] = "cantSaveState applications can not use custom processes";
- }
+ // A heavy-weight application can not be in a custom process.
+ // We can do direct compare because we intern all strings.
+ if (ai.processName != null && !ai.processName.equals(ai.packageName)) {
+ outError[0] = "cantSaveState applications can not use custom processes";
}
}
}
@@ -6849,6 +6847,11 @@ public class PackageParser {
dest.writeParcelable(group, flags);
}
+ /** @hide */
+ public boolean isAppOp() {
+ return info.isAppOp();
+ }
+
private Permission(Parcel in) {
super(in);
final ClassLoader boot = Object.class.getClassLoader();
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index b45c26ce..5dd7aeda 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -353,6 +353,11 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
return size;
}
+ /** @hide */
+ public boolean isAppOp() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+ }
+
public static final Creator<PermissionInfo> CREATOR =
new Creator<PermissionInfo>() {
@Override
diff --git a/android/content/pm/ResolveInfo.java b/android/content/pm/ResolveInfo.java
index 79931670..3f63d80f 100644
--- a/android/content/pm/ResolveInfo.java
+++ b/android/content/pm/ResolveInfo.java
@@ -222,6 +222,40 @@ public class ResolveInfo implements Parcelable {
}
/**
+ * @return The resource that would be used when loading
+ * the label for this resolve info.
+ *
+ * @hide
+ */
+ public int resolveLabelResId() {
+ if (labelRes != 0) {
+ return labelRes;
+ }
+ final ComponentInfo componentInfo = getComponentInfo();
+ if (componentInfo.labelRes != 0) {
+ return componentInfo.labelRes;
+ }
+ return componentInfo.applicationInfo.labelRes;
+ }
+
+ /**
+ * @return The resource that would be used when loading
+ * the icon for this resolve info.
+ *
+ * @hide
+ */
+ public int resolveIconResId() {
+ if (icon != 0) {
+ return icon;
+ }
+ final ComponentInfo componentInfo = getComponentInfo();
+ if (componentInfo.icon != 0) {
+ return componentInfo.icon;
+ }
+ return componentInfo.applicationInfo.icon;
+ }
+
+ /**
* Retrieve the current graphical icon associated with this resolution. This
* will call back on the given PackageManager to load the icon from
* the application.
diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java
index 6b9c7537..9ff07757 100644
--- a/android/content/pm/ShortcutInfo.java
+++ b/android/content/pm/ShortcutInfo.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.TaskStackBuilder;
import android.content.ComponentName;
@@ -100,6 +101,13 @@ public final class ShortcutInfo implements Parcelable {
/** @hide When this is set, the bitmap icon is waiting to be saved. */
public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11;
+ /**
+ * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been
+ * installed yet.
+ * @hide
+ */
+ public static final int FLAG_SHADOW = 1 << 12;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -158,6 +166,124 @@ public final class ShortcutInfo implements Parcelable {
public @interface CloneFlags {}
/**
+ * Shortcut is not disabled.
+ */
+ public static final int DISABLED_REASON_NOT_DISABLED = 0;
+
+ /**
+ * Shortcut has been disabled by the publisher app with the
+ * {@link ShortcutManager#disableShortcuts(List)} API.
+ */
+ public static final int DISABLED_REASON_BY_APP = 1;
+
+ /**
+ * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut
+ * no longer exists.)
+ */
+ public static final int DISABLED_REASON_APP_CHANGED = 2;
+
+ /**
+ * A disabled reason that's equal to or bigger than this is due to backup and restore issue.
+ * A shortcut with such a reason wil be visible to the launcher, but not to the publisher.
+ * ({@link #isVisibleToPublisher()} will be false.)
+ */
+ private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100;
+
+ /**
+ * Shortcut has been restored from the previous device, but the publisher app on the current
+ * device is of a lower version. The shortcut will not be usable until the app is upgraded to
+ * the same version or higher.
+ */
+ public static final int DISABLED_REASON_VERSION_LOWER = 100;
+
+ /**
+ * Shortcut has not been restored because the publisher app does not support backup and restore.
+ */
+ public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101;
+
+ /**
+ * Shortcut has not been restored because the publisher app's signature has changed.
+ */
+ public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102;
+
+ /**
+ * Shortcut has not been restored for unknown reason.
+ */
+ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+
+ /** @hide */
+ @IntDef(value = {
+ DISABLED_REASON_NOT_DISABLED,
+ DISABLED_REASON_BY_APP,
+ DISABLED_REASON_APP_CHANGED,
+ DISABLED_REASON_VERSION_LOWER,
+ DISABLED_REASON_BACKUP_NOT_SUPPORTED,
+ DISABLED_REASON_SIGNATURE_MISMATCH,
+ DISABLED_REASON_OTHER_RESTORE_ISSUE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisabledReason{}
+
+ /**
+ * Return a label for disabled reasons, which are *not* supposed to be shown to the user.
+ * @hide
+ */
+ public static String getDisabledReasonDebugString(@DisabledReason int disabledReason) {
+ switch (disabledReason) {
+ case DISABLED_REASON_NOT_DISABLED:
+ return "[Not disabled]";
+ case DISABLED_REASON_BY_APP:
+ return "[Disabled: by app]";
+ case DISABLED_REASON_APP_CHANGED:
+ return "[Disabled: app changed]";
+ case DISABLED_REASON_VERSION_LOWER:
+ return "[Disabled: lower version]";
+ case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+ return "[Disabled: backup not supported]";
+ case DISABLED_REASON_SIGNATURE_MISMATCH:
+ return "[Disabled: signature mismatch]";
+ case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+ return "[Disabled: unknown restore issue]";
+ }
+ return "[Disabled: unknown reason:" + disabledReason + "]";
+ }
+
+ /**
+ * Return a label for a disabled reason for shortcuts that are disabled due to a backup and
+ * restore issue. If the reason is not due to backup & restore, then it'll return null.
+ *
+ * This method returns localized, user-facing strings, which will be returned by
+ * {@link #getDisabledMessage()}.
+ *
+ * @hide
+ */
+ public static String getDisabledReasonForRestoreIssue(Context context,
+ @DisabledReason int disabledReason) {
+ final Resources res = context.getResources();
+
+ switch (disabledReason) {
+ case DISABLED_REASON_VERSION_LOWER:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restored_on_lower_version);
+ case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_not_supported);
+ case DISABLED_REASON_SIGNATURE_MISMATCH:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_signature_mismatch);
+ case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_unknown_issue);
+ }
+ return null;
+ }
+
+ /** @hide */
+ public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) {
+ return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START;
+ }
+
+ /**
* Shortcut category for messaging related actions, such as chat.
*/
public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -240,6 +366,11 @@ public final class ShortcutInfo implements Parcelable {
private final int mUserId;
+ /** @hide */
+ public static final int VERSION_CODE_UNKNOWN = -1;
+
+ private int mDisabledReason;
+
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
@@ -352,6 +483,7 @@ public final class ShortcutInfo implements Parcelable {
mActivity = source.mActivity;
mFlags = source.mFlags;
mLastChangedTimestamp = source.mLastChangedTimestamp;
+ mDisabledReason = source.mDisabledReason;
// Just always keep it since it's cheep.
mIconResId = source.mIconResId;
@@ -615,13 +747,23 @@ public final class ShortcutInfo implements Parcelable {
/**
* @hide
+ *
+ * @isUpdating set true if it's "update", as opposed to "replace".
*/
- public void ensureUpdatableWith(ShortcutInfo source) {
+ public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) {
+ if (isUpdating) {
+ Preconditions.checkState(isVisibleToPublisher(),
+ "[Framework BUG] Invisible shortcuts can't be updated");
+ }
Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
Preconditions.checkState(mId.equals(source.mId), "ID must match");
Preconditions.checkState(mPackageName.equals(source.mPackageName),
"Package name must match");
- Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+
+ if (isVisibleToPublisher()) {
+ // Don't do this check for restore-blocked shortcuts.
+ Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+ }
}
/**
@@ -638,7 +780,7 @@ public final class ShortcutInfo implements Parcelable {
* @hide
*/
public void copyNonNullFieldsFrom(ShortcutInfo source) {
- ensureUpdatableWith(source);
+ ensureUpdatableWith(source, /*isUpdating=*/ true);
if (source.mActivity != null) {
mActivity = source.mActivity;
@@ -1169,6 +1311,19 @@ public final class ShortcutInfo implements Parcelable {
return mDisabledMessageResId;
}
+ /** @hide */
+ public void setDisabledReason(@DisabledReason int reason) {
+ mDisabledReason = reason;
+ }
+
+ /**
+ * Returns why a shortcut has been disabled.
+ */
+ @DisabledReason
+ public int getDisabledReason() {
+ return mDisabledReason;
+ }
+
/**
* Return the shortcut's categories.
*
@@ -1403,6 +1558,21 @@ public final class ShortcutInfo implements Parcelable {
return hasFlags(FLAG_IMMUTABLE);
}
+ /** @hide */
+ public boolean isDynamicVisible() {
+ return isDynamic() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isPinnedVisible() {
+ return isPinned() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isManifestVisible() {
+ return isDeclaredInManifest() && isVisibleToPublisher();
+ }
+
/**
* Return if a shortcut is immutable, in which case it cannot be modified with any of
* {@link ShortcutManager} APIs.
@@ -1491,6 +1661,18 @@ public final class ShortcutInfo implements Parcelable {
}
/**
+ * When the system wasn't able to restore a shortcut, it'll still be registered to the system
+ * but disabled, and such shortcuts will not be visible to the publisher. They're still visible
+ * to launchers though.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleToPublisher() {
+ return !isDisabledForRestoreIssue(mDisabledReason);
+ }
+
+ /**
* Return whether a shortcut only contains "key" information only or not. If true, only the
* following fields are available.
* <ul>
@@ -1668,6 +1850,7 @@ public final class ShortcutInfo implements Parcelable {
mFlags = source.readInt();
mIconResId = source.readInt();
mLastChangedTimestamp = source.readLong();
+ mDisabledReason = source.readInt();
if (source.readInt() == 0) {
return; // key information only.
@@ -1711,6 +1894,7 @@ public final class ShortcutInfo implements Parcelable {
dest.writeInt(mFlags);
dest.writeInt(mIconResId);
dest.writeLong(mLastChangedTimestamp);
+ dest.writeInt(mDisabledReason);
if (hasKeyFieldsOnly()) {
dest.writeInt(0);
@@ -1808,6 +1992,11 @@ public final class ShortcutInfo implements Parcelable {
sb.append(", flags=0x");
sb.append(Integer.toHexString(mFlags));
sb.append(" [");
+ if ((mFlags & FLAG_SHADOW) != 0) {
+ // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
+ // we don't have an isXxx for this.
+ sb.append("Sdw");
+ }
if (!isEnabled()) {
sb.append("Dis");
}
@@ -1848,7 +2037,9 @@ public final class ShortcutInfo implements Parcelable {
sb.append("packageName=");
sb.append(mPackageName);
- sb.append(", activity=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("activity=");
sb.append(mActivity);
addIndentOrComma(sb, indent);
@@ -1883,6 +2074,11 @@ public final class ShortcutInfo implements Parcelable {
addIndentOrComma(sb, indent);
+ sb.append("disabledReason=");
+ sb.append(getDisabledReasonDebugString(mDisabledReason));
+
+ addIndentOrComma(sb, indent);
+
sb.append("categories=");
sb.append(mCategories);
@@ -1953,7 +2149,7 @@ public final class ShortcutInfo implements Parcelable {
CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
long lastChangedTimestamp,
- int flags, int iconResId, String iconResName, String bitmapPath) {
+ int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) {
mUserId = userId;
mId = id;
mPackageName = packageName;
@@ -1978,5 +2174,6 @@ public final class ShortcutInfo implements Parcelable {
mIconResId = iconResId;
mIconResName = iconResName;
mBitmapPath = bitmapPath;
+ mDisabledReason = disabledReason;
}
}
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
index 7b7d8ae4..7fc25d82 100644
--- a/android/content/pm/ShortcutServiceInternal.java
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -46,7 +46,7 @@ public abstract class ShortcutServiceInternal {
@NonNull String callingPackage, long changedSince,
@Nullable String packageName, @Nullable List<String> shortcutIds,
@Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
- int userId);
+ int userId, int callingPid, int callingUid);
public abstract boolean
isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
@@ -58,7 +58,8 @@ public abstract class ShortcutServiceInternal {
public abstract Intent[] createShortcutIntents(
int launcherUserId, @NonNull String callingPackage,
- @NonNull String packageName, @NonNull String shortcutId, int userId);
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ int callingPid, int callingUid);
public abstract void addListener(@NonNull ShortcutChangeListener listener);
@@ -70,7 +71,7 @@ public abstract class ShortcutServiceInternal {
@NonNull String packageName, @NonNull String shortcutId, int userId);
public abstract boolean hasShortcutHostPermission(int launcherUserId,
- @NonNull String callingPackage);
+ @NonNull String callingPackage, int callingPid, int callingUid);
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
diff --git a/android/content/res/FontResourcesParser.java b/android/content/res/FontResourcesParser.java
index 042eb87f..28e9fce3 100644
--- a/android/content/res/FontResourcesParser.java
+++ b/android/content/res/FontResourcesParser.java
@@ -15,7 +15,6 @@
*/
package android.content.res;
-import com.android.internal.R;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Typeface;
@@ -23,6 +22,8 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -78,12 +79,15 @@ public class FontResourcesParser {
private final @NonNull String mFileName;
private int mWeight;
private int mItalic;
+ private int mTtcIndex;
private int mResourceId;
- public FontFileResourceEntry(@NonNull String fileName, int weight, int italic) {
+ public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
+ int ttcIndex) {
mFileName = fileName;
mWeight = weight;
mItalic = italic;
+ mTtcIndex = ttcIndex;
}
public @NonNull String getFileName() {
@@ -97,6 +101,10 @@ public class FontResourcesParser {
public int getItalic() {
return mItalic;
}
+
+ public int getTtcIndex() {
+ return mTtcIndex;
+ }
}
// A class represents file based font-family element in xml file.
@@ -203,6 +211,7 @@ public class FontResourcesParser {
Typeface.RESOLVE_BY_FONT_TABLE);
int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
Typeface.RESOLVE_BY_FONT_TABLE);
+ int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
String filename = array.getString(R.styleable.FontFamilyFont_font);
array.recycle();
while (parser.next() != XmlPullParser.END_TAG) {
@@ -211,7 +220,7 @@ public class FontResourcesParser {
if (filename == null) {
return null;
}
- return new FontFileResourceEntry(filename, weight, italic);
+ return new FontFileResourceEntry(filename, weight, italic, 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 a8b8c4b5..386239cf 100644
--- a/android/content/res/ResourcesImpl.java
+++ b/android/content/res/ResourcesImpl.java
@@ -796,7 +796,7 @@ public class ResourcesImpl {
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
- } catch (Exception e) {
+ } catch (Exception | StackOverflowError e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
diff --git a/android/database/CursorWindow.java b/android/database/CursorWindow.java
index a75372ff..f84ec65f 100644
--- a/android/database/CursorWindow.java
+++ b/android/database/CursorWindow.java
@@ -16,8 +16,7 @@
package android.database;
-import dalvik.system.CloseGuard;
-
+import android.annotation.BytesLong;
import android.content.res.Resources;
import android.database.sqlite.SQLiteClosable;
import android.database.sqlite.SQLiteException;
@@ -26,8 +25,10 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.util.Log;
-import android.util.SparseIntArray;
import android.util.LongSparseArray;
+import android.util.SparseIntArray;
+
+import dalvik.system.CloseGuard;
/**
* A buffer containing multiple cursor rows.
@@ -94,19 +95,29 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* @param name The name of the cursor window, or null if none.
*/
public CursorWindow(String name) {
+ this(name, getCursorWindowSize());
+ }
+
+ /**
+ * Creates a new empty cursor window and gives it a name.
+ * <p>
+ * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to
+ * set the number of columns before adding any rows to the cursor.
+ * </p>
+ *
+ * @param name The name of the cursor window, or null if none.
+ * @param windowSizeBytes Size of cursor window in bytes.
+ * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
+ * window. Depending on the amount of data stored, the actual amount of memory allocated can be
+ * lower than specified size, but cannot exceed it.
+ */
+ public CursorWindow(String name, @BytesLong long windowSizeBytes) {
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
- if (sCursorWindowSize < 0) {
- /** The cursor window size. resource xml file specifies the value in kB.
- * convert it to bytes here by multiplying with 1024.
- */
- sCursorWindowSize = Resources.getSystem().getInteger(
- com.android.internal.R.integer.config_cursorWindowSize) * 1024;
- }
- mWindowPtr = nativeCreate(mName, sCursorWindowSize);
+ mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
if (mWindowPtr == 0) {
throw new CursorWindowAllocationException("Cursor window allocation of " +
- (sCursorWindowSize / 1024) + " kb failed. " + printStats());
+ windowSizeBytes + " bytes failed. " + printStats());
}
mCloseGuard.open("close");
recordNewWindow(Binder.getCallingPid(), mWindowPtr);
@@ -773,6 +784,16 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
return "# Open Cursors=" + total + s;
}
+ private static int getCursorWindowSize() {
+ if (sCursorWindowSize < 0) {
+ // The cursor window size. resource xml file specifies the value in kB.
+ // convert it to bytes here by multiplying with 1024.
+ sCursorWindowSize = Resources.getSystem().getInteger(
+ com.android.internal.R.integer.config_cursorWindowSize) * 1024;
+ }
+ return sCursorWindowSize;
+ }
+
@Override
public String toString() {
return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
diff --git a/android/database/SQLiteDatabaseIoPerfTest.java b/android/database/SQLiteDatabaseIoPerfTest.java
new file mode 100644
index 00000000..7c5316d2
--- /dev/null
+++ b/android/database/SQLiteDatabaseIoPerfTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.database;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Performance tests for measuring amount of data written during typical DB operations
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabaseIoPerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabaseIoPerfTest {
+ private static final String TAG = "SQLiteDatabaseIoPerfTest";
+ private static final String DB_NAME = "db_io_perftest";
+ private static final int DEFAULT_DATASET_SIZE = 500;
+
+ private Long mWriteBytes;
+
+ private SQLiteDatabase mDatabase;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContext.deleteDatabase(DB_NAME);
+ mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+ mDatabase.execSQL("CREATE TABLE T1 "
+ + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+ }
+
+ @After
+ public void tearDown() {
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ }
+
+ @Test
+ public void testDatabaseModifications() {
+ startMeasuringWrites();
+ ContentValues cv = new ContentValues();
+ String[] whereArg = new String[1];
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ cv.put("_ID", i);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.0);
+ assertEquals(i, mDatabase.insert("T1", null, cv));
+ }
+ cv = new ContentValues();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ cv.put("COL_B", "UpdatedValue");
+ cv.put("COL_C", 1.1);
+ whereArg[0] = String.valueOf(i);
+ assertEquals(1, mDatabase.update("T1", cv, "_ID=?", whereArg));
+ }
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ whereArg[0] = String.valueOf(i);
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", whereArg));
+ }
+ // Make sure all changes are written to disk
+ mDatabase.close();
+ long bytes = endMeasuringWrites();
+ sendResults("testDatabaseModifications" , bytes);
+ }
+
+ @Test
+ public void testInsertsWithTransactions() {
+ startMeasuringWrites();
+ final int txSize = 10;
+ ContentValues cv = new ContentValues();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE * 5; i++) {
+ if (i % txSize == 0) {
+ mDatabase.beginTransaction();
+ }
+ if (i % txSize == txSize-1) {
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ }
+ cv.put("_ID", i);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.0);
+ assertEquals(i, mDatabase.insert("T1", null, cv));
+ }
+ // Make sure all changes are written to disk
+ mDatabase.close();
+ long bytes = endMeasuringWrites();
+ sendResults("testInsertsWithTransactions" , bytes);
+ }
+
+ private void startMeasuringWrites() {
+ Preconditions.checkState(mWriteBytes == null, "Measurement already started");
+ mWriteBytes = getIoStats().get("write_bytes");
+ }
+
+ private long endMeasuringWrites() {
+ Preconditions.checkState(mWriteBytes != null, "Measurement wasn't started");
+ Long newWriteBytes = getIoStats().get("write_bytes");
+ return newWriteBytes - mWriteBytes;
+ }
+
+ private void sendResults(String testName, long writeBytes) {
+ Log.i(TAG, testName + " write_bytes: " + writeBytes);
+ Bundle status = new Bundle();
+ status.putLong("write_bytes", writeBytes);
+ InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+ }
+
+ private static Map<String, Long> getIoStats() {
+ String ioStat = "/proc/self/io";
+ Map<String, Long> results = new ArrayMap<>();
+ try {
+ List<String> lines = Files.readAllLines(new File(ioStat).toPath());
+ for (String line : lines) {
+ line = line.trim();
+ String[] split = line.split(":");
+ if (split.length == 2) {
+ try {
+ String key = split[0].trim();
+ Long value = Long.valueOf(split[1].trim());
+ results.put(key, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Cannot parse number from " + line);
+ }
+ } else if (line.isEmpty()) {
+ Log.e(TAG, "Cannot parse line " + line);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read: " + ioStat, e);
+ }
+ return results;
+ }
+
+}
diff --git a/android/database/SQLiteDatabasePerfTest.java b/android/database/SQLiteDatabasePerfTest.java
new file mode 100644
index 00000000..7a32c0cc
--- /dev/null
+++ b/android/database/SQLiteDatabasePerfTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Performance tests for typical CRUD operations and loading rows into the Cursor
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabasePerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabasePerfTest {
+ // TODO b/64262688 Add Concurrency tests to compare WAL vs DELETE read/write
+ private static final String DB_NAME = "dbperftest";
+ private static final int DEFAULT_DATASET_SIZE = 1000;
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+ private SQLiteDatabase mDatabase;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContext.deleteDatabase(DB_NAME);
+ mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+ mDatabase.execSQL("CREATE TABLE T1 "
+ + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+ mDatabase.execSQL("CREATE TABLE T2 ("
+ + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
+ + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+ }
+
+ @After
+ public void tearDown() {
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ }
+
+ @Test
+ public void testSelect() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID=?", new String[]{String.valueOf(index)})) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ }
+ }
+ }
+
+ @Test
+ public void testSelectMultipleRows() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Random rnd = new Random(0);
+ final int querySize = 50;
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE - querySize - 1);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID BETWEEN ? and ? ORDER BY _ID",
+ new String[]{String.valueOf(index), String.valueOf(index + querySize - 1)})) {
+ int i = 0;
+ while(cursor.moveToNext()) {
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ index++;
+ i++;
+ }
+ assertEquals(querySize, i);
+ }
+ }
+ }
+
+ @Test
+ public void testInnerJoin() {
+ mDatabase.setForeignKeyConstraintsEnabled(true);
+ mDatabase.beginTransaction();
+ insertT1TestDataSet();
+ insertT2TestDataSet();
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(1000);
+ try (Cursor cursor = mDatabase.rawQuery(
+ "SELECT T1._ID, T1.COL_A, T1.COL_B, T1.COL_C, T2.COL_A FROM T1 "
+ + "INNER JOIN T2 on T2.T1_ID=T1._ID WHERE T1._ID = ?",
+ new String[]{String.valueOf(index)})) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ assertEquals("T2Value" + index, cursor.getString(4));
+ }
+ }
+ }
+
+ @Test
+ public void testInsert() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ ContentValues cv = new ContentValues();
+ cv.put("_ID", DEFAULT_DATASET_SIZE);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.1);
+ String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+ while (state.keepRunning()) {
+ assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
+ state.pauseTiming();
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+ state.resumeTiming();
+ }
+ }
+
+ @Test
+ public void testDelete() {
+ insertT1TestDataSet();
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+ Object[] insertsArgs = new Object[]{DEFAULT_DATASET_SIZE, DEFAULT_DATASET_SIZE,
+ "ValueToDelete", 1.1};
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)", insertsArgs);
+ state.resumeTiming();
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+ }
+ }
+
+ @Test
+ public void testUpdate() {
+ insertT1TestDataSet();
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ int i = 0;
+ ContentValues cv = new ContentValues();
+ String[] argArray = new String[1];
+ while (state.keepRunning()) {
+ int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "UpdatedValue");
+ cv.put("COL_C", i);
+ argArray[0] = String.valueOf(id);
+ assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
+ i++;
+ }
+ }
+
+ private void insertT1TestDataSet() {
+ mDatabase.beginTransaction();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)",
+ new Object[]{i, i, "T1Value" + i, i * 1.1});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ }
+
+ private void insertT2TestDataSet() {
+ mDatabase.beginTransaction();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ mDatabase.execSQL("INSERT INTO T2 VALUES (?, ?, ?)",
+ new Object[]{i, "T2Value" + i, i});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ }
+}
+
diff --git a/android/graphics/Bitmap.java b/android/graphics/Bitmap.java
index 57c75490..0072012f 100644
--- a/android/graphics/Bitmap.java
+++ b/android/graphics/Bitmap.java
@@ -21,6 +21,7 @@ import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.WorkerThread;
import android.content.res.ResourcesImpl;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1233,6 +1234,7 @@ public final class Bitmap implements Parcelable {
* @param stream The outputstream to write the compressed data.
* @return true if successfully compressed to the specified stream.
*/
+ @WorkerThread
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java
index ffb39e33..f5bf754a 100644
--- a/android/graphics/BitmapFactory.java
+++ b/android/graphics/BitmapFactory.java
@@ -354,6 +354,7 @@ public class BitmapFactory {
* decode, in the case of which a more accurate, but slightly slower,
* IDCT method will be used instead.
*/
+ @Deprecated
public boolean inPreferQualityOverSpeed;
/**
@@ -412,6 +413,7 @@ public class BitmapFactory {
* can check, inbetween the bounds decode and the image decode, to see
* if the operation is canceled.
*/
+ @Deprecated
public boolean mCancel;
/**
@@ -426,6 +428,7 @@ public class BitmapFactory {
* or if inJustDecodeBounds is true, will set outWidth/outHeight
* to -1
*/
+ @Deprecated
public void requestCancelDecode() {
mCancel = true;
}
diff --git a/android/graphics/Typeface.java b/android/graphics/Typeface.java
index cfc389f0..9961ed64 100644
--- a/android/graphics/Typeface.java
+++ b/android/graphics/Typeface.java
@@ -250,9 +250,9 @@ public class Typeface {
FontFamily fontFamily = new FontFamily();
for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
- // TODO: Add ttc and variation font support. (b/37853920)
+ // TODO: Add variation font support. (b/37853920)
if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
- 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
+ 0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
return null;
}
diff --git a/android/graphics/pdf/PdfEditor.java b/android/graphics/pdf/PdfEditor.java
index 0c509b77..3821bc7a 100644
--- a/android/graphics/pdf/PdfEditor.java
+++ b/android/graphics/pdf/PdfEditor.java
@@ -23,6 +23,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
+import android.system.Os;
import android.system.OsConstants;
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
@@ -72,8 +73,8 @@ public final class PdfEditor {
final long size;
try {
- Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
- size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+ Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+ size = Os.fstat(input.getFileDescriptor()).st_size;
} catch (ErrnoException ee) {
throw new IllegalArgumentException("file descriptor not seekable");
}
diff --git a/android/graphics/pdf/PdfRenderer.java b/android/graphics/pdf/PdfRenderer.java
index c82ab0dd..4a917052 100644
--- a/android/graphics/pdf/PdfRenderer.java
+++ b/android/graphics/pdf/PdfRenderer.java
@@ -26,12 +26,12 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
+import android.system.Os;
import android.system.OsConstants;
import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -156,8 +156,8 @@ public final class PdfRenderer implements AutoCloseable {
final long size;
try {
- Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
- size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+ Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+ size = Os.fstat(input.getFileDescriptor()).st_size;
} catch (ErrnoException ee) {
throw new IllegalArgumentException("file descriptor not seekable");
}
diff --git a/android/inputmethodservice/AbstractInputMethodService.java b/android/inputmethodservice/AbstractInputMethodService.java
index 29177b6b..185215a5 100644
--- a/android/inputmethodservice/AbstractInputMethodService.java
+++ b/android/inputmethodservice/AbstractInputMethodService.java
@@ -16,6 +16,7 @@
package android.inputmethodservice;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.app.Service;
import android.content.Intent;
@@ -62,6 +63,7 @@ public abstract class AbstractInputMethodService extends Service
* back to {@link AbstractInputMethodService#onCreateInputMethodSessionInterface()
* AbstractInputMethodService.onCreateInputMethodSessionInterface()}.
*/
+ @MainThread
public void createSession(SessionCallback callback) {
callback.sessionCreated(onCreateInputMethodSessionInterface());
}
@@ -71,6 +73,7 @@ public abstract class AbstractInputMethodService extends Service
* {@link AbstractInputMethodSessionImpl#revokeSelf()
* AbstractInputMethodSessionImpl.setEnabled()} method.
*/
+ @MainThread
public void setSessionEnabled(InputMethodSession session, boolean enabled) {
((AbstractInputMethodSessionImpl)session).setEnabled(enabled);
}
@@ -80,6 +83,7 @@ public abstract class AbstractInputMethodService extends Service
* {@link AbstractInputMethodSessionImpl#revokeSelf()
* AbstractInputMethodSessionImpl.revokeSelf()} method.
*/
+ @MainThread
public void revokeSession(InputMethodSession session) {
((AbstractInputMethodSessionImpl)session).revokeSelf();
}
diff --git a/android/inputmethodservice/IInputMethodWrapper.java b/android/inputmethodservice/IInputMethodWrapper.java
index 765aff96..2c7e51a1 100644
--- a/android/inputmethodservice/IInputMethodWrapper.java
+++ b/android/inputmethodservice/IInputMethodWrapper.java
@@ -16,14 +16,8 @@
package android.inputmethodservice;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethod;
-import com.android.internal.view.IInputMethodSession;
-import com.android.internal.view.IInputSessionCallback;
-import com.android.internal.view.InputConnectionWrapper;
-
+import android.annotation.BinderThread;
+import android.annotation.MainThread;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -41,11 +35,20 @@ import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.InputConnectionWrapper;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Implements the internal IInputMethod interface to convert incoming calls
@@ -67,17 +70,27 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_SHOW_SOFT_INPUT = 60;
private static final int DO_HIDE_SOFT_INPUT = 70;
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
-
+
final WeakReference<AbstractInputMethodService> mTarget;
final Context mContext;
final HandlerCaller mCaller;
final WeakReference<InputMethod> mInputMethod;
final int mTargetSdkVersion;
-
- static class Notifier {
- boolean notified;
- }
-
+
+ /**
+ * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
+ * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
+ * called or not, mainly to avoid unnecessary blocking operations.
+ *
+ * <p>This field must be set and cleared only from the binder thread(s), where the system
+ * guarantees that {@link #bindInput(InputBinding)},
+ * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+ * {@link #unbindInput()} are called with the same order as the original calls
+ * in {@link com.android.server.InputMethodManagerService}. See {@link IBinder#FLAG_ONEWAY}
+ * for detailed semantics.</p>
+ */
+ AtomicBoolean mIsUnbindIssued = null;
+
// NOTE: we should have a cache of these.
static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
final Context mContext;
@@ -108,20 +121,16 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
}
-
- public IInputMethodWrapper(AbstractInputMethodService context,
- InputMethod inputMethod) {
- mTarget = new WeakReference<AbstractInputMethodService>(context);
+
+ public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
+ mTarget = new WeakReference<>(context);
mContext = context.getApplicationContext();
mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
- mInputMethod = new WeakReference<InputMethod>(inputMethod);
+ mInputMethod = new WeakReference<>(inputMethod);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
}
- public InputMethod getInternalInputMethod() {
- return mInputMethod.get();
- }
-
+ @MainThread
@Override
public void executeMessage(Message msg) {
InputMethod inputMethod = mInputMethod.get();
@@ -169,8 +178,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
final IBinder startInputToken = (IBinder) args.arg1;
final IInputContext inputContext = (IInputContext) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
+ final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
final InputConnection ic = inputContext != null
- ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
+ ? new InputConnectionWrapper(
+ mTarget, inputContext, missingMethods, isUnbindIssued) : null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
startInputToken);
@@ -205,6 +216,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
Log.w(TAG, "Unhandled message code: " + msg.what);
}
+ @BinderThread
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
AbstractInputMethodService target = mTarget.get();
@@ -232,40 +244,63 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @BinderThread
@Override
public void attachToken(IBinder token) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
}
+ @BinderThread
@Override
public void bindInput(InputBinding binding) {
+ if (mIsUnbindIssued != null) {
+ Log.e(TAG, "bindInput must be paired with unbindInput.");
+ }
+ mIsUnbindIssued = new AtomicBoolean();
// This IInputContext is guaranteed to implement all the methods.
final int missingMethodFlags = 0;
InputConnection ic = new InputConnectionWrapper(mTarget,
- IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
+ IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
+ mIsUnbindIssued);
InputBinding nu = new InputBinding(ic, binding);
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
+ @BinderThread
@Override
public void unbindInput() {
+ if (mIsUnbindIssued != null) {
+ // Signal the flag then forget it.
+ mIsUnbindIssued.set(true);
+ mIsUnbindIssued = null;
+ } else {
+ Log.e(TAG, "unbindInput must be paired with bindInput.");
+ }
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
}
+ @BinderThread
@Override
public void startInput(IBinder startInputToken, IInputContext inputContext,
@InputConnectionInspector.MissingMethodFlags final int missingMethods,
EditorInfo attribute, boolean restarting) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT,
- missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute));
+ if (mIsUnbindIssued == null) {
+ Log.e(TAG, "startInput must be called after bindInput.");
+ mIsUnbindIssued = new AtomicBoolean();
+ }
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
+ missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
+ mIsUnbindIssued));
}
+ @BinderThread
@Override
public void createSession(InputChannel channel, IInputSessionCallback callback) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
channel, callback));
}
+ @BinderThread
@Override
public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
try {
@@ -282,6 +317,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @BinderThread
@Override
public void revokeSession(IInputMethodSession session) {
try {
@@ -297,18 +333,21 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @BinderThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
flags, resultReceiver));
}
+ @BinderThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
flags, resultReceiver));
}
+ @BinderThread
@Override
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
diff --git a/android/inputmethodservice/InputMethodService.java b/android/inputmethodservice/InputMethodService.java
index 7a20943e..223ed73b 100644
--- a/android/inputmethodservice/InputMethodService.java
+++ b/android/inputmethodservice/InputMethodService.java
@@ -382,8 +382,10 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public class InputMethodImpl extends AbstractInputMethodImpl {
/**
- * Take care of attaching the given window token provided by the system.
+ * {@inheritDoc}
*/
+ @MainThread
+ @Override
public void attachToken(IBinder token) {
if (mToken == null) {
mToken = token;
@@ -392,10 +394,12 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Handle a new input binding, calling
- * {@link InputMethodService#onBindInput InputMethodService.onBindInput()}
- * when done.
+ * {@inheritDoc}
+ *
+ * <p>Calls {@link InputMethodService#onBindInput()} when done.</p>
*/
+ @MainThread
+ @Override
public void bindInput(InputBinding binding) {
mInputBinding = binding;
mInputConnection = binding.getConnection();
@@ -409,8 +413,12 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Clear the current input binding.
+ * {@inheritDoc}
+ *
+ * <p>Calls {@link InputMethodService#onUnbindInput()} when done.</p>
*/
+ @MainThread
+ @Override
public void unbindInput() {
if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
+ " ic=" + mInputConnection);
@@ -419,11 +427,21 @@ public class InputMethodService extends AbstractInputMethodService {
mInputConnection = null;
}
+ /**
+ * {@inheritDoc}
+ */
+ @MainThread
+ @Override
public void startInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
doStartInput(ic, attribute, false);
}
+ /**
+ * {@inheritDoc}
+ */
+ @MainThread
+ @Override
public void restartInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute);
doStartInput(ic, attribute, true);
@@ -433,6 +451,7 @@ public class InputMethodService extends AbstractInputMethodService {
* {@inheritDoc}
* @hide
*/
+ @MainThread
@Override
public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
@@ -447,8 +466,10 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Handle a request by the system to hide the soft input area.
+ * {@inheritDoc}
*/
+ @MainThread
+ @Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
boolean wasVis = isInputViewShown();
@@ -465,8 +486,10 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Handle a request by the system to show the soft input area.
+ * {@inheritDoc}
*/
+ @MainThread
+ @Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
boolean wasVis = isInputViewShown();
@@ -495,6 +518,11 @@ public class InputMethodService extends AbstractInputMethodService {
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @MainThread
+ @Override
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
onCurrentInputMethodSubtypeChanged(subtype);
}
diff --git a/android/location/GnssMeasurement.java b/android/location/GnssMeasurement.java
index d24a4774..412cc291 100644
--- a/android/location/GnssMeasurement.java
+++ b/android/location/GnssMeasurement.java
@@ -612,6 +612,16 @@ public final class GnssMeasurement implements Parcelable {
*
* <pre>
* accumulated delta range = -k * carrier phase (where k is a constant)</pre>
+ *
+ * <p>Similar to the concept of an RTCM "Phaserange", when the accumulated delta range is
+ * initially chosen, and whenever it is reset, it will retain the integer nature
+ * of the relative carrier phase offset between satellites observed by this receiver, such that
+ * the double difference of this value between receivers and satellites may be used, together
+ * with integer ambiguity resolution, to determine highly precise relative location between
+ * receivers.
+ *
+ * <p>This includes ensuring that all half-cycle ambiguities are resolved before this value is
+ * reported as {@link #ADR_STATE_VALID}.
*/
public double getAccumulatedDeltaRangeMeters() {
return mAccumulatedDeltaRangeMeters;
@@ -861,7 +871,7 @@ public final class GnssMeasurement implements Parcelable {
}
/**
- * Gets the Signal-to-Noise ratio (SNR) in dB.
+ * Gets the (post-correlation & integration) Signal-to-Noise ratio (SNR) in dB.
*
* <p>The value is only available if {@link #hasSnrInDb()} is {@code true}.
*/
diff --git a/android/location/Location.java b/android/location/Location.java
index e8eaa59a..e7f903e8 100644
--- a/android/location/Location.java
+++ b/android/location/Location.java
@@ -813,15 +813,16 @@ public class Location implements Parcelable {
/**
* Get the estimated vertical accuracy of this location, in meters.
*
- * <p>We define vertical accuracy as the radius of 68% confidence. In other
- * words, if you draw a circle centered at this location's altitude, and with a radius
- * equal to the vertical accuracy, then there is a 68% probability that the true altitude is
- * inside the circle.
+ * <p>We define vertical accuracy at 68% confidence. Specifically, as 1-side of the
+ * 2-sided range above and below the estimated altitude reported by {@link #getAltitude()},
+ * within which there is a 68% probability of finding the true altitude.
*
- * <p>In statistical terms, it is assumed that location errors
- * are random with a normal distribution, so the 68% confidence circle
- * represents one standard deviation. Note that in practice, location
- * errors do not always follow such a simple distribution.
+ * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be
+ * considered 1 standard deviation.
+ *
+ * <p>For example, if {@link #getAltitude()} returns 150, and
+ * {@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.
*/
@@ -866,14 +867,16 @@ public class Location implements Parcelable {
/**
* Get the estimated speed accuracy of this location, in meters per second.
*
- * <p>We define speed accuracy as a 1-standard-deviation value, i.e. as 1-side of the
- * 2-sided range above and below the estimated
- * speed reported by {@link #getSpeed()}, within which there is a 68% probability of
- * finding the true speed.
+ * <p>We define speed accuracy at 68% confidence. Specifically, as 1-side of the
+ * 2-sided range above and below the estimated speed reported by {@link #getSpeed()},
+ * within which there is a 68% probability of finding the true speed.
+ *
+ * <p>In the case where the underlying
+ * distribution is assumed Gaussian normal, this would be considered 1 standard deviation.
*
- * <p>For example, if {@link #getSpeed()} returns 5.0, and
- * {@link #getSpeedAccuracyMetersPerSecond()} returns 1.0, then there is a 68% probably of the
- * true speed being between 4.0 and 6.0 meters per second.
+ * <p>For example, if {@link #getSpeed()} returns 5, and
+ * {@link #getSpeedAccuracyMetersPerSecond()} returns 1, then there is a 68% probability of
+ * the true speed being between 4 and 6 meters per second.
*
* <p>Note that the speed and speed accuracy is often better than would be obtained simply from
* differencing sequential positions, such as when the Doppler measurements from GNSS satellites
@@ -922,13 +925,16 @@ public class Location implements Parcelable {
/**
* Get the estimated bearing accuracy of this location, in degrees.
*
- * <p>We define bearing accuracy as a 1-standard-deviation value, i.e. as 1-side of the
+ * <p>We define bearing accuracy at 68% confidence. Specifically, as 1-side of the
* 2-sided range on each side of the estimated bearing reported by {@link #getBearing()},
* within which there is a 68% probability of finding the true bearing.
*
- * <p>For example, if {@link #getBearing()} returns 60., and
- * {@link #getBearingAccuracyDegrees()} ()} returns 10., then there is a 68% probably of the
- * true bearing being between 50. and 70. degrees.
+ * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be
+ * 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
+ * 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 26ead3d1..20405d3b 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -202,6 +202,22 @@ public final class AudioAttributes implements Parcelable {
* @see #SUPPRESSIBLE_USAGES
*/
public final static int SUPPRESSIBLE_NEVER = 3;
+ /**
+ * @hide
+ * Denotes a usage for alarms,
+ * will be muted when the Zen mode doesn't allow alarms
+ * @see #SUPPRESSIBLE_USAGES
+ */
+ public final static int SUPPRESSIBLE_ALARM = 4;
+ /**
+ * @hide
+ * Denotes a usage for all other sounds not caught in SUPPRESSIBLE_NOTIFICATION,
+ * SUPPRESSIBLE_CALL,SUPPRESSIBLE_NEVER or SUPPRESSIBLE_ALARM.
+ * This includes media, system, game, navigation, the assistant, and more.
+ * These will be muted when the Zen mode doesn't allow media/system/other.
+ * @see #SUPPRESSIBLE_USAGES
+ */
+ public final static int SUPPRESSIBLE_MEDIA_SYSTEM_OTHER = 5;
/**
* @hide
@@ -221,6 +237,13 @@ public final class AudioAttributes implements Parcelable {
SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT, SUPPRESSIBLE_NOTIFICATION);
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_ACCESSIBILITY, SUPPRESSIBLE_NEVER);
SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION, SUPPRESSIBLE_NEVER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ALARM, SUPPRESSIBLE_ALARM);
+ SUPPRESSIBLE_USAGES.put(USAGE_MEDIA, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_GAME, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
}
/**
diff --git a/android/media/MediaMetadataRetriever.java b/android/media/MediaMetadataRetriever.java
index 4ea4e381..760cc49b 100644
--- a/android/media/MediaMetadataRetriever.java
+++ b/android/media/MediaMetadataRetriever.java
@@ -395,7 +395,7 @@ public class MediaMetadataRetriever
* @see #getFrameAtTime(long, int)
*/
/* Do not change these option values without updating their counterparts
- * in include/media/stagefright/MediaSource.h!
+ * in include/media/MediaSource.h!
*/
/**
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve
diff --git a/android/media/MediaPlayer.java b/android/media/MediaPlayer.java
index 7787d4b5..62757e2e 100644
--- a/android/media/MediaPlayer.java
+++ b/android/media/MediaPlayer.java
@@ -39,6 +39,7 @@ import android.os.PowerManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.system.ErrnoException;
+import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.util.Pair;
@@ -60,7 +61,6 @@ import android.media.SyncParams;
import com.android.internal.util.Preconditions;
import libcore.io.IoBridge;
-import libcore.io.Libcore;
import libcore.io.Streams;
import java.io.ByteArrayOutputStream;
@@ -2843,7 +2843,7 @@ public class MediaPlayer extends PlayerBase
final FileDescriptor dupedFd;
try {
- dupedFd = Libcore.os.dup(fd);
+ dupedFd = Os.dup(fd);
} catch (ErrnoException ex) {
Log.e(TAG, ex.getMessage(), ex);
throw new RuntimeException(ex);
@@ -2881,7 +2881,7 @@ public class MediaPlayer extends PlayerBase
private int addTrack() {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
- Libcore.os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
+ Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
byte[] buffer = new byte[4096];
for (long total = 0; total < length2;) {
int bytesToRead = (int) Math.min(buffer.length, length2 - total);
@@ -2905,7 +2905,7 @@ public class MediaPlayer extends PlayerBase
return MEDIA_INFO_TIMED_TEXT_ERROR;
} finally {
try {
- Libcore.os.close(dupedFd);
+ Os.close(dupedFd);
} catch (ErrnoException e) {
Log.e(TAG, e.getMessage(), e);
}
diff --git a/android/media/MediaRecorder.java b/android/media/MediaRecorder.java
index 59a124fa..76784904 100644
--- a/android/media/MediaRecorder.java
+++ b/android/media/MediaRecorder.java
@@ -917,7 +917,7 @@ public class MediaRecorder
*/
public void setNextOutputFile(File file) throws IOException
{
- RandomAccessFile f = new RandomAccessFile(file, "rws");
+ RandomAccessFile f = new RandomAccessFile(file, "rw");
try {
_setNextOutputFile(f.getFD());
} finally {
@@ -942,7 +942,7 @@ public class MediaRecorder
public void prepare() throws IllegalStateException, IOException
{
if (mPath != null) {
- RandomAccessFile file = new RandomAccessFile(mPath, "rws");
+ RandomAccessFile file = new RandomAccessFile(mPath, "rw");
try {
_setOutputFile(file.getFD());
} finally {
@@ -951,7 +951,7 @@ public class MediaRecorder
} else if (mFd != null) {
_setOutputFile(mFd);
} else if (mFile != null) {
- RandomAccessFile file = new RandomAccessFile(mFile, "rws");
+ RandomAccessFile file = new RandomAccessFile(mFile, "rw");
try {
_setOutputFile(file.getFD());
} finally {
diff --git a/android/media/projection/MediaProjectionManager.java b/android/media/projection/MediaProjectionManager.java
index 9f2c08e5..aa0d0cc0 100644
--- a/android/media/projection/MediaProjectionManager.java
+++ b/android/media/projection/MediaProjectionManager.java
@@ -20,8 +20,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.media.projection.IMediaProjection;
import android.os.Handler;
import android.os.IBinder;
@@ -71,8 +73,11 @@ public final class MediaProjectionManager {
*/
public Intent createScreenCaptureIntent() {
Intent i = new Intent();
- i.setClassName("com.android.systemui",
- "com.android.systemui.media.MediaProjectionPermissionActivity");
+ final ComponentName mediaProjectionPermissionDialogComponent =
+ ComponentName.unflattenFromString(mContext.getResources().getString(
+ com.android.internal.R.string
+ .config_mediaProjectionPermissionDialogComponent));
+ i.setComponent(mediaProjectionPermissionDialogComponent);
return i;
}
diff --git a/android/media/tv/TvInputManager.java b/android/media/tv/TvInputManager.java
index d7a9edef..fd1f2cf6 100644
--- a/android/media/tv/TvInputManager.java
+++ b/android/media/tv/TvInputManager.java
@@ -2590,12 +2590,9 @@ public final class TvInputManager {
}
}
+ /** @removed */
public boolean dispatchKeyEventToHdmi(KeyEvent event) {
- try {
- return mInterface.dispatchKeyEventToHdmi(event);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
+ return false;
}
public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
diff --git a/android/mtp/MtpDatabase.java b/android/mtp/MtpDatabase.java
index 17b23264..aaf18e7f 100644
--- a/android/mtp/MtpDatabase.java
+++ b/android/mtp/MtpDatabase.java
@@ -471,10 +471,14 @@ public class MtpDatabase implements AutoCloseable {
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
+ where = STORAGE_PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(storageID),
+ Integer.toString(parent)};
+ } else {
+ // If a parent is specified, the storage is redundant
+ where = PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(parent)};
}
- where = STORAGE_PARENT_WHERE;
- whereArgs = new String[] { Integer.toString(storageID),
- Integer.toString(parent) };
}
} else {
// query specific format
@@ -487,11 +491,16 @@ public class MtpDatabase implements AutoCloseable {
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
+ where = STORAGE_FORMAT_PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(storageID),
+ Integer.toString(format),
+ Integer.toString(parent)};
+ } else {
+ // If a parent is specified, the storage is redundant
+ where = FORMAT_PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(format),
+ Integer.toString(parent)};
}
- where = STORAGE_FORMAT_PARENT_WHERE;
- whereArgs = new String[] { Integer.toString(storageID),
- Integer.toString(format),
- Integer.toString(parent) };
}
}
}
@@ -845,7 +854,7 @@ public class MtpDatabase implements AutoCloseable {
return MtpConstants.RESPONSE_OK;
}
- private int moveObject(int handle, int newParent, String newPath) {
+ private int moveObject(int handle, int newParent, int newStorage, String newPath) {
String[] whereArgs = new String[] { Integer.toString(handle) };
// do not allow renaming any of the special subdirectories
@@ -857,6 +866,7 @@ public class MtpDatabase implements AutoCloseable {
ContentValues values = new ContentValues();
values.put(Files.FileColumns.DATA, newPath);
values.put(Files.FileColumns.PARENT, newParent);
+ values.put(Files.FileColumns.STORAGE_ID, newStorage);
int updated = 0;
try {
// note - we are relying on a special case in MediaProvider.update() to update
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 79310e29..16b14523 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -31,7 +31,6 @@ import java.util.Arrays;
* RFC 4301.
*/
public final class IpSecAlgorithm implements Parcelable {
-
/**
* AES-CBC Encryption/Ciphering Algorithm.
*
@@ -68,6 +67,7 @@ public final class IpSecAlgorithm implements Parcelable {
* <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
*/
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
/**
* SHA512 HMAC Authentication/Integrity Algorithm
*
@@ -75,8 +75,24 @@ public final class IpSecAlgorithm implements Parcelable {
*/
public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
+ /**
+ * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for this key are {128, 192, 256}.
+ *
+ * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+ */
+ public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
/** @hide */
- @StringDef({CRYPT_AES_CBC, AUTH_HMAC_MD5, AUTH_HMAC_SHA1, AUTH_HMAC_SHA256, AUTH_HMAC_SHA512})
+ @StringDef({
+ CRYPT_AES_CBC,
+ AUTH_HMAC_MD5,
+ AUTH_HMAC_SHA1,
+ AUTH_HMAC_SHA256,
+ AUTH_HMAC_SHA512,
+ AUTH_CRYPT_AES_GCM
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface AlgorithmName {}
@@ -102,7 +118,7 @@ public final class IpSecAlgorithm implements Parcelable {
* @param algoName precise name of the algorithm to be used.
* @param key non-null Key padded to a multiple of 8 bits.
* @param truncLenBits the number of bits of output hash to use; only meaningful for
- * Authentication.
+ * Authentication or Authenticated Encryption (equivalent to ICV length).
*/
public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
if (!isTruncationLengthValid(algoName, truncLenBits)) {
@@ -175,6 +191,8 @@ public final class IpSecAlgorithm implements Parcelable {
return (truncLenBits >= 192 && truncLenBits <= 384);
case AUTH_HMAC_SHA512:
return (truncLenBits >= 256 && truncLenBits <= 512);
+ case AUTH_CRYPT_AES_GCM:
+ return (truncLenBits == 64 || truncLenBits == 96 || truncLenBits == 128);
default:
return false;
}
diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java
index 632b7fc0..61b13a92 100644
--- a/android/net/IpSecConfig.java
+++ b/android/net/IpSecConfig.java
@@ -50,6 +50,9 @@ public final class IpSecConfig implements Parcelable {
// Authentication Algorithm
private IpSecAlgorithm mAuthentication;
+ // Authenticated Encryption Algorithm
+ private IpSecAlgorithm mAuthenticatedEncryption;
+
@Override
public String toString() {
return new StringBuilder()
@@ -59,6 +62,8 @@ public final class IpSecConfig implements Parcelable {
.append(mEncryption)
.append(", mAuthentication=")
.append(mAuthentication)
+ .append(", mAuthenticatedEncryption=")
+ .append(mAuthenticatedEncryption)
.append("}")
.toString();
}
@@ -118,6 +123,11 @@ public final class IpSecConfig implements Parcelable {
mFlow[direction].mAuthentication = authentication;
}
+ /** Set the authenticated encryption algorithm for a given direction */
+ public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) {
+ mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption;
+ }
+
public void setNetwork(Network network) {
mNetwork = network;
}
@@ -163,6 +173,10 @@ public final class IpSecConfig implements Parcelable {
return mFlow[direction].mAuthentication;
}
+ public IpSecAlgorithm getAuthenticatedEncryption(int direction) {
+ return mFlow[direction].mAuthenticatedEncryption;
+ }
+
public Network getNetwork() {
return mNetwork;
}
@@ -199,9 +213,11 @@ public final class IpSecConfig implements Parcelable {
out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags);
out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags);
out.writeInt(mEncapType);
out.writeInt(mEncapSocketResourceId);
out.writeInt(mEncapRemotePort);
@@ -221,11 +237,15 @@ public final class IpSecConfig implements Parcelable {
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mEncapType = in.readInt();
mEncapSocketResourceId = in.readInt();
mEncapRemotePort = in.readInt();
diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java
index e15a2c67..48b5bd5c 100644
--- a/android/net/IpSecTransform.java
+++ b/android/net/IpSecTransform.java
@@ -281,6 +281,8 @@ public final class IpSecTransform implements AutoCloseable {
* <p>If encryption is set for a given direction without also providing an SPI for that
* direction, creation of an IpSecTransform will fail upon calling a build() method.
*
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
* @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
*/
@@ -296,6 +298,8 @@ public final class IpSecTransform implements AutoCloseable {
* <p>If authentication is set for a given direction without also providing an SPI for that
* direction, creation of an IpSecTransform will fail upon calling a build() method.
*
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
* @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
*/
@@ -306,6 +310,29 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
+ * Add an authenticated encryption algorithm to the transform for the given direction.
+ *
+ * <p>If an authenticated encryption algorithm is set for a given direction without also
+ * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
+ * a build() method.
+ *
+ * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
+ * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
+ * referred to in RFC 4301)
+ *
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
+ * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
+ * be applied.
+ */
+ public IpSecTransform.Builder setAuthenticatedEncryption(
+ @TransformDirection int direction, IpSecAlgorithm algo) {
+ mConfig.setAuthenticatedEncryption(direction, algo);
+ return this;
+ }
+
+ /**
* Set the SPI, which uniquely identifies a particular IPsec session from others. Because
* IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
* given destination address.
diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java
index 2c9fb23e..4e474c8e 100644
--- a/android/net/LinkProperties.java
+++ b/android/net/LinkProperties.java
@@ -683,9 +683,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4Address() {
for (LinkAddress address : mLinkAddresses) {
- if (address.getAddress() instanceof Inet4Address) {
- return true;
- }
+ if (address.getAddress() instanceof Inet4Address) {
+ return true;
+ }
}
return false;
}
@@ -725,9 +725,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4DefaultRoute() {
for (RouteInfo r : mRoutes) {
- if (r.isIPv4Default()) {
- return true;
- }
+ if (r.isIPv4Default()) {
+ return true;
+ }
}
return false;
}
@@ -740,9 +740,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv6DefaultRoute() {
for (RouteInfo r : mRoutes) {
- if (r.isIPv6Default()) {
- return true;
- }
+ if (r.isIPv6Default()) {
+ return true;
+ }
}
return false;
}
@@ -755,9 +755,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4DnsServer() {
for (InetAddress ia : mDnses) {
- if (ia instanceof Inet4Address) {
- return true;
- }
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
}
return false;
}
@@ -770,9 +770,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv6DnsServer() {
for (InetAddress ia : mDnses) {
- if (ia instanceof Inet6Address) {
- return true;
- }
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
}
return false;
}
diff --git a/android/net/apf/ApfFilter.java b/android/net/apf/ApfFilter.java
index 190b3a61..5c2b66f6 100644
--- a/android/net/apf/ApfFilter.java
+++ b/android/net/apf/ApfFilter.java
@@ -33,7 +33,7 @@ import android.net.NetworkUtils;
import android.net.apf.ApfGenerator;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.IpConnectivityLog;
@@ -238,7 +238,7 @@ public class ApfFilter {
private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
private final ApfCapabilities mApfCapabilities;
- private final IpManager.Callback mIpManagerCallback;
+ private final IpClient.Callback mIpClientCallback;
private final NetworkInterface mNetworkInterface;
private final IpConnectivityLog mMetricsLog;
@@ -262,10 +262,10 @@ public class ApfFilter {
@VisibleForTesting
ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
- IpManager.Callback ipManagerCallback, boolean multicastFilter,
+ IpClient.Callback ipClientCallback, boolean multicastFilter,
boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
mApfCapabilities = apfCapabilities;
- mIpManagerCallback = ipManagerCallback;
+ mIpClientCallback = ipClientCallback;
mNetworkInterface = networkInterface;
mMulticastFilter = multicastFilter;
mDrop802_3Frames = ieee802_3Filter;
@@ -275,7 +275,7 @@ public class ApfFilter {
mMetricsLog = log;
- // TODO: ApfFilter should not generate programs until IpManager sends provisioning success.
+ // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
maybeStartFilter();
}
@@ -1051,7 +1051,7 @@ public class ApfFilter {
if (VDBG) {
hexDump("Installing filter: ", program, program.length);
}
- mIpManagerCallback.installPacketFilter(program);
+ mIpClientCallback.installPacketFilter(program);
logApfProgramEventLocked(now);
mLastInstallEvent = new ApfProgramEvent();
mLastInstallEvent.lifetime = programMinLifetime;
@@ -1161,7 +1161,7 @@ public class ApfFilter {
* filtering using APF programs.
*/
public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
- NetworkInterface networkInterface, IpManager.Callback ipManagerCallback,
+ NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
if (apfCapabilities == null || networkInterface == null) return null;
if (apfCapabilities.apfVersionSupported == 0) return null;
@@ -1178,7 +1178,7 @@ public class ApfFilter {
Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
return null;
}
- return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback,
+ return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
}
diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java
index 0230f36b..1925c39e 100644
--- a/android/net/ip/ConnectivityPacketTracker.java
+++ b/android/net/ip/ConnectivityPacketTracker.java
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.system.ErrnoException;
import android.system.Os;
import android.system.PacketSocketAddress;
+import android.text.TextUtils;
import android.util.Log;
import android.util.LocalLog;
@@ -59,11 +60,14 @@ public class ConnectivityPacketTracker {
private static final boolean DBG = false;
private static final String MARK_START = "--- START ---";
private static final String MARK_STOP = "--- STOP ---";
+ private static final String MARK_NAMED_START = "--- START (%s) ---";
+ private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
private final String mTag;
private final LocalLog mLog;
private final BlockingSocketReader mPacketListener;
private boolean mRunning;
+ private String mDisplayName;
public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) {
final String ifname;
@@ -85,14 +89,16 @@ public class ConnectivityPacketTracker {
mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
}
- public void start() {
+ public void start(String displayName) {
mRunning = true;
+ mDisplayName = displayName;
mPacketListener.start();
}
public void stop() {
mPacketListener.stop();
mRunning = false;
+ mDisplayName = null;
}
private final class PacketListener extends BlockingSocketReader {
@@ -133,16 +139,19 @@ public class ConnectivityPacketTracker {
@Override
protected void onStart() {
- mLog.log(MARK_START);
+ final String msg = TextUtils.isEmpty(mDisplayName)
+ ? MARK_START
+ : String.format(MARK_NAMED_START, mDisplayName);
+ mLog.log(msg);
}
@Override
protected void onStop() {
- if (mRunning) {
- mLog.log(MARK_STOP);
- } else {
- mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)");
- }
+ String msg = TextUtils.isEmpty(mDisplayName)
+ ? MARK_STOP
+ : String.format(MARK_NAMED_STOP, mDisplayName);
+ if (!mRunning) msg += " (packet listener stopped unexpectedly)";
+ mLog.log(msg);
}
@Override
diff --git a/android/net/ip/IpClient.java b/android/net/ip/IpClient.java
new file mode 100644
index 00000000..2359fab4
--- /dev/null
+++ b/android/net/ip/IpClient.java
@@ -0,0 +1,1712 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ip;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.WakeupMessage;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties.ProvisioningChange;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
+import android.net.util.NetworkConstants;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.IState;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+ private static final boolean DBG = false;
+
+ // For message logging.
+ private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+ private static final SparseArray<String> sWhatToString =
+ MessageUtils.findMessageNames(sMessageClasses);
+
+ /**
+ * Callbacks for handling IpClient events.
+ */
+ public static class Callback {
+ // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
+ // when constructing a ProvisioningConfiguration.
+ //
+ // Implementations of onPreDhcpAction() must call
+ // IpClient#completedPreDhcpAction() to indicate that DHCP is clear
+ // to proceed.
+ public void onPreDhcpAction() {}
+ public void onPostDhcpAction() {}
+
+ // This is purely advisory and not an indication of provisioning
+ // success or failure. This is only here for callers that want to
+ // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+ // DHCPv4 or static IPv4 configuration failure or success can be
+ // determined by whether or not the passed-in DhcpResults object is
+ // null or not.
+ public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+ public void onProvisioningSuccess(LinkProperties newLp) {}
+ public void onProvisioningFailure(LinkProperties newLp) {}
+
+ // Invoked on LinkProperties changes.
+ public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+ // Called when the internal IpReachabilityMonitor (if enabled) has
+ // detected the loss of a critical number of required neighbors.
+ public void onReachabilityLost(String logMsg) {}
+
+ // Called when the IpClient state machine terminates.
+ public void onQuit() {}
+
+ // Install an APF program to filter incoming packets.
+ public void installPacketFilter(byte[] filter) {}
+
+ // If multicast filtering cannot be accomplished with APF, this function will be called to
+ // actuate multicast filtering using another means.
+ public void setFallbackMulticastFilter(boolean enabled) {}
+
+ // Enabled/disable Neighbor Discover offload functionality. This is
+ // called, for example, whenever 464xlat is being started or stopped.
+ public void setNeighborDiscoveryOffload(boolean enable) {}
+ }
+
+ // Use a wrapper class to log in order to ensure complete and detailed
+ // logging. This method is lighter weight than annotations/reflection
+ // and has the following benefits:
+ //
+ // - No invoked method can be forgotten.
+ // Any new method added to IpClient.Callback must be overridden
+ // here or it will never be called.
+ //
+ // - No invoking call site can be forgotten.
+ // Centralized logging in this way means call sites don't need to
+ // remember to log, and therefore no call site can be forgotten.
+ //
+ // - No variation in log format among call sites.
+ // Encourages logging of any available arguments, and all call sites
+ // are necessarily logged identically.
+ //
+ // TODO: Find an lighter weight approach.
+ private class LoggingCallbackWrapper extends Callback {
+ private static final String PREFIX = "INVOKE ";
+ private Callback mCallback;
+
+ public LoggingCallbackWrapper(Callback callback) {
+ mCallback = callback;
+ }
+
+ private void log(String msg) {
+ mLog.log(PREFIX + msg);
+ }
+
+ @Override
+ public void onPreDhcpAction() {
+ mCallback.onPreDhcpAction();
+ log("onPreDhcpAction()");
+ }
+ @Override
+ public void onPostDhcpAction() {
+ mCallback.onPostDhcpAction();
+ log("onPostDhcpAction()");
+ }
+ @Override
+ public void onNewDhcpResults(DhcpResults dhcpResults) {
+ mCallback.onNewDhcpResults(dhcpResults);
+ log("onNewDhcpResults({" + dhcpResults + "})");
+ }
+ @Override
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ mCallback.onProvisioningSuccess(newLp);
+ log("onProvisioningSuccess({" + newLp + "})");
+ }
+ @Override
+ public void onProvisioningFailure(LinkProperties newLp) {
+ mCallback.onProvisioningFailure(newLp);
+ log("onProvisioningFailure({" + newLp + "})");
+ }
+ @Override
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ mCallback.onLinkPropertiesChange(newLp);
+ log("onLinkPropertiesChange({" + newLp + "})");
+ }
+ @Override
+ public void onReachabilityLost(String logMsg) {
+ mCallback.onReachabilityLost(logMsg);
+ log("onReachabilityLost(" + logMsg + ")");
+ }
+ @Override
+ public void onQuit() {
+ mCallback.onQuit();
+ log("onQuit()");
+ }
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mCallback.installPacketFilter(filter);
+ log("installPacketFilter(byte[" + filter.length + "])");
+ }
+ @Override
+ public void setFallbackMulticastFilter(boolean enabled) {
+ mCallback.setFallbackMulticastFilter(enabled);
+ log("setFallbackMulticastFilter(" + enabled + ")");
+ }
+ @Override
+ public void setNeighborDiscoveryOffload(boolean enable) {
+ mCallback.setNeighborDiscoveryOffload(enable);
+ log("setNeighborDiscoveryOffload(" + enable + ")");
+ }
+ }
+
+ /**
+ * This class encapsulates parameters to be passed to
+ * IpClient#startProvisioning(). A defensive copy is made by IpClient
+ * and the values specified herein are in force until IpClient#stop()
+ * is called.
+ *
+ * Example use:
+ *
+ * final ProvisioningConfiguration config =
+ * mIpClient.buildProvisioningConfiguration()
+ * .withPreDhcpAction()
+ * .withProvisioningTimeoutMs(36 * 1000)
+ * .build();
+ * mIpClient.startProvisioning(config);
+ * ...
+ * mIpClient.stop();
+ *
+ * The specified provisioning configuration will only be active until
+ * IpClient#stop() is called. Future calls to IpClient#startProvisioning()
+ * must specify the configuration again.
+ */
+ public static class ProvisioningConfiguration {
+ // TODO: Delete this default timeout once those callers that care are
+ // fixed to pass in their preferred timeout.
+ //
+ // We pick 36 seconds so we can send DHCP requests at
+ //
+ // t=0, t=2, t=6, t=14, t=30
+ //
+ // allowing for 10% jitter.
+ private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+ public static class Builder {
+ private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+ public Builder withoutIPv4() {
+ mConfig.mEnableIPv4 = false;
+ return this;
+ }
+
+ public Builder withoutIPv6() {
+ mConfig.mEnableIPv6 = false;
+ return this;
+ }
+
+ public Builder withoutIpReachabilityMonitor() {
+ mConfig.mUsingIpReachabilityMonitor = false;
+ return this;
+ }
+
+ public Builder withPreDhcpAction() {
+ mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+ return this;
+ }
+
+ public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+ mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+ return this;
+ }
+
+ public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+ mConfig.mInitialConfig = initialConfig;
+ return this;
+ }
+
+ public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+ mConfig.mStaticIpConfig = staticConfig;
+ return this;
+ }
+
+ public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+ mConfig.mApfCapabilities = apfCapabilities;
+ return this;
+ }
+
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ mConfig.mProvisioningTimeoutMs = timeoutMs;
+ return this;
+ }
+
+ public Builder withIPv6AddrGenModeEUI64() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+ return this;
+ }
+
+ public Builder withIPv6AddrGenModeStablePrivacy() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ return this;
+ }
+
+ public Builder withNetwork(Network network) {
+ mConfig.mNetwork = network;
+ return this;
+ }
+
+ public Builder withDisplayName(String displayName) {
+ mConfig.mDisplayName = displayName;
+ return this;
+ }
+
+ public ProvisioningConfiguration build() {
+ return new ProvisioningConfiguration(mConfig);
+ }
+ }
+
+ /* package */ boolean mEnableIPv4 = true;
+ /* package */ boolean mEnableIPv6 = true;
+ /* package */ boolean mUsingIpReachabilityMonitor = true;
+ /* package */ int mRequestedPreDhcpActionMs;
+ /* package */ InitialConfiguration mInitialConfig;
+ /* package */ StaticIpConfiguration mStaticIpConfig;
+ /* package */ ApfCapabilities mApfCapabilities;
+ /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+ /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ /* package */ Network mNetwork = null;
+ /* package */ String mDisplayName = null;
+
+ public ProvisioningConfiguration() {} // used by Builder
+
+ public ProvisioningConfiguration(ProvisioningConfiguration other) {
+ mEnableIPv4 = other.mEnableIPv4;
+ mEnableIPv6 = other.mEnableIPv6;
+ mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+ mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+ mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+ mStaticIpConfig = other.mStaticIpConfig;
+ mApfCapabilities = other.mApfCapabilities;
+ mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+ mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+ mNetwork = other.mNetwork;
+ mDisplayName = other.mDisplayName;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mEnableIPv4: " + mEnableIPv4)
+ .add("mEnableIPv6: " + mEnableIPv6)
+ .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+ .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+ .add("mInitialConfig: " + mInitialConfig)
+ .add("mStaticIpConfig: " + mStaticIpConfig)
+ .add("mApfCapabilities: " + mApfCapabilities)
+ .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+ .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+ .add("mNetwork: " + mNetwork)
+ .add("mDisplayName: " + mDisplayName)
+ .toString();
+ }
+
+ public boolean isValid() {
+ return (mInitialConfig == null) || mInitialConfig.isValid();
+ }
+ }
+
+ public static class InitialConfiguration {
+ public final Set<LinkAddress> ipAddresses = new HashSet<>();
+ public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+ public final Set<InetAddress> dnsServers = new HashSet<>();
+ public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
+
+ public static InitialConfiguration copy(InitialConfiguration config) {
+ if (config == null) {
+ return null;
+ }
+ InitialConfiguration configCopy = new InitialConfiguration();
+ configCopy.ipAddresses.addAll(config.ipAddresses);
+ configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+ configCopy.dnsServers.addAll(config.dnsServers);
+ return configCopy;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
+ join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+ join(", ", dnsServers), gateway);
+ }
+
+ public boolean isValid() {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ // For every IP address, there must be at least one prefix containing that address.
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+ return false;
+ }
+ }
+ // For every dns server, there must be at least one prefix containing that address.
+ for (InetAddress addr : dnsServers) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+ return false;
+ }
+ }
+ // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+ // (read: compliant with RFC4291#section2.5.4).
+ if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // If directlyConnectedRoutes contains an IPv6 default route
+ // then ipAddresses MUST contain at least one non-ULA GUA.
+ if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+ && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+ return false;
+ }
+ // The prefix length of routes in directlyConnectedRoutes be within reasonable
+ // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+ if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // There no more than one IPv4 address
+ if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return true if the given list of addressess and routes satisfies provisioning for this
+ * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+ * because addresses and routes seen by Netlink will contain additional fields like flags,
+ * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+ * provisioning check always fails.
+ *
+ * If the given list of routes is null, only addresses are taken into considerations.
+ */
+ public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+ return false;
+ }
+ }
+
+ if (routes != null) {
+ for (IpPrefix prefix : directlyConnectedRoutes) {
+ if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+ return !route.hasGateway() && prefix.equals(route.getDestination());
+ }
+
+ private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+ return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+ }
+
+ private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+ return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+ }
+
+ private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+ return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+ && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
+ }
+
+ private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+ return prefix.getAddress().equals(Inet6Address.ANY);
+ }
+
+ private static boolean isIPv6GUA(LinkAddress addr) {
+ return addr.isIPv6() && addr.isGlobalPreferred();
+ }
+ }
+
+ public static final String DUMP_ARG = "ipclient";
+ public static final String DUMP_ARG_CONFIRM = "confirm";
+
+ private static final int CMD_TERMINATE_AFTER_STOP = 1;
+ private static final int CMD_STOP = 2;
+ private static final int CMD_START = 3;
+ private static final int CMD_CONFIRM = 4;
+ private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
+ // Sent by NetlinkTracker to communicate netlink events.
+ private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+ private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
+ private static final int CMD_UPDATE_HTTP_PROXY = 8;
+ private static final int CMD_SET_MULTICAST_FILTER = 9;
+ private static final int EVENT_PROVISIONING_TIMEOUT = 10;
+ private static final int EVENT_DHCPACTION_TIMEOUT = 11;
+
+ private static final int MAX_LOG_RECORDS = 500;
+ private static final int MAX_PACKET_RECORDS = 100;
+
+ private static final boolean NO_CALLBACKS = false;
+ private static final boolean SEND_CALLBACKS = true;
+
+ // This must match the interface prefix in clatd.c.
+ // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+ private static final String CLAT_PREFIX = "v4-";
+
+ private final State mStoppedState = new StoppedState();
+ private final State mStoppingState = new StoppingState();
+ private final State mStartedState = new StartedState();
+ private final State mRunningState = new RunningState();
+
+ private final String mTag;
+ private final Context mContext;
+ private final String mInterfaceName;
+ private final String mClatInterfaceName;
+ @VisibleForTesting
+ protected final Callback mCallback;
+ private final INetworkManagementService mNwService;
+ private final NetlinkTracker mNetlinkTracker;
+ private final WakeupMessage mProvisioningTimeoutAlarm;
+ private final WakeupMessage mDhcpActionTimeoutAlarm;
+ private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+ private final SharedLog mLog;
+ private final LocalLog mConnectivityPacketLog;
+ private final MessageHandlingLogger mMsgStateLogger;
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+ private final InterfaceController mInterfaceCtrl;
+
+ private NetworkInterface mNetworkInterface;
+
+ /**
+ * Non-final member variables accessed only from within our StateMachine.
+ */
+ private LinkProperties mLinkProperties;
+ private ProvisioningConfiguration mConfiguration;
+ private IpReachabilityMonitor mIpReachabilityMonitor;
+ private DhcpClient mDhcpClient;
+ private DhcpResults mDhcpResults;
+ private String mTcpBufferSizes;
+ private ProxyInfo mHttpProxy;
+ private ApfFilter mApfFilter;
+ private boolean mMulticastFiltering;
+ private long mStartTimeMillis;
+
+ public IpClient(Context context, String ifName, Callback callback) {
+ this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
+ NetdService.getInstance());
+ }
+
+ /**
+ * An expanded constructor, useful for dependency injection.
+ * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+ */
+ public IpClient(Context context, String ifName, Callback callback,
+ INetworkManagementService nwService) {
+ this(context, ifName, callback, nwService, NetdService.getInstance());
+ }
+
+ @VisibleForTesting
+ IpClient(Context context, String ifName, Callback callback,
+ INetworkManagementService nwService, INetd netd) {
+ super(IpClient.class.getSimpleName() + "." + ifName);
+ mTag = getName();
+
+ mContext = context;
+ mInterfaceName = ifName;
+ mClatInterfaceName = CLAT_PREFIX + ifName;
+ mCallback = new LoggingCallbackWrapper(callback);
+ mNwService = nwService;
+
+ mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
+ mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
+ mMsgStateLogger = new MessageHandlingLogger();
+
+ mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
+
+ mNetlinkTracker = new NetlinkTracker(
+ mInterfaceName,
+ new NetlinkTracker.Callback() {
+ @Override
+ public void update() {
+ sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ }
+ }) {
+ @Override
+ public void interfaceAdded(String iface) {
+ super.interfaceAdded(iface);
+ if (mClatInterfaceName.equals(iface)) {
+ mCallback.setNeighborDiscoveryOffload(false);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceAdded(" + iface +")";
+ logMsg(msg);
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ super.interfaceRemoved(iface);
+ // TODO: Also observe mInterfaceName going down and take some
+ // kind of appropriate action.
+ if (mClatInterfaceName.equals(iface)) {
+ // TODO: consider sending a message to the IpClient main
+ // StateMachine thread, in case "NDO enabled" state becomes
+ // tied to more things that 464xlat operation.
+ mCallback.setNeighborDiscoveryOffload(true);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceRemoved(" + iface +")";
+ logMsg(msg);
+ }
+
+ private void logMsg(String msg) {
+ Log.d(mTag, msg);
+ getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
+ }
+ };
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+
+ mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
+ () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
+
+ mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+ mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+ // Anything the StateMachine may access must have been instantiated
+ // before this point.
+ configureAndStartStateMachine();
+
+ // Anything that may send messages to the StateMachine must only be
+ // configured to do so after the StateMachine has started (above).
+ startStateMachineUpdaters();
+ }
+
+ private void configureAndStartStateMachine() {
+ addState(mStoppedState);
+ addState(mStartedState);
+ addState(mRunningState, mStartedState);
+ addState(mStoppingState);
+
+ setInitialState(mStoppedState);
+
+ super.start();
+ }
+
+ private void startStateMachineUpdaters() {
+ try {
+ mNwService.registerObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't register NetlinkTracker: %s", e);
+ }
+
+ mMultinetworkPolicyTracker.start();
+ }
+
+ private void stopStateMachineUpdaters() {
+ try {
+ mNwService.unregisterObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't unregister NetlinkTracker: %s", e);
+ }
+
+ mMultinetworkPolicyTracker.shutdown();
+ }
+
+ @Override
+ protected void onQuitting() {
+ mCallback.onQuit();
+ }
+
+ // Shut down this IpClient instance altogether.
+ public void shutdown() {
+ stop();
+ sendMessage(CMD_TERMINATE_AFTER_STOP);
+ }
+
+ public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+ return new ProvisioningConfiguration.Builder();
+ }
+
+ public void startProvisioning(ProvisioningConfiguration req) {
+ if (!req.isValid()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ return;
+ }
+
+ getNetworkInterface();
+
+ mCallback.setNeighborDiscoveryOffload(true);
+ sendMessage(CMD_START, new ProvisioningConfiguration(req));
+ }
+
+ // TODO: Delete this.
+ public void startProvisioning(StaticIpConfiguration staticIpConfig) {
+ startProvisioning(buildProvisioningConfiguration()
+ .withStaticConfiguration(staticIpConfig)
+ .build());
+ }
+
+ public void startProvisioning() {
+ startProvisioning(new ProvisioningConfiguration());
+ }
+
+ public void stop() {
+ sendMessage(CMD_STOP);
+ }
+
+ public void confirmConfiguration() {
+ sendMessage(CMD_CONFIRM);
+ }
+
+ public void completedPreDhcpAction() {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+
+ /**
+ * Set the TCP buffer sizes to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+ }
+
+ /**
+ * Set the HTTP Proxy configuration to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setHttpProxy(ProxyInfo proxyInfo) {
+ sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+ }
+
+ /**
+ * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
+ * if not, Callback.setFallbackMulticastFilter() is called.
+ */
+ public void setMulticastFilter(boolean enabled) {
+ sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+ // Execute confirmConfiguration() and take no further action.
+ confirmConfiguration();
+ return;
+ }
+
+ // Thread-unsafe access to mApfFilter but just used for debugging.
+ final ApfFilter apfFilter = mApfFilter;
+ final ProvisioningConfiguration provisioningConfig = mConfiguration;
+ final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+ ? provisioningConfig.mApfCapabilities : null;
+
+ IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println(mTag + " APF dump:");
+ pw.increaseIndent();
+ if (apfFilter != null) {
+ apfFilter.dump(pw);
+ } else {
+ pw.print("No active ApfFilter; ");
+ if (provisioningConfig == null) {
+ pw.println("IpClient not yet started.");
+ } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+ pw.println("Hardware does not support APF.");
+ } else {
+ pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+ }
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " current ProvisioningConfiguration:");
+ pw.increaseIndent();
+ pw.println(Objects.toString(provisioningConfig, "N/A"));
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " StateMachine dump:");
+ pw.increaseIndent();
+ mLog.dump(fd, pw, args);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " connectivity packet log:");
+ pw.println();
+ pw.println("Debug with python and scapy via:");
+ pw.println("shell$ python");
+ pw.println(">>> from scapy import all as scapy");
+ pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+ pw.println();
+
+ pw.increaseIndent();
+ mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+ pw.decreaseIndent();
+ }
+
+
+ /**
+ * Internals.
+ */
+
+ @Override
+ protected String getWhatToString(int what) {
+ return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+ }
+
+ @Override
+ protected String getLogRecString(Message msg) {
+ final String logLine = String.format(
+ "%s/%d %d %d %s [%s]",
+ mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
+ msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+ final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+ mLog.log(richerLogLine);
+ if (DBG) {
+ Log.d(mTag, richerLogLine);
+ }
+
+ mMsgStateLogger.reset();
+ return logLine;
+ }
+
+ @Override
+ protected boolean recordLogRec(Message msg) {
+ // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+ // and we already log any LinkProperties change that results in an
+ // invocation of IpClient.Callback#onLinkPropertiesChange().
+ final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ if (!shouldLog) {
+ mMsgStateLogger.reset();
+ }
+ return shouldLog;
+ }
+
+ private void logError(String fmt, Object... args) {
+ final String msg = "ERROR " + String.format(fmt, args);
+ Log.e(mTag, msg);
+ mLog.log(msg);
+ }
+
+ private void getNetworkInterface() {
+ try {
+ mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
+ } catch (SocketException | NullPointerException e) {
+ // TODO: throw new IllegalStateException.
+ logError("Failed to get interface object: %s", e);
+ }
+ }
+
+ // This needs to be called with care to ensure that our LinkProperties
+ // are in sync with the actual LinkProperties of the interface. For example,
+ // we should only call this if we know for sure that there are no IP addresses
+ // assigned to the interface, etc.
+ private void resetLinkProperties() {
+ mNetlinkTracker.clearLinkProperties();
+ mConfiguration = null;
+ mDhcpResults = null;
+ mTcpBufferSizes = "";
+ mHttpProxy = null;
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+ }
+
+ private void recordMetric(final int type) {
+ if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
+ final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
+ mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+ }
+
+ // For now: use WifiStateMachine's historical notion of provisioned.
+ @VisibleForTesting
+ static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+ // For historical reasons, we should connect even if all we have is
+ // an IPv4 address and nothing else.
+ if (lp.hasIPv4Address() || lp.isProvisioned()) {
+ return true;
+ }
+ if (config == null) {
+ return false;
+ }
+
+ // When an InitialConfiguration is specified, ignore any difference with previous
+ // properties and instead check if properties observed match the desired properties.
+ return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+ }
+
+ // TODO: Investigate folding all this into the existing static function
+ // LinkProperties.compareProvisioning() or some other single function that
+ // takes two LinkProperties objects and returns a ProvisioningChange
+ // object that is a correct and complete assessment of what changed, taking
+ // account of the asymmetries described in the comments in this function.
+ // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+ private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+ ProvisioningChange delta;
+ InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+ final boolean wasProvisioned = isProvisioned(oldLp, config);
+ final boolean isProvisioned = isProvisioned(newLp, config);
+
+ if (!wasProvisioned && isProvisioned) {
+ delta = ProvisioningChange.GAINED_PROVISIONING;
+ } else if (wasProvisioned && isProvisioned) {
+ delta = ProvisioningChange.STILL_PROVISIONED;
+ } else if (!wasProvisioned && !isProvisioned) {
+ delta = ProvisioningChange.STILL_NOT_PROVISIONED;
+ } else {
+ // (wasProvisioned && !isProvisioned)
+ //
+ // Note that this is true even if we lose a configuration element
+ // (e.g., a default gateway) that would not be required to advance
+ // into provisioned state. This is intended: if we have a default
+ // router and we lose it, that's a sure sign of a problem, but if
+ // we connect to a network with no IPv4 DNS servers, we consider
+ // that to be a network without DNS servers and connect anyway.
+ //
+ // See the comment below.
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+ final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+ final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+ // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+ // provisioning. Otherwise, when a hotspot that loses Internet
+ // access sends out a 0-lifetime RA to its clients, the clients
+ // will disconnect and then reconnect, avoiding the bad hotspot,
+ // instead of getting stuck on the bad hotspot. http://b/31827713 .
+ //
+ // This is incorrect because if the hotspot then regains Internet
+ // access with a different prefix, TCP connections on the
+ // deprecated addresses will remain stuck.
+ //
+ // Note that we can still be disconnected by IpReachabilityMonitor
+ // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+ // accompanying code in IpReachabilityMonitor) is unreachable.
+ final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
+
+ // Additionally:
+ //
+ // Partial configurations (e.g., only an IPv4 address with no DNS
+ // servers and no default route) are accepted as long as DHCPv4
+ // succeeds. On such a network, isProvisioned() will always return
+ // false, because the configuration is not complete, but we want to
+ // connect anyway. It might be a disconnected network such as a
+ // Chromecast or a wireless printer, for example.
+ //
+ // Because on such a network isProvisioned() will always return false,
+ // delta will never be LOST_PROVISIONING. So check for loss of
+ // provisioning here too.
+ if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ // Additionally:
+ //
+ // If the previous link properties had a global IPv6 address and an
+ // IPv6 default route then also consider the loss of that default route
+ // to be a loss of provisioning. See b/27962810.
+ if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ return delta;
+ }
+
+ private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
+ switch (delta) {
+ case GAINED_PROVISIONING:
+ if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
+ recordMetric(IpManagerEvent.PROVISIONING_OK);
+ mCallback.onProvisioningSuccess(newLp);
+ break;
+
+ case LOST_PROVISIONING:
+ if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
+ recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+ mCallback.onProvisioningFailure(newLp);
+ break;
+
+ default:
+ if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
+ mCallback.onLinkPropertiesChange(newLp);
+ break;
+ }
+ }
+
+ // Updates all IpClient-related state concerned with LinkProperties.
+ // Returns a ProvisioningChange for possibly notifying other interested
+ // parties that are not fronted by IpClient.
+ private ProvisioningChange setLinkProperties(LinkProperties newLp) {
+ if (mApfFilter != null) {
+ mApfFilter.setLinkProperties(newLp);
+ }
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.updateLinkProperties(newLp);
+ }
+
+ ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
+ mLinkProperties = new LinkProperties(newLp);
+
+ if (delta == ProvisioningChange.GAINED_PROVISIONING) {
+ // TODO: Add a proper ProvisionedState and cancel the alarm in
+ // its enter() method.
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ return delta;
+ }
+
+ private LinkProperties assembleLinkProperties() {
+ // [1] Create a new LinkProperties object to populate.
+ LinkProperties newLp = new LinkProperties();
+ newLp.setInterfaceName(mInterfaceName);
+
+ // [2] Pull in data from netlink:
+ // - IPv4 addresses
+ // - IPv6 addresses
+ // - IPv6 routes
+ // - IPv6 DNS servers
+ //
+ // N.B.: this is fundamentally race-prone and should be fixed by
+ // changing NetlinkTracker from a hybrid edge/level model to an
+ // edge-only model, or by giving IpClient its own netlink socket(s)
+ // so as to track all required information directly.
+ LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+ newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+ for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+ // [3] Add in data from DHCPv4, if available.
+ //
+ // mDhcpResults is never shared with any other owner so we don't have
+ // to worry about concurrent modification.
+ if (mDhcpResults != null) {
+ for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+ newLp.setDomains(mDhcpResults.domains);
+
+ if (mDhcpResults.mtu != 0) {
+ newLp.setMtu(mDhcpResults.mtu);
+ }
+ }
+
+ // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+ if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+ newLp.setTcpBufferSizes(mTcpBufferSizes);
+ }
+ if (mHttpProxy != null) {
+ newLp.setHttpProxy(mHttpProxy);
+ }
+
+ // [5] Add data from InitialConfiguration
+ if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+ InitialConfiguration config = mConfiguration.mInitialConfig;
+ // Add InitialConfiguration routes and dns server addresses once all addresses
+ // specified in the InitialConfiguration have been observed with Netlink.
+ if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+ for (IpPrefix prefix : config.directlyConnectedRoutes) {
+ newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+ }
+ }
+ addAllReachableDnsServers(newLp, config.dnsServers);
+ }
+ final LinkProperties oldLp = mLinkProperties;
+ if (DBG) {
+ Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+ netlinkLinkProperties, newLp, oldLp));
+ }
+
+ // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+ // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+ return newLp;
+ }
+
+ private static void addAllReachableDnsServers(
+ LinkProperties lp, Iterable<InetAddress> dnses) {
+ // TODO: Investigate deleting this reachability check. We should be
+ // able to pass everything down to netd and let netd do evaluation
+ // and RFC6724-style sorting.
+ for (InetAddress dns : dnses) {
+ if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+ lp.addDnsServer(dns);
+ }
+ }
+ }
+
+ // Returns false if we have lost provisioning, true otherwise.
+ private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+ final LinkProperties newLp = assembleLinkProperties();
+ if (Objects.equals(newLp, mLinkProperties)) {
+ return true;
+ }
+ final ProvisioningChange delta = setLinkProperties(newLp);
+ if (sendCallbacks) {
+ dispatchCallback(delta, newLp);
+ }
+ return (delta != ProvisioningChange.LOST_PROVISIONING);
+ }
+
+ private void handleIPv4Success(DhcpResults dhcpResults) {
+ mDhcpResults = new DhcpResults(dhcpResults);
+ final LinkProperties newLp = assembleLinkProperties();
+ final ProvisioningChange delta = setLinkProperties(newLp);
+
+ if (DBG) {
+ Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+ }
+ mCallback.onNewDhcpResults(dhcpResults);
+ dispatchCallback(delta, newLp);
+ }
+
+ private void handleIPv4Failure() {
+ // TODO: Investigate deleting this clearIPv4Address() call.
+ //
+ // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+ // that could trigger a call to this function. If we missed handling
+ // that message in StartedState for some reason we would still clear
+ // any addresses upon entry to StoppedState.
+ mInterfaceCtrl.clearIPv4Address();
+ mDhcpResults = null;
+ if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
+ mCallback.onNewDhcpResults(null);
+
+ handleProvisioningFailure();
+ }
+
+ private void handleProvisioningFailure() {
+ final LinkProperties newLp = assembleLinkProperties();
+ ProvisioningChange delta = setLinkProperties(newLp);
+ // If we've gotten here and we're still not provisioned treat that as
+ // a total loss of provisioning.
+ //
+ // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+ // there was no usable IPv6 obtained before a non-zero provisioning
+ // timeout expired.
+ //
+ // Regardless: GAME OVER.
+ if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ dispatchCallback(delta, newLp);
+ if (delta == ProvisioningChange.LOST_PROVISIONING) {
+ transitionTo(mStoppingState);
+ }
+ }
+
+ private void doImmediateProvisioningFailure(int failureType) {
+ logError("onProvisioningFailure(): %s", failureType);
+ recordMetric(failureType);
+ mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ }
+
+ private boolean startIPv4() {
+ // If we have a StaticIpConfiguration attempt to apply it and
+ // handle the result accordingly.
+ if (mConfiguration.mStaticIpConfig != null) {
+ if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+ handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+ } else {
+ return false;
+ }
+ } else {
+ // Start DHCPv4.
+ mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceName);
+ mDhcpClient.registerForPreDhcpNotification();
+ mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+ }
+
+ return true;
+ }
+
+ private boolean startIPv6() {
+ return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
+ mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
+ mInterfaceCtrl.enableIPv6();
+ }
+
+ private boolean applyInitialConfig(InitialConfiguration config) {
+ // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+ for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+ if (!mInterfaceCtrl.addAddress(addr)) return false;
+ }
+
+ return true;
+ }
+
+ private boolean startIpReachabilityMonitor() {
+ try {
+ mIpReachabilityMonitor = new IpReachabilityMonitor(
+ mContext,
+ mInterfaceName,
+ mLog,
+ new IpReachabilityMonitor.Callback() {
+ @Override
+ public void notifyLost(InetAddress ip, String logMsg) {
+ mCallback.onReachabilityLost(logMsg);
+ }
+ },
+ mMultinetworkPolicyTracker);
+ } catch (IllegalArgumentException iae) {
+ // Failed to start IpReachabilityMonitor. Log it and call
+ // onProvisioningFailure() immediately.
+ //
+ // See http://b/31038971.
+ logError("IpReachabilityMonitor failure: %s", iae);
+ mIpReachabilityMonitor = null;
+ }
+
+ return (mIpReachabilityMonitor != null);
+ }
+
+ private void stopAllIP() {
+ // We don't need to worry about routes, just addresses, because:
+ // - disableIpv6() will clear autoconf IPv6 routes as well, and
+ // - we don't get IPv4 routes from netlink
+ // so we neither react to nor need to wait for changes in either.
+
+ mInterfaceCtrl.disableIPv6();
+ mInterfaceCtrl.clearAllAddresses();
+ }
+
+ class StoppedState extends State {
+ @Override
+ public void enter() {
+ stopAllIP();
+
+ resetLinkProperties();
+ if (mStartTimeMillis > 0) {
+ recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+ mStartTimeMillis = 0;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_TERMINATE_AFTER_STOP:
+ stopStateMachineUpdaters();
+ quit();
+ break;
+
+ case CMD_STOP:
+ break;
+
+ case CMD_START:
+ mConfiguration = (ProvisioningConfiguration) msg.obj;
+ transitionTo(mStartedState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER:
+ mMulticastFiltering = (boolean) msg.obj;
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // Everything is already stopped.
+ logError("Unexpected CMD_ON_QUIT (already stopped).");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StoppingState extends State {
+ @Override
+ public void enter() {
+ if (mDhcpClient == null) {
+ // There's no DHCPv4 for which to wait; proceed to stopped.
+ transitionTo(mStoppedState);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_STOP:
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ mDhcpClient = null;
+ transitionTo(mStoppedState);
+ break;
+
+ default:
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StartedState extends State {
+ @Override
+ public void enter() {
+ mStartTimeMillis = SystemClock.elapsedRealtime();
+
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime() +
+ mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
+
+ if (readyToProceed()) {
+ transitionTo(mRunningState);
+ } else {
+ // Clear all IPv4 and IPv6 before proceeding to RunningState.
+ // Clean up any leftover state from an abnormal exit from
+ // tethering or during an IpClient restart.
+ stopAllIP();
+ }
+ }
+
+ @Override
+ public void exit() {
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ if (readyToProceed()) {
+ transitionTo(mRunningState);
+ }
+ break;
+
+ case EVENT_PROVISIONING_TIMEOUT:
+ handleProvisioningFailure();
+ break;
+
+ default:
+ // It's safe to process messages out of order because the
+ // only message that can both
+ // a) be received at this time and
+ // b) affect provisioning state
+ // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+
+ boolean readyToProceed() {
+ return (!mLinkProperties.hasIPv4Address() &&
+ !mLinkProperties.hasGlobalIPv6Address());
+ }
+ }
+
+ class RunningState extends State {
+ private ConnectivityPacketTracker mPacketTracker;
+ private boolean mDhcpActionInFlight;
+
+ @Override
+ public void enter() {
+ // Get the Configuration for ApfFilter from Context
+ final boolean filter802_3Frames =
+ 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);
+ // TODO: investigate the effects of any multicast filtering racing/interfering with the
+ // rest of this IP configuration startup.
+ if (mApfFilter == null) {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+
+ mPacketTracker = createPacketTracker();
+ if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+ if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+ transitionTo(mStoppingState);
+ return;
+ }
+
+ if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+ transitionTo(mStoppingState);
+ return;
+ }
+
+ final InitialConfiguration config = mConfiguration.mInitialConfig;
+ if ((config != null) && !applyInitialConfig(config)) {
+ // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ transitionTo(mStoppingState);
+ return;
+ }
+
+ if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+ doImmediateProvisioningFailure(
+ IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+ transitionTo(mStoppingState);
+ return;
+ }
+ }
+
+ @Override
+ public void exit() {
+ stopDhcpAction();
+
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.stop();
+ mIpReachabilityMonitor = null;
+ }
+
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+ mDhcpClient.doQuit();
+ }
+
+ if (mPacketTracker != null) {
+ mPacketTracker.stop();
+ mPacketTracker = null;
+ }
+
+ if (mApfFilter != null) {
+ mApfFilter.shutdown();
+ mApfFilter = null;
+ }
+
+ resetLinkProperties();
+ }
+
+ private ConnectivityPacketTracker createPacketTracker() {
+ try {
+ return new ConnectivityPacketTracker(
+ getHandler(), mNetworkInterface, mConnectivityPacketLog);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private void ensureDhcpAction() {
+ if (!mDhcpActionInFlight) {
+ mCallback.onPreDhcpAction();
+ mDhcpActionInFlight = true;
+ final long alarmTime = SystemClock.elapsedRealtime() +
+ mConfiguration.mRequestedPreDhcpActionMs;
+ mDhcpActionTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+
+ private void stopDhcpAction() {
+ mDhcpActionTimeoutAlarm.cancel();
+ if (mDhcpActionInFlight) {
+ mCallback.onPostDhcpAction();
+ mDhcpActionInFlight = false;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case CMD_START:
+ logError("ALERT: START received in StartedState. Please fix caller.");
+ break;
+
+ case CMD_CONFIRM:
+ // TODO: Possibly introduce a second type of confirmation
+ // that both probes (a) on-link neighbors and (b) does
+ // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
+ // roams.
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.probeAll();
+ }
+ break;
+
+ case EVENT_PRE_DHCP_ACTION_COMPLETE:
+ // It's possible to reach here if, for example, someone
+ // calls completedPreDhcpAction() after provisioning with
+ // a static IP configuration.
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+ transitionTo(mStoppingState);
+ }
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER: {
+ mMulticastFiltering = (boolean) msg.obj;
+ if (mApfFilter != null) {
+ mApfFilter.setMulticastFilter(mMulticastFiltering);
+ } else {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+ break;
+ }
+
+ case EVENT_DHCPACTION_TIMEOUT:
+ stopDhcpAction();
+ break;
+
+ case DhcpClient.CMD_PRE_DHCP_ACTION:
+ if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+ ensureDhcpAction();
+ } else {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+ final LinkAddress ipAddress = (LinkAddress) msg.obj;
+ if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+ mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+ } else {
+ logError("Failed to set IPv4 address.");
+ dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
+ new LinkProperties(mLinkProperties));
+ transitionTo(mStoppingState);
+ }
+ break;
+ }
+
+ // This message is only received when:
+ //
+ // a) initial address acquisition succeeds,
+ // b) renew succeeds or is NAK'd,
+ // c) rebind succeeds or is NAK'd, or
+ // c) the lease expires,
+ //
+ // but never when initial address acquisition fails. The latter
+ // condition is now governed by the provisioning timeout.
+ case DhcpClient.CMD_POST_DHCP_ACTION:
+ stopDhcpAction();
+
+ switch (msg.arg1) {
+ case DhcpClient.DHCP_SUCCESS:
+ handleIPv4Success((DhcpResults) msg.obj);
+ break;
+ case DhcpClient.DHCP_FAILURE:
+ handleIPv4Failure();
+ break;
+ default:
+ logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+ }
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // DHCPv4 quit early for some reason.
+ logError("Unexpected CMD_ON_QUIT.");
+ mDhcpClient = null;
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ private static class MessageHandlingLogger {
+ public String processedInState;
+ public String receivedInState;
+
+ public void reset() {
+ processedInState = null;
+ receivedInState = null;
+ }
+
+ public void handled(State processedIn, IState receivedIn) {
+ processedInState = processedIn.getClass().getSimpleName();
+ receivedInState = receivedIn.getName();
+ }
+
+ public String toString() {
+ return String.format("rcvd_in=%s, proc_in=%s",
+ receivedInState, processedInState);
+ }
+ }
+
+ // TODO: extract out into CollectionUtils.
+ static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+ for (T t : coll) {
+ if (fn.test(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+ return !any(coll, not(fn));
+ }
+
+ static <T> Predicate<T> not(Predicate<T> fn) {
+ return (t) -> !fn.test(t);
+ }
+
+ static <T> String join(String delimiter, Collection<T> coll) {
+ return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+ }
+
+ static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+ for (T t: coll) {
+ if (fn.test(t)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+ return coll.stream().filter(fn).collect(Collectors.toList());
+ }
+}
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index bc07b810..b12cb32c 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,1708 +16,161 @@
package android.net.ip;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
-
import android.content.Context;
-import android.net.DhcpResults;
import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties.ProvisioningChange;
import android.net.LinkProperties;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
+import android.net.Network;
import android.net.StaticIpConfiguration;
import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
-import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
import android.os.INetworkManagementService;
-import android.os.Message;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.SparseArray;
+import android.net.apf.ApfCapabilities;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.IState;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.net.NetlinkTracker;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.List;
-import java.util.Set;
-import java.util.StringJoiner;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * IpManager
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpManager is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
+/*
+ * TODO: Delete this altogether in favor of its renamed successor: IpClient.
*
* @hide
*/
-public class IpManager extends StateMachine {
- private static final boolean DBG = false;
-
- // For message logging.
- private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
- private static final SparseArray<String> sWhatToString =
- MessageUtils.findMessageNames(sMessageClasses);
-
- /**
- * Callbacks for handling IpManager events.
- */
- public static class Callback {
- // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
- // when constructing a ProvisioningConfiguration.
- //
- // Implementations of onPreDhcpAction() must call
- // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
- // to proceed.
- public void onPreDhcpAction() {}
- public void onPostDhcpAction() {}
-
- // This is purely advisory and not an indication of provisioning
- // success or failure. This is only here for callers that want to
- // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
- // DHCPv4 or static IPv4 configuration failure or success can be
- // determined by whether or not the passed-in DhcpResults object is
- // null or not.
- public void onNewDhcpResults(DhcpResults dhcpResults) {}
-
- public void onProvisioningSuccess(LinkProperties newLp) {}
- public void onProvisioningFailure(LinkProperties newLp) {}
-
- // Invoked on LinkProperties changes.
- public void onLinkPropertiesChange(LinkProperties newLp) {}
-
- // Called when the internal IpReachabilityMonitor (if enabled) has
- // detected the loss of a critical number of required neighbors.
- public void onReachabilityLost(String logMsg) {}
-
- // Called when the IpManager state machine terminates.
- public void onQuit() {}
-
- // Install an APF program to filter incoming packets.
- public void installPacketFilter(byte[] filter) {}
-
- // If multicast filtering cannot be accomplished with APF, this function will be called to
- // actuate multicast filtering using another means.
- public void setFallbackMulticastFilter(boolean enabled) {}
-
- // Enabled/disable Neighbor Discover offload functionality. This is
- // called, for example, whenever 464xlat is being started or stopped.
- public void setNeighborDiscoveryOffload(boolean enable) {}
- }
-
- public static class WaitForProvisioningCallback extends Callback {
- private LinkProperties mCallbackLinkProperties;
-
- public LinkProperties waitForProvisioning() {
- synchronized (this) {
- try {
- wait();
- } catch (InterruptedException e) {}
- return mCallbackLinkProperties;
- }
+public class IpManager extends IpClient {
+ public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration {
+ public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) {
+ super(ipcConfig);
}
- @Override
- public void onProvisioningSuccess(LinkProperties newLp) {
- synchronized (this) {
- mCallbackLinkProperties = newLp;
- notify();
- }
- }
-
- @Override
- public void onProvisioningFailure(LinkProperties newLp) {
- synchronized (this) {
- mCallbackLinkProperties = null;
- notify();
- }
- }
- }
-
- // Use a wrapper class to log in order to ensure complete and detailed
- // logging. This method is lighter weight than annotations/reflection
- // and has the following benefits:
- //
- // - No invoked method can be forgotten.
- // Any new method added to IpManager.Callback must be overridden
- // here or it will never be called.
- //
- // - No invoking call site can be forgotten.
- // Centralized logging in this way means call sites don't need to
- // remember to log, and therefore no call site can be forgotten.
- //
- // - No variation in log format among call sites.
- // Encourages logging of any available arguments, and all call sites
- // are necessarily logged identically.
- //
- // TODO: Find an lighter weight approach.
- private class LoggingCallbackWrapper extends Callback {
- private static final String PREFIX = "INVOKE ";
- private Callback mCallback;
-
- public LoggingCallbackWrapper(Callback callback) {
- mCallback = callback;
- }
-
- private void log(String msg) {
- mLog.log(PREFIX + msg);
- }
-
- @Override
- public void onPreDhcpAction() {
- mCallback.onPreDhcpAction();
- log("onPreDhcpAction()");
- }
- @Override
- public void onPostDhcpAction() {
- mCallback.onPostDhcpAction();
- log("onPostDhcpAction()");
- }
- @Override
- public void onNewDhcpResults(DhcpResults dhcpResults) {
- mCallback.onNewDhcpResults(dhcpResults);
- log("onNewDhcpResults({" + dhcpResults + "})");
- }
- @Override
- public void onProvisioningSuccess(LinkProperties newLp) {
- mCallback.onProvisioningSuccess(newLp);
- log("onProvisioningSuccess({" + newLp + "})");
- }
- @Override
- public void onProvisioningFailure(LinkProperties newLp) {
- mCallback.onProvisioningFailure(newLp);
- log("onProvisioningFailure({" + newLp + "})");
- }
- @Override
- public void onLinkPropertiesChange(LinkProperties newLp) {
- mCallback.onLinkPropertiesChange(newLp);
- log("onLinkPropertiesChange({" + newLp + "})");
- }
- @Override
- public void onReachabilityLost(String logMsg) {
- mCallback.onReachabilityLost(logMsg);
- log("onReachabilityLost(" + logMsg + ")");
- }
- @Override
- public void onQuit() {
- mCallback.onQuit();
- log("onQuit()");
- }
- @Override
- public void installPacketFilter(byte[] filter) {
- mCallback.installPacketFilter(filter);
- log("installPacketFilter(byte[" + filter.length + "])");
- }
- @Override
- public void setFallbackMulticastFilter(boolean enabled) {
- mCallback.setFallbackMulticastFilter(enabled);
- log("setFallbackMulticastFilter(" + enabled + ")");
- }
- @Override
- public void setNeighborDiscoveryOffload(boolean enable) {
- mCallback.setNeighborDiscoveryOffload(enable);
- log("setNeighborDiscoveryOffload(" + enable + ")");
- }
- }
-
- /**
- * This class encapsulates parameters to be passed to
- * IpManager#startProvisioning(). A defensive copy is made by IpManager
- * and the values specified herein are in force until IpManager#stop()
- * is called.
- *
- * Example use:
- *
- * final ProvisioningConfiguration config =
- * mIpManager.buildProvisioningConfiguration()
- * .withPreDhcpAction()
- * .withProvisioningTimeoutMs(36 * 1000)
- * .build();
- * mIpManager.startProvisioning(config);
- * ...
- * mIpManager.stop();
- *
- * The specified provisioning configuration will only be active until
- * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
- * must specify the configuration again.
- */
- public static class ProvisioningConfiguration {
- // TODO: Delete this default timeout once those callers that care are
- // fixed to pass in their preferred timeout.
- //
- // We pick 36 seconds so we can send DHCP requests at
- //
- // t=0, t=2, t=6, t=14, t=30
- //
- // allowing for 10% jitter.
- private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
-
- public static class Builder {
- private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
-
+ public static class Builder extends IpClient.ProvisioningConfiguration.Builder {
+ @Override
public Builder withoutIPv4() {
- mConfig.mEnableIPv4 = false;
+ super.withoutIPv4();
return this;
}
-
+ @Override
public Builder withoutIPv6() {
- mConfig.mEnableIPv6 = false;
+ super.withoutIPv6();
return this;
}
-
+ @Override
public Builder withoutIpReachabilityMonitor() {
- mConfig.mUsingIpReachabilityMonitor = false;
+ super.withoutIpReachabilityMonitor();
return this;
}
-
+ @Override
public Builder withPreDhcpAction() {
- mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+ super.withPreDhcpAction();
return this;
}
-
+ @Override
public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
- mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+ super.withPreDhcpAction(dhcpActionTimeoutMs);
return this;
}
-
+ // No Override; locally defined type.
public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
- mConfig.mInitialConfig = initialConfig;
+ super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig);
return this;
}
-
+ @Override
public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
- mConfig.mStaticIpConfig = staticConfig;
+ super.withStaticConfiguration(staticConfig);
return this;
}
-
+ @Override
public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
- mConfig.mApfCapabilities = apfCapabilities;
+ super.withApfCapabilities(apfCapabilities);
return this;
}
-
+ @Override
public Builder withProvisioningTimeoutMs(int timeoutMs) {
- mConfig.mProvisioningTimeoutMs = timeoutMs;
+ super.withProvisioningTimeoutMs(timeoutMs);
return this;
}
-
+ @Override
public Builder withIPv6AddrGenModeEUI64() {
- mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+ super.withIPv6AddrGenModeEUI64();
return this;
}
-
+ @Override
public Builder withIPv6AddrGenModeStablePrivacy() {
- mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ super.withIPv6AddrGenModeStablePrivacy();
return this;
}
-
+ @Override
+ public Builder withNetwork(Network network) {
+ super.withNetwork(network);
+ return this;
+ }
+ @Override
+ public Builder withDisplayName(String displayName) {
+ super.withDisplayName(displayName);
+ return this;
+ }
+ @Override
public ProvisioningConfiguration build() {
- return new ProvisioningConfiguration(mConfig);
+ return new ProvisioningConfiguration(super.build());
}
}
+ }
- /* package */ boolean mEnableIPv4 = true;
- /* package */ boolean mEnableIPv6 = true;
- /* package */ boolean mUsingIpReachabilityMonitor = true;
- /* package */ int mRequestedPreDhcpActionMs;
- /* package */ InitialConfiguration mInitialConfig;
- /* package */ StaticIpConfiguration mStaticIpConfig;
- /* package */ ApfCapabilities mApfCapabilities;
- /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
- /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-
- public ProvisioningConfiguration() {} // used by Builder
-
- public ProvisioningConfiguration(ProvisioningConfiguration other) {
- mEnableIPv4 = other.mEnableIPv4;
- mEnableIPv6 = other.mEnableIPv6;
- mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
- mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
- mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
- mStaticIpConfig = other.mStaticIpConfig;
- mApfCapabilities = other.mApfCapabilities;
- mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
- }
+ public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+ return new ProvisioningConfiguration.Builder();
+ }
- @Override
- public String toString() {
- return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
- .add("mEnableIPv4: " + mEnableIPv4)
- .add("mEnableIPv6: " + mEnableIPv6)
- .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
- .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
- .add("mInitialConfig: " + mInitialConfig)
- .add("mStaticIpConfig: " + mStaticIpConfig)
- .add("mApfCapabilities: " + mApfCapabilities)
- .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
- .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
- .toString();
- }
+ public static class InitialConfiguration extends IpClient.InitialConfiguration {
+ }
- public boolean isValid() {
- return (mInitialConfig == null) || mInitialConfig.isValid();
- }
+ public static class Callback extends IpClient.Callback {
}
- public static class InitialConfiguration {
- public final Set<LinkAddress> ipAddresses = new HashSet<>();
- public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
- public final Set<InetAddress> dnsServers = new HashSet<>();
- public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
+ public static class WaitForProvisioningCallback extends Callback {
+ private LinkProperties mCallbackLinkProperties;
- public static InitialConfiguration copy(InitialConfiguration config) {
- if (config == null) {
- return null;
+ public LinkProperties waitForProvisioning() {
+ synchronized (this) {
+ try {
+ wait();
+ } catch (InterruptedException e) {}
+ return mCallbackLinkProperties;
}
- InitialConfiguration configCopy = new InitialConfiguration();
- configCopy.ipAddresses.addAll(config.ipAddresses);
- configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
- configCopy.dnsServers.addAll(config.dnsServers);
- return configCopy;
}
@Override
- public String toString() {
- return String.format(
- "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
- join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
- join(", ", dnsServers), gateway);
- }
-
- public boolean isValid() {
- if (ipAddresses.isEmpty()) {
- return false;
- }
-
- // For every IP address, there must be at least one prefix containing that address.
- for (LinkAddress addr : ipAddresses) {
- if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
- return false;
- }
- }
- // For every dns server, there must be at least one prefix containing that address.
- for (InetAddress addr : dnsServers) {
- if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
- return false;
- }
- }
- // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
- // (read: compliant with RFC4291#section2.5.4).
- if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
- return false;
- }
- // If directlyConnectedRoutes contains an IPv6 default route
- // then ipAddresses MUST contain at least one non-ULA GUA.
- if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
- && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
- return false;
- }
- // The prefix length of routes in directlyConnectedRoutes be within reasonable
- // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
- if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
- return false;
- }
- // There no more than one IPv4 address
- if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
- return false;
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ synchronized (this) {
+ mCallbackLinkProperties = newLp;
+ notify();
}
-
- return true;
}
- /**
- * @return true if the given list of addressess and routes satisfies provisioning for this
- * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
- * because addresses and routes seen by Netlink will contain additional fields like flags,
- * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
- * provisioning check always fails.
- *
- * If the given list of routes is null, only addresses are taken into considerations.
- */
- public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
- if (ipAddresses.isEmpty()) {
- return false;
- }
-
- for (LinkAddress addr : ipAddresses) {
- if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
- return false;
- }
- }
-
- if (routes != null) {
- for (IpPrefix prefix : directlyConnectedRoutes) {
- if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
- return false;
- }
- }
+ @Override
+ public void onProvisioningFailure(LinkProperties newLp) {
+ synchronized (this) {
+ mCallbackLinkProperties = null;
+ notify();
}
-
- return true;
- }
-
- private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
- return !route.hasGateway() && prefix.equals(route.getDestination());
- }
-
- private static boolean isPrefixLengthCompliant(LinkAddress addr) {
- return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
- }
-
- private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
- return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
- }
-
- private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
- return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
- && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
- }
-
- private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
- return prefix.getAddress().equals(Inet6Address.ANY);
- }
-
- private static boolean isIPv6GUA(LinkAddress addr) {
- return addr.isIPv6() && addr.isGlobalPreferred();
}
}
- public static final String DUMP_ARG = "ipmanager";
- public static final String DUMP_ARG_CONFIRM = "confirm";
-
- private static final int CMD_TERMINATE_AFTER_STOP = 1;
- private static final int CMD_STOP = 2;
- private static final int CMD_START = 3;
- private static final int CMD_CONFIRM = 4;
- private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
- // Sent by NetlinkTracker to communicate netlink events.
- private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
- private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
- private static final int CMD_UPDATE_HTTP_PROXY = 8;
- private static final int CMD_SET_MULTICAST_FILTER = 9;
- private static final int EVENT_PROVISIONING_TIMEOUT = 10;
- private static final int EVENT_DHCPACTION_TIMEOUT = 11;
-
- private static final int MAX_LOG_RECORDS = 500;
- private static final int MAX_PACKET_RECORDS = 100;
-
- private static final boolean NO_CALLBACKS = false;
- private static final boolean SEND_CALLBACKS = true;
-
- // This must match the interface prefix in clatd.c.
- // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
- private static final String CLAT_PREFIX = "v4-";
-
- private final State mStoppedState = new StoppedState();
- private final State mStoppingState = new StoppingState();
- private final State mStartedState = new StartedState();
- private final State mRunningState = new RunningState();
-
- private final String mTag;
- private final Context mContext;
- private final String mInterfaceName;
- private final String mClatInterfaceName;
- @VisibleForTesting
- protected final Callback mCallback;
- private final INetworkManagementService mNwService;
- private final NetlinkTracker mNetlinkTracker;
- private final WakeupMessage mProvisioningTimeoutAlarm;
- private final WakeupMessage mDhcpActionTimeoutAlarm;
- private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
- private final SharedLog mLog;
- private final LocalLog mConnectivityPacketLog;
- private final MessageHandlingLogger mMsgStateLogger;
- private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
- private final InterfaceController mInterfaceCtrl;
-
- private NetworkInterface mNetworkInterface;
-
- /**
- * Non-final member variables accessed only from within our StateMachine.
- */
- private LinkProperties mLinkProperties;
- private ProvisioningConfiguration mConfiguration;
- private IpReachabilityMonitor mIpReachabilityMonitor;
- private DhcpClient mDhcpClient;
- private DhcpResults mDhcpResults;
- private String mTcpBufferSizes;
- private ProxyInfo mHttpProxy;
- private ApfFilter mApfFilter;
- private boolean mMulticastFiltering;
- private long mStartTimeMillis;
-
public IpManager(Context context, String ifName, Callback callback) {
this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
NetdService.getInstance());
}
- /**
- * An expanded constructor, useful for dependency injection.
- * TODO: migrate all test users to mock IpManager directly and remove this ctor.
- */
public IpManager(Context context, String ifName, Callback callback,
INetworkManagementService nwService) {
this(context, ifName, callback, nwService, NetdService.getInstance());
}
@VisibleForTesting
- IpManager(Context context, String ifName, Callback callback,
+ public IpManager(Context context, String ifName, Callback callback,
INetworkManagementService nwService, INetd netd) {
- super(IpManager.class.getSimpleName() + "." + ifName);
- mTag = getName();
-
- mContext = context;
- mInterfaceName = ifName;
- mClatInterfaceName = CLAT_PREFIX + ifName;
- mCallback = new LoggingCallbackWrapper(callback);
- mNwService = nwService;
-
- mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
- mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
- mMsgStateLogger = new MessageHandlingLogger();
-
- mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
-
- mNetlinkTracker = new NetlinkTracker(
- mInterfaceName,
- new NetlinkTracker.Callback() {
- @Override
- public void update() {
- sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- }
- }) {
- @Override
- public void interfaceAdded(String iface) {
- super.interfaceAdded(iface);
- if (mClatInterfaceName.equals(iface)) {
- mCallback.setNeighborDiscoveryOffload(false);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceAdded(" + iface +")";
- logMsg(msg);
- }
-
- @Override
- public void interfaceRemoved(String iface) {
- super.interfaceRemoved(iface);
- // TODO: Also observe mInterfaceName going down and take some
- // kind of appropriate action.
- if (mClatInterfaceName.equals(iface)) {
- // TODO: consider sending a message to the IpManager main
- // StateMachine thread, in case "NDO enabled" state becomes
- // tied to more things that 464xlat operation.
- mCallback.setNeighborDiscoveryOffload(true);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceRemoved(" + iface +")";
- logMsg(msg);
- }
-
- private void logMsg(String msg) {
- Log.d(mTag, msg);
- getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
- }
- };
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
-
- mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
- () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
-
- mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
- mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
- // Anything the StateMachine may access must have been instantiated
- // before this point.
- configureAndStartStateMachine();
-
- // Anything that may send messages to the StateMachine must only be
- // configured to do so after the StateMachine has started (above).
- startStateMachineUpdaters();
- }
-
- private void configureAndStartStateMachine() {
- addState(mStoppedState);
- addState(mStartedState);
- addState(mRunningState, mStartedState);
- addState(mStoppingState);
-
- setInitialState(mStoppedState);
-
- super.start();
- }
-
- private void startStateMachineUpdaters() {
- try {
- mNwService.registerObserver(mNetlinkTracker);
- } catch (RemoteException e) {
- logError("Couldn't register NetlinkTracker: %s", e);
- }
-
- mMultinetworkPolicyTracker.start();
- }
-
- private void stopStateMachineUpdaters() {
- try {
- mNwService.unregisterObserver(mNetlinkTracker);
- } catch (RemoteException e) {
- logError("Couldn't unregister NetlinkTracker: %s", e);
- }
-
- mMultinetworkPolicyTracker.shutdown();
- }
-
- @Override
- protected void onQuitting() {
- mCallback.onQuit();
- }
-
- // Shut down this IpManager instance altogether.
- public void shutdown() {
- stop();
- sendMessage(CMD_TERMINATE_AFTER_STOP);
- }
-
- public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
- return new ProvisioningConfiguration.Builder();
+ super(context, ifName, callback, nwService, netd);
}
public void startProvisioning(ProvisioningConfiguration req) {
- if (!req.isValid()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- return;
- }
-
- getNetworkInterface();
-
- mCallback.setNeighborDiscoveryOffload(true);
- sendMessage(CMD_START, new ProvisioningConfiguration(req));
- }
-
- // TODO: Delete this.
- public void startProvisioning(StaticIpConfiguration staticIpConfig) {
- startProvisioning(buildProvisioningConfiguration()
- .withStaticConfiguration(staticIpConfig)
- .build());
- }
-
- public void startProvisioning() {
- startProvisioning(new ProvisioningConfiguration());
- }
-
- public void stop() {
- sendMessage(CMD_STOP);
- }
-
- public void confirmConfiguration() {
- sendMessage(CMD_CONFIRM);
- }
-
- public void completedPreDhcpAction() {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
-
- /**
- * Set the TCP buffer sizes to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
- */
- public void setTcpBufferSizes(String tcpBufferSizes) {
- sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
- }
-
- /**
- * Set the HTTP Proxy configuration to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
- */
- public void setHttpProxy(ProxyInfo proxyInfo) {
- sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
- }
-
- /**
- * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
- * if not, Callback.setFallbackMulticastFilter() is called.
- */
- public void setMulticastFilter(boolean enabled) {
- sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
- }
-
- public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
- // Execute confirmConfiguration() and take no further action.
- confirmConfiguration();
- return;
- }
-
- // Thread-unsafe access to mApfFilter but just used for debugging.
- final ApfFilter apfFilter = mApfFilter;
- final ProvisioningConfiguration provisioningConfig = mConfiguration;
- final ApfCapabilities apfCapabilities = (provisioningConfig != null)
- ? provisioningConfig.mApfCapabilities : null;
-
- IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println(mTag + " APF dump:");
- pw.increaseIndent();
- if (apfFilter != null) {
- apfFilter.dump(pw);
- } else {
- pw.print("No active ApfFilter; ");
- if (provisioningConfig == null) {
- pw.println("IpManager not yet started.");
- } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
- pw.println("Hardware does not support APF.");
- } else {
- pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
- }
- }
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " current ProvisioningConfiguration:");
- pw.increaseIndent();
- pw.println(Objects.toString(provisioningConfig, "N/A"));
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " StateMachine dump:");
- pw.increaseIndent();
- mLog.dump(fd, pw, args);
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " connectivity packet log:");
- pw.println();
- pw.println("Debug with python and scapy via:");
- pw.println("shell$ python");
- pw.println(">>> from scapy import all as scapy");
- pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
- pw.println();
-
- pw.increaseIndent();
- mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
- pw.decreaseIndent();
- }
-
-
- /**
- * Internals.
- */
-
- @Override
- protected String getWhatToString(int what) {
- return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
- }
-
- @Override
- protected String getLogRecString(Message msg) {
- final String logLine = String.format(
- "%s/%d %d %d %s [%s]",
- mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
- msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
- final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
- mLog.log(richerLogLine);
- if (DBG) {
- Log.d(mTag, richerLogLine);
- }
-
- mMsgStateLogger.reset();
- return logLine;
- }
-
- @Override
- protected boolean recordLogRec(Message msg) {
- // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
- // and we already log any LinkProperties change that results in an
- // invocation of IpManager.Callback#onLinkPropertiesChange().
- final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- if (!shouldLog) {
- mMsgStateLogger.reset();
- }
- return shouldLog;
- }
-
- private void logError(String fmt, Object... args) {
- final String msg = "ERROR " + String.format(fmt, args);
- Log.e(mTag, msg);
- mLog.log(msg);
- }
-
- private void getNetworkInterface() {
- try {
- mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
- } catch (SocketException | NullPointerException e) {
- // TODO: throw new IllegalStateException.
- logError("Failed to get interface object: %s", e);
- }
- }
-
- // This needs to be called with care to ensure that our LinkProperties
- // are in sync with the actual LinkProperties of the interface. For example,
- // we should only call this if we know for sure that there are no IP addresses
- // assigned to the interface, etc.
- private void resetLinkProperties() {
- mNetlinkTracker.clearLinkProperties();
- mConfiguration = null;
- mDhcpResults = null;
- mTcpBufferSizes = "";
- mHttpProxy = null;
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
- }
-
- private void recordMetric(final int type) {
- if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
- final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
- mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
- }
-
- // For now: use WifiStateMachine's historical notion of provisioned.
- @VisibleForTesting
- static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
- // For historical reasons, we should connect even if all we have is
- // an IPv4 address and nothing else.
- if (lp.hasIPv4Address() || lp.isProvisioned()) {
- return true;
- }
- if (config == null) {
- return false;
- }
-
- // When an InitialConfiguration is specified, ignore any difference with previous
- // properties and instead check if properties observed match the desired properties.
- return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
- }
-
- // TODO: Investigate folding all this into the existing static function
- // LinkProperties.compareProvisioning() or some other single function that
- // takes two LinkProperties objects and returns a ProvisioningChange
- // object that is a correct and complete assessment of what changed, taking
- // account of the asymmetries described in the comments in this function.
- // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
- private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
- ProvisioningChange delta;
- InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
- final boolean wasProvisioned = isProvisioned(oldLp, config);
- final boolean isProvisioned = isProvisioned(newLp, config);
-
- if (!wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.GAINED_PROVISIONING;
- } else if (wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.STILL_PROVISIONED;
- } else if (!wasProvisioned && !isProvisioned) {
- delta = ProvisioningChange.STILL_NOT_PROVISIONED;
- } else {
- // (wasProvisioned && !isProvisioned)
- //
- // Note that this is true even if we lose a configuration element
- // (e.g., a default gateway) that would not be required to advance
- // into provisioned state. This is intended: if we have a default
- // router and we lose it, that's a sure sign of a problem, but if
- // we connect to a network with no IPv4 DNS servers, we consider
- // that to be a network without DNS servers and connect anyway.
- //
- // See the comment below.
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
- final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
- final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
- // If bad wifi avoidance is disabled, then ignore IPv6 loss of
- // provisioning. Otherwise, when a hotspot that loses Internet
- // access sends out a 0-lifetime RA to its clients, the clients
- // will disconnect and then reconnect, avoiding the bad hotspot,
- // instead of getting stuck on the bad hotspot. http://b/31827713 .
- //
- // This is incorrect because if the hotspot then regains Internet
- // access with a different prefix, TCP connections on the
- // deprecated addresses will remain stuck.
- //
- // Note that we can still be disconnected by IpReachabilityMonitor
- // if the IPv6 default gateway (but not the IPv6 DNS servers; see
- // accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
-
- // Additionally:
- //
- // Partial configurations (e.g., only an IPv4 address with no DNS
- // servers and no default route) are accepted as long as DHCPv4
- // succeeds. On such a network, isProvisioned() will always return
- // false, because the configuration is not complete, but we want to
- // connect anyway. It might be a disconnected network such as a
- // Chromecast or a wireless printer, for example.
- //
- // Because on such a network isProvisioned() will always return false,
- // delta will never be LOST_PROVISIONING. So check for loss of
- // provisioning here too.
- if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- // Additionally:
- //
- // If the previous link properties had a global IPv6 address and an
- // IPv6 default route then also consider the loss of that default route
- // to be a loss of provisioning. See b/27962810.
- if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- return delta;
- }
-
- private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
- switch (delta) {
- case GAINED_PROVISIONING:
- if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
- recordMetric(IpManagerEvent.PROVISIONING_OK);
- mCallback.onProvisioningSuccess(newLp);
- break;
-
- case LOST_PROVISIONING:
- if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
- recordMetric(IpManagerEvent.PROVISIONING_FAIL);
- mCallback.onProvisioningFailure(newLp);
- break;
-
- default:
- if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
- mCallback.onLinkPropertiesChange(newLp);
- break;
- }
- }
-
- // Updates all IpManager-related state concerned with LinkProperties.
- // Returns a ProvisioningChange for possibly notifying other interested
- // parties that are not fronted by IpManager.
- private ProvisioningChange setLinkProperties(LinkProperties newLp) {
- if (mApfFilter != null) {
- mApfFilter.setLinkProperties(newLp);
- }
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.updateLinkProperties(newLp);
- }
-
- ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
- mLinkProperties = new LinkProperties(newLp);
-
- if (delta == ProvisioningChange.GAINED_PROVISIONING) {
- // TODO: Add a proper ProvisionedState and cancel the alarm in
- // its enter() method.
- mProvisioningTimeoutAlarm.cancel();
- }
-
- return delta;
- }
-
- private LinkProperties assembleLinkProperties() {
- // [1] Create a new LinkProperties object to populate.
- LinkProperties newLp = new LinkProperties();
- newLp.setInterfaceName(mInterfaceName);
-
- // [2] Pull in data from netlink:
- // - IPv4 addresses
- // - IPv6 addresses
- // - IPv6 routes
- // - IPv6 DNS servers
- //
- // N.B.: this is fundamentally race-prone and should be fixed by
- // changing NetlinkTracker from a hybrid edge/level model to an
- // edge-only model, or by giving IpManager its own netlink socket(s)
- // so as to track all required information directly.
- LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
- newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
- for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
- // [3] Add in data from DHCPv4, if available.
- //
- // mDhcpResults is never shared with any other owner so we don't have
- // to worry about concurrent modification.
- if (mDhcpResults != null) {
- for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
- newLp.setDomains(mDhcpResults.domains);
-
- if (mDhcpResults.mtu != 0) {
- newLp.setMtu(mDhcpResults.mtu);
- }
- }
-
- // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
- if (!TextUtils.isEmpty(mTcpBufferSizes)) {
- newLp.setTcpBufferSizes(mTcpBufferSizes);
- }
- if (mHttpProxy != null) {
- newLp.setHttpProxy(mHttpProxy);
- }
-
- // [5] Add data from InitialConfiguration
- if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
- InitialConfiguration config = mConfiguration.mInitialConfig;
- // Add InitialConfiguration routes and dns server addresses once all addresses
- // specified in the InitialConfiguration have been observed with Netlink.
- if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
- for (IpPrefix prefix : config.directlyConnectedRoutes) {
- newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
- }
- }
- addAllReachableDnsServers(newLp, config.dnsServers);
- }
- final LinkProperties oldLp = mLinkProperties;
- if (DBG) {
- Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
- netlinkLinkProperties, newLp, oldLp));
- }
-
- // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
- // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
- return newLp;
- }
-
- private static void addAllReachableDnsServers(
- LinkProperties lp, Iterable<InetAddress> dnses) {
- // TODO: Investigate deleting this reachability check. We should be
- // able to pass everything down to netd and let netd do evaluation
- // and RFC6724-style sorting.
- for (InetAddress dns : dnses) {
- if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
- lp.addDnsServer(dns);
- }
- }
- }
-
- // Returns false if we have lost provisioning, true otherwise.
- private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
- final LinkProperties newLp = assembleLinkProperties();
- if (Objects.equals(newLp, mLinkProperties)) {
- return true;
- }
- final ProvisioningChange delta = setLinkProperties(newLp);
- if (sendCallbacks) {
- dispatchCallback(delta, newLp);
- }
- return (delta != ProvisioningChange.LOST_PROVISIONING);
- }
-
- private void handleIPv4Success(DhcpResults dhcpResults) {
- mDhcpResults = new DhcpResults(dhcpResults);
- final LinkProperties newLp = assembleLinkProperties();
- final ProvisioningChange delta = setLinkProperties(newLp);
-
- if (DBG) {
- Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
- }
- mCallback.onNewDhcpResults(dhcpResults);
- dispatchCallback(delta, newLp);
- }
-
- private void handleIPv4Failure() {
- // TODO: Investigate deleting this clearIPv4Address() call.
- //
- // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
- // that could trigger a call to this function. If we missed handling
- // that message in StartedState for some reason we would still clear
- // any addresses upon entry to StoppedState.
- mInterfaceCtrl.clearIPv4Address();
- mDhcpResults = null;
- if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
- mCallback.onNewDhcpResults(null);
-
- handleProvisioningFailure();
- }
-
- private void handleProvisioningFailure() {
- final LinkProperties newLp = assembleLinkProperties();
- ProvisioningChange delta = setLinkProperties(newLp);
- // If we've gotten here and we're still not provisioned treat that as
- // a total loss of provisioning.
- //
- // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
- // there was no usable IPv6 obtained before a non-zero provisioning
- // timeout expired.
- //
- // Regardless: GAME OVER.
- if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- dispatchCallback(delta, newLp);
- if (delta == ProvisioningChange.LOST_PROVISIONING) {
- transitionTo(mStoppingState);
- }
- }
-
- private void doImmediateProvisioningFailure(int failureType) {
- logError("onProvisioningFailure(): %s", failureType);
- recordMetric(failureType);
- mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
- }
-
- private boolean startIPv4() {
- // If we have a StaticIpConfiguration attempt to apply it and
- // handle the result accordingly.
- if (mConfiguration.mStaticIpConfig != null) {
- if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
- handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
- } else {
- return false;
- }
- } else {
- // Start DHCPv4.
- mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
- mDhcpClient.registerForPreDhcpNotification();
- mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
- }
-
- return true;
- }
-
- private boolean startIPv6() {
- return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
- mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
- mInterfaceCtrl.enableIPv6();
- }
-
- private boolean applyInitialConfig(InitialConfiguration config) {
- // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
- for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
- if (!mInterfaceCtrl.addAddress(addr)) return false;
- }
-
- return true;
- }
-
- private boolean startIpReachabilityMonitor() {
- try {
- mIpReachabilityMonitor = new IpReachabilityMonitor(
- mContext,
- mInterfaceName,
- mLog,
- new IpReachabilityMonitor.Callback() {
- @Override
- public void notifyLost(InetAddress ip, String logMsg) {
- mCallback.onReachabilityLost(logMsg);
- }
- },
- mMultinetworkPolicyTracker);
- } catch (IllegalArgumentException iae) {
- // Failed to start IpReachabilityMonitor. Log it and call
- // onProvisioningFailure() immediately.
- //
- // See http://b/31038971.
- logError("IpReachabilityMonitor failure: %s", iae);
- mIpReachabilityMonitor = null;
- }
-
- return (mIpReachabilityMonitor != null);
- }
-
- private void stopAllIP() {
- // We don't need to worry about routes, just addresses, because:
- // - disableIpv6() will clear autoconf IPv6 routes as well, and
- // - we don't get IPv4 routes from netlink
- // so we neither react to nor need to wait for changes in either.
-
- mInterfaceCtrl.disableIPv6();
- mInterfaceCtrl.clearAllAddresses();
- }
-
- class StoppedState extends State {
- @Override
- public void enter() {
- stopAllIP();
-
- resetLinkProperties();
- if (mStartTimeMillis > 0) {
- recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
- mStartTimeMillis = 0;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_TERMINATE_AFTER_STOP:
- stopStateMachineUpdaters();
- quit();
- break;
-
- case CMD_STOP:
- break;
-
- case CMD_START:
- mConfiguration = (ProvisioningConfiguration) msg.obj;
- transitionTo(mStartedState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER:
- mMulticastFiltering = (boolean) msg.obj;
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // Everything is already stopped.
- logError("Unexpected CMD_ON_QUIT (already stopped).");
- break;
-
- default:
- return NOT_HANDLED;
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- class StoppingState extends State {
- @Override
- public void enter() {
- if (mDhcpClient == null) {
- // There's no DHCPv4 for which to wait; proceed to stopped.
- transitionTo(mStoppedState);
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_STOP:
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- mDhcpClient = null;
- transitionTo(mStoppedState);
- break;
-
- default:
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- class StartedState extends State {
- @Override
- public void enter() {
- mStartTimeMillis = SystemClock.elapsedRealtime();
-
- if (mConfiguration.mProvisioningTimeoutMs > 0) {
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mProvisioningTimeoutMs;
- mProvisioningTimeoutAlarm.schedule(alarmTime);
- }
-
- if (readyToProceed()) {
- transitionTo(mRunningState);
- } else {
- // Clear all IPv4 and IPv6 before proceeding to RunningState.
- // Clean up any leftover state from an abnormal exit from
- // tethering or during an IpManager restart.
- stopAllIP();
- }
- }
-
- @Override
- public void exit() {
- mProvisioningTimeoutAlarm.cancel();
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- if (readyToProceed()) {
- transitionTo(mRunningState);
- }
- break;
-
- case EVENT_PROVISIONING_TIMEOUT:
- handleProvisioningFailure();
- break;
-
- default:
- // It's safe to process messages out of order because the
- // only message that can both
- // a) be received at this time and
- // b) affect provisioning state
- // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
-
- boolean readyToProceed() {
- return (!mLinkProperties.hasIPv4Address() &&
- !mLinkProperties.hasGlobalIPv6Address());
- }
- }
-
- class RunningState extends State {
- private ConnectivityPacketTracker mPacketTracker;
- private boolean mDhcpActionInFlight;
-
- @Override
- public void enter() {
- // Get the Configuration for ApfFilter from Context
- boolean filter802_3Frames =
- mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
- int[] ethTypeBlackList = mContext.getResources().getIntArray(
- R.array.config_apfEthTypeBlackList);
-
- mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
- mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
- // TODO: investigate the effects of any multicast filtering racing/interfering with the
- // rest of this IP configuration startup.
- if (mApfFilter == null) {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
-
- mPacketTracker = createPacketTracker();
- if (mPacketTracker != null) mPacketTracker.start();
-
- if (mConfiguration.mEnableIPv6 && !startIPv6()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
- transitionTo(mStoppingState);
- return;
- }
-
- if (mConfiguration.mEnableIPv4 && !startIPv4()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
- transitionTo(mStoppingState);
- return;
- }
-
- InitialConfiguration config = mConfiguration.mInitialConfig;
- if ((config != null) && !applyInitialConfig(config)) {
- // TODO introduce a new IpManagerEvent constant to distinguish this error case.
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- transitionTo(mStoppingState);
- return;
- }
-
- if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
- doImmediateProvisioningFailure(
- IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
- transitionTo(mStoppingState);
- return;
- }
- }
-
- @Override
- public void exit() {
- stopDhcpAction();
-
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.stop();
- mIpReachabilityMonitor = null;
- }
-
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
- mDhcpClient.doQuit();
- }
-
- if (mPacketTracker != null) {
- mPacketTracker.stop();
- mPacketTracker = null;
- }
-
- if (mApfFilter != null) {
- mApfFilter.shutdown();
- mApfFilter = null;
- }
-
- resetLinkProperties();
- }
-
- private ConnectivityPacketTracker createPacketTracker() {
- try {
- return new ConnectivityPacketTracker(
- getHandler(), mNetworkInterface, mConnectivityPacketLog);
- } catch (IllegalArgumentException e) {
- return null;
- }
- }
-
- private void ensureDhcpAction() {
- if (!mDhcpActionInFlight) {
- mCallback.onPreDhcpAction();
- mDhcpActionInFlight = true;
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mRequestedPreDhcpActionMs;
- mDhcpActionTimeoutAlarm.schedule(alarmTime);
- }
- }
-
- private void stopDhcpAction() {
- mDhcpActionTimeoutAlarm.cancel();
- if (mDhcpActionInFlight) {
- mCallback.onPostDhcpAction();
- mDhcpActionInFlight = false;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case CMD_START:
- logError("ALERT: START received in StartedState. Please fix caller.");
- break;
-
- case CMD_CONFIRM:
- // TODO: Possibly introduce a second type of confirmation
- // that both probes (a) on-link neighbors and (b) does
- // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
- // roams.
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.probeAll();
- }
- break;
-
- case EVENT_PRE_DHCP_ACTION_COMPLETE:
- // It's possible to reach here if, for example, someone
- // calls completedPreDhcpAction() after provisioning with
- // a static IP configuration.
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
- transitionTo(mStoppingState);
- }
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER: {
- mMulticastFiltering = (boolean) msg.obj;
- if (mApfFilter != null) {
- mApfFilter.setMulticastFilter(mMulticastFiltering);
- } else {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
- break;
- }
-
- case EVENT_DHCPACTION_TIMEOUT:
- stopDhcpAction();
- break;
-
- case DhcpClient.CMD_PRE_DHCP_ACTION:
- if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
- ensureDhcpAction();
- } else {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
- final LinkAddress ipAddress = (LinkAddress) msg.obj;
- if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
- mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
- } else {
- logError("Failed to set IPv4 address.");
- dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
- new LinkProperties(mLinkProperties));
- transitionTo(mStoppingState);
- }
- break;
- }
-
- // This message is only received when:
- //
- // a) initial address acquisition succeeds,
- // b) renew succeeds or is NAK'd,
- // c) rebind succeeds or is NAK'd, or
- // c) the lease expires,
- //
- // but never when initial address acquisition fails. The latter
- // condition is now governed by the provisioning timeout.
- case DhcpClient.CMD_POST_DHCP_ACTION:
- stopDhcpAction();
-
- switch (msg.arg1) {
- case DhcpClient.DHCP_SUCCESS:
- handleIPv4Success((DhcpResults) msg.obj);
- break;
- case DhcpClient.DHCP_FAILURE:
- handleIPv4Failure();
- break;
- default:
- logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
- }
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // DHCPv4 quit early for some reason.
- logError("Unexpected CMD_ON_QUIT.");
- mDhcpClient = null;
- break;
-
- default:
- return NOT_HANDLED;
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- private static class MessageHandlingLogger {
- public String processedInState;
- public String receivedInState;
-
- public void reset() {
- processedInState = null;
- receivedInState = null;
- }
-
- public void handled(State processedIn, IState receivedIn) {
- processedInState = processedIn.getClass().getSimpleName();
- receivedInState = receivedIn.getName();
- }
-
- public String toString() {
- return String.format("rcvd_in=%s, proc_in=%s",
- receivedInState, processedInState);
- }
- }
-
- // TODO: extract out into CollectionUtils.
- static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
- for (T t : coll) {
- if (fn.test(t)) {
- return true;
- }
- }
- return false;
- }
-
- static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
- return !any(coll, not(fn));
- }
-
- static <T> Predicate<T> not(Predicate<T> fn) {
- return (t) -> !fn.test(t);
- }
-
- static <T> String join(String delimiter, Collection<T> coll) {
- return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
- }
-
- static <T> T find(Iterable<T> coll, Predicate<T> fn) {
- for (T t: coll) {
- if (fn.test(t)) {
- return t;
- }
- }
- return null;
- }
-
- static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
- return coll.stream().filter(fn).collect(Collectors.toList());
+ super.startProvisioning((IpClient.ProvisioningConfiguration) req);
}
}
diff --git a/android/net/metrics/ConnectStats.java b/android/net/metrics/ConnectStats.java
index 30b26562..2495cab1 100644
--- a/android/net/metrics/ConnectStats.java
+++ b/android/net/metrics/ConnectStats.java
@@ -20,6 +20,7 @@ import android.net.NetworkCapabilities;
import android.system.OsConstants;
import android.util.IntArray;
import android.util.SparseIntArray;
+
import com.android.internal.util.BitUtils;
import com.android.internal.util.TokenBucket;
@@ -43,6 +44,8 @@ public class ConnectStats {
public final TokenBucket mLatencyTb;
/** Maximum number of latency values recorded. */
public final int mMaxLatencyRecords;
+ /** Total count of events */
+ public int eventCount = 0;
/** Total count of successful connects. */
public int connectCount = 0;
/** Total count of successful connects done in blocking mode. */
@@ -57,12 +60,15 @@ public class ConnectStats {
mMaxLatencyRecords = maxLatencyRecords;
}
- public void addEvent(int errno, int latencyMs, String ipAddr) {
+ boolean addEvent(int errno, int latencyMs, String ipAddr) {
+ eventCount++;
if (isSuccess(errno)) {
countConnect(errno, ipAddr);
countLatency(errno, latencyMs);
+ return true;
} else {
countError(errno);
+ return false;
}
}
@@ -101,7 +107,7 @@ public class ConnectStats {
return (errno == 0) || isNonBlocking(errno);
}
- private static boolean isNonBlocking(int errno) {
+ static boolean isNonBlocking(int errno) {
// On non-blocking TCP sockets, connect() immediately returns EINPROGRESS.
// On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY.
return (errno == EINPROGRESS) || (errno == EALREADY);
@@ -117,6 +123,7 @@ public class ConnectStats {
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
+ builder.append(String.format("%d events, ", eventCount));
builder.append(String.format("%d success, ", connectCount));
builder.append(String.format("%d blocking, ", connectBlockingCount));
builder.append(String.format("%d IPv6 dst", ipv6ConnectCount));
diff --git a/android/net/metrics/DnsEvent.java b/android/net/metrics/DnsEvent.java
index a4970e4d..81b098bb 100644
--- a/android/net/metrics/DnsEvent.java
+++ b/android/net/metrics/DnsEvent.java
@@ -17,11 +17,13 @@
package android.net.metrics;
import android.net.NetworkCapabilities;
-import java.util.Arrays;
+
import com.android.internal.util.BitUtils;
+import java.util.Arrays;
+
/**
- * A DNS event recorded by NetdEventListenerService.
+ * A batch of DNS events recorded by NetdEventListenerService for a specific network.
* {@hide}
*/
final public class DnsEvent {
@@ -38,6 +40,8 @@ final public class DnsEvent {
// the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event
// is spread across the three array at position i.
public int eventCount;
+ // The number of successful DNS queries recorded.
+ public int successCount;
// The types of DNS queries as defined in INetdEventListener.
public byte[] eventTypes;
// Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there
@@ -54,10 +58,11 @@ final public class DnsEvent {
latenciesMs = new int[initialCapacity];
}
- public void addResult(byte eventType, byte returnCode, int latencyMs) {
+ boolean addResult(byte eventType, byte returnCode, int latencyMs) {
+ boolean isSuccess = (returnCode == 0);
if (eventCount >= SIZE_LIMIT) {
// TODO: implement better rate limiting that does not biases metrics.
- return;
+ return isSuccess;
}
if (eventCount == eventTypes.length) {
resize((int) (1.4 * eventCount));
@@ -66,6 +71,10 @@ final public class DnsEvent {
returnCodes[eventCount] = returnCode;
latenciesMs[eventCount] = latencyMs;
eventCount++;
+ if (isSuccess) {
+ successCount++;
+ }
+ return isSuccess;
}
public void resize(int newLength) {
@@ -80,6 +89,8 @@ final public class DnsEvent {
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
- return builder.append(eventCount).append(" events)").toString();
+ builder.append(String.format("%d events, ", eventCount));
+ builder.append(String.format("%d success)", successCount));
+ return builder.toString();
}
}
diff --git a/android/net/metrics/NetworkMetrics.java b/android/net/metrics/NetworkMetrics.java
new file mode 100644
index 00000000..2b662a0c
--- /dev/null
+++ b/android/net/metrics/NetworkMetrics.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.metrics;
+
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.TokenBucket;
+
+import java.util.StringJoiner;
+
+/**
+ * A class accumulating network metrics received from Netd regarding dns queries and
+ * connect() calls on a given network.
+ *
+ * This class also accumulates running sums of dns and connect latency stats and
+ * error counts for bug report logging.
+ *
+ * @hide
+ */
+public class NetworkMetrics {
+
+ private static final int INITIAL_DNS_BATCH_SIZE = 100;
+ private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+ // The network id of the Android Network.
+ public final int netId;
+ // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java.
+ public final long transports;
+ // Accumulated metrics for connect events.
+ public final ConnectStats connectMetrics;
+ // Accumulated metrics for dns events.
+ public final DnsEvent dnsMetrics;
+ // Running sums of latencies and error counts for connect and dns events.
+ public final Summary summary;
+ // Running sums of the most recent latencies and error counts for connect and dns events.
+ // Starts null until some events are accumulated.
+ // Allows to collect periodic snapshot of the running summaries for a given network.
+ public Summary pendingSummary;
+
+ public NetworkMetrics(int netId, long transports, TokenBucket tb) {
+ this.netId = netId;
+ this.transports = transports;
+ this.connectMetrics =
+ new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS);
+ this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
+ this.summary = new Summary(netId, transports);
+ }
+
+ /**
+ * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them
+ * into the long running Summary statistics of this NetworkMetrics, and also clear them.
+ */
+ public Summary getPendingStats() {
+ Summary s = pendingSummary;
+ pendingSummary = null;
+ if (s != null) {
+ summary.merge(s);
+ }
+ return s;
+ }
+
+ /** Accumulate a dns query result reported by netd. */
+ public void addDnsResult(int eventType, int returnCode, int latencyMs) {
+ if (pendingSummary == null) {
+ pendingSummary = new Summary(netId, transports);
+ }
+ boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs);
+ pendingSummary.dnsLatencies.count(latencyMs);
+ pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1);
+ }
+
+ /** Accumulate a connect query result reported by netd. */
+ public void addConnectResult(int error, int latencyMs, String ipAddr) {
+ if (pendingSummary == null) {
+ pendingSummary = new Summary(netId, transports);
+ }
+ boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr);
+ pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1);
+ if (ConnectStats.isNonBlocking(error)) {
+ pendingSummary.connectLatencies.count(latencyMs);
+ }
+ }
+
+ /** Represents running sums for dns and connect average error counts and average latencies. */
+ public static class Summary {
+
+ public final int netId;
+ public final long transports;
+ // DNS latencies measured in milliseconds.
+ public final Metrics dnsLatencies = new Metrics();
+ // DNS error rate measured in percentage points.
+ public final Metrics dnsErrorRate = new Metrics();
+ // Blocking connect latencies measured in milliseconds.
+ public final Metrics connectLatencies = new Metrics();
+ // Blocking and non blocking connect error rate measured in percentage points.
+ public final Metrics connectErrorRate = new Metrics();
+
+ public Summary(int netId, long transports) {
+ this.netId = netId;
+ this.transports = transports;
+ }
+
+ void merge(Summary that) {
+ dnsLatencies.merge(that.dnsLatencies);
+ dnsErrorRate.merge(that.dnsErrorRate);
+ connectLatencies.merge(that.connectLatencies);
+ connectErrorRate.merge(that.connectErrorRate);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner j = new StringJoiner(", ", "{", "}");
+ j.add("netId=" + netId);
+ for (int t : BitUtils.unpackBits(transports)) {
+ j.add(NetworkCapabilities.transportNameOf(t));
+ }
+ j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d",
+ (int) dnsLatencies.average(), (int) dnsLatencies.max,
+ 100 * dnsErrorRate.average(), dnsErrorRate.count));
+ j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d",
+ (int) connectLatencies.average(), (int) connectLatencies.max,
+ 100 * connectErrorRate.average(), connectErrorRate.count));
+ return j.toString();
+ }
+ }
+
+ /** Tracks a running sum and returns the average of a metric. */
+ static class Metrics {
+ public double sum;
+ public double max = Double.MIN_VALUE;
+ public int count;
+
+ void merge(Metrics that) {
+ this.count += that.count;
+ this.sum += that.sum;
+ this.max = Math.max(this.max, that.max);
+ }
+
+ void count(double value) {
+ count++;
+ sum += value;
+ max = Math.max(max, value);
+ }
+
+ double average() {
+ double a = sum / (double) count;
+ if (Double.isNaN(a)) {
+ a = 0;
+ }
+ return a;
+ }
+ }
+}
diff --git a/android/net/netlink/NetlinkSocket.java b/android/net/netlink/NetlinkSocket.java
index a9e0cd99..f5f211d8 100644
--- a/android/net/netlink/NetlinkSocket.java
+++ b/android/net/netlink/NetlinkSocket.java
@@ -96,7 +96,7 @@ public class NetlinkSocket implements Closeable {
mDescriptor = Os.socket(
OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
- Libcore.os.setsockoptInt(
+ Os.setsockoptInt(
mDescriptor, OsConstants.SOL_SOCKET,
OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
}
diff --git a/android/net/util/SharedLog.java b/android/net/util/SharedLog.java
index 343d237f..bbd3d13e 100644
--- a/android/net/util/SharedLog.java
+++ b/android/net/util/SharedLog.java
@@ -106,6 +106,10 @@ public class SharedLog {
record(Category.NONE, msg);
}
+ public void logf(String fmt, Object... args) {
+ log(String.format(fmt, args));
+ }
+
public void mark(String msg) {
record(Category.MARK, msg);
}
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index b08b4b7c..649b0cef 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -1029,6 +1029,26 @@ public class WifiManager {
}
/**
+ * Return all matching WifiConfigurations for this ScanResult.
+ *
+ * An empty list will be returned when no configurations are installed or if no configurations
+ * match the ScanResult.
+ *
+ * @param scanResult scanResult that represents the BSSID
+ * @return A list of {@link WifiConfiguration}
+ * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+ * @hide
+ */
+ public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+ try {
+ return mService.getAllMatchingWifiConfigs(scanResult);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
* Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
*
* An empty list will be returned if no match is found.
diff --git a/android/net/wifi/rtt/RangingRequest.java b/android/net/wifi/rtt/RangingRequest.java
index 997b6800..a396281f 100644
--- a/android/net/wifi/rtt/RangingRequest.java
+++ b/android/net/wifi/rtt/RangingRequest.java
@@ -17,13 +17,22 @@
package android.net.wifi.rtt;
import android.net.wifi.ScanResult;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareManager;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import libcore.util.HexEncoding;
+
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.StringJoiner;
/**
@@ -33,8 +42,8 @@ import java.util.StringJoiner;
* {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
* <p>
* The ranging request is a batch request - specifying a set of devices (specified using
- * {@link RangingRequest.Builder#addAp(ScanResult)} and
- * {@link RangingRequest.Builder#addAps(List)}).
+ * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and
+ * {@link RangingRequest.Builder#addAccessPoints(List)}).
*
* @hide RTT_API
*/
@@ -44,8 +53,8 @@ public final class RangingRequest implements Parcelable {
/**
* Returns the maximum number of peers to range which can be specified in a single {@code
* RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
- * through {@link RangingRequest.Builder#addAp(ScanResult)} or
- * {@link RangingRequest.Builder#addAps(List)}.
+ * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or
+ * {@link RangingRequest.Builder#addAccessPoints(List)}.
*
* @return Maximum number of peers.
*/
@@ -94,11 +103,34 @@ public final class RangingRequest implements Parcelable {
}
/** @hide */
- public void enforceValidity() {
+ public void enforceValidity(boolean awareSupported) {
if (mRttPeers.size() > MAX_PEERS) {
throw new IllegalArgumentException(
"Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
}
+
+ for (RttPeer peer: mRttPeers) {
+ if (peer instanceof RttPeerAp) {
+ RttPeerAp apPeer = (RttPeerAp) peer;
+ if (apPeer.scanResult == null || apPeer.scanResult.BSSID == null) {
+ throw new IllegalArgumentException("Invalid AP peer specification");
+ }
+ } else if (peer instanceof RttPeerAware) {
+ if (!awareSupported) {
+ throw new IllegalArgumentException(
+ "Request contains Aware peers - but Aware isn't supported on this "
+ + "device");
+ }
+
+ RttPeerAware awarePeer = (RttPeerAware) peer;
+ if (awarePeer.peerMacAddress == null && awarePeer.peerHandle == null) {
+ throw new IllegalArgumentException("Invalid Aware peer specification");
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Request contains unknown peer specification types");
+ }
+ }
}
/**
@@ -116,7 +148,7 @@ public final class RangingRequest implements Parcelable {
* @return The builder to facilitate chaining
* {@code builder.setXXX(..).setXXX(..)}.
*/
- public Builder addAp(ScanResult apInfo) {
+ public Builder addAccessPoint(ScanResult apInfo) {
if (apInfo == null) {
throw new IllegalArgumentException("Null ScanResult!");
}
@@ -133,17 +165,55 @@ public final class RangingRequest implements Parcelable {
* @return The builder to facilitate chaining
* {@code builder.setXXX(..).setXXX(..)}.
*/
- public Builder addAps(List<ScanResult> apInfos) {
+ public Builder addAccessPoints(List<ScanResult> apInfos) {
if (apInfos == null) {
throw new IllegalArgumentException("Null list of ScanResults!");
}
for (ScanResult scanResult : apInfos) {
- addAp(scanResult);
+ addAccessPoint(scanResult);
}
return this;
}
/**
+ * Add the device specified by the {@code peerMacAddress} to the list of devices with
+ * which to measure range.
+ *
+ * The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi
+ * Aware device may obtain its MAC address using the {@link IdentityChangedListener}
+ * provided to
+ * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}.
+ *
+ * * Note: in order to use this API the device must support Wi-Fi Aware
+ * {@link android.net.wifi.aware}.
+ *
+ * @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
+ * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addWifiAwarePeer(byte[] peerMacAddress) {
+ mRttPeers.add(new RttPeerAware(peerMacAddress));
+ return this;
+ }
+
+ /**
+ * Add a device specified by a {@link PeerHandle} to the list of devices with which to
+ * measure range.
+ *
+ * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
+ * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
+ *
+ * Note: in order to use this API the device must support Wi-Fi Aware
+ * {@link android.net.wifi.aware}.
+ *
+ * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
+ * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addWifiAwarePeer(PeerHandle peerHandle) {
+ mRttPeers.add(new RttPeerAware(peerHandle));
+ return this;
+ }
+
+ /**
* Build {@link RangingRequest} given the current configurations made on the
* builder.
*/
@@ -234,4 +304,89 @@ public final class RangingRequest implements Parcelable {
return scanResult.hashCode();
}
}
-} \ No newline at end of file
+
+ /** @hide */
+ public static class RttPeerAware implements RttPeer, Parcelable {
+ public PeerHandle peerHandle;
+ public byte[] peerMacAddress;
+
+ public RttPeerAware(PeerHandle peerHandle) {
+ if (peerHandle == null) {
+ throw new IllegalArgumentException("Null peerHandle");
+ }
+ this.peerHandle = peerHandle;
+ peerMacAddress = null;
+ }
+
+ public RttPeerAware(byte[] peerMacAddress) {
+ if (peerMacAddress == null) {
+ throw new IllegalArgumentException("Null peerMacAddress");
+ }
+
+ this.peerMacAddress = peerMacAddress;
+ peerHandle = null;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (peerHandle == null) {
+ dest.writeBoolean(false);
+ dest.writeByteArray(peerMacAddress);
+ } else {
+ dest.writeBoolean(true);
+ dest.writeInt(peerHandle.peerId);
+ }
+ }
+
+ public static final Creator<RttPeerAware> CREATOR = new Creator<RttPeerAware>() {
+ @Override
+ public RttPeerAware[] newArray(int size) {
+ return new RttPeerAware[size];
+ }
+
+ @Override
+ public RttPeerAware createFromParcel(Parcel in) {
+ boolean peerHandleAvail = in.readBoolean();
+ if (peerHandleAvail) {
+ return new RttPeerAware(new PeerHandle(in.readInt()));
+ } else {
+ return new RttPeerAware(in.createByteArray());
+ }
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RttPeerAware: peerHandle=").append(
+ peerHandle == null ? "<null>" : Integer.toString(peerHandle.peerId)).append(
+ ", peerMacAddress=").append(peerMacAddress == null ? "<null>"
+ : new String(HexEncoding.encode(peerMacAddress))).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RttPeerAware)) {
+ return false;
+ }
+
+ RttPeerAware lhs = (RttPeerAware) o;
+
+ return Objects.equals(peerHandle, lhs.peerHandle) && Arrays.equals(peerMacAddress,
+ lhs.peerMacAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(peerHandle.peerId, peerMacAddress);
+ }
+ }
+}
diff --git a/android/net/wifi/rtt/RangingResult.java b/android/net/wifi/rtt/RangingResult.java
index 918803ef..93e52aeb 100644
--- a/android/net/wifi/rtt/RangingResult.java
+++ b/android/net/wifi/rtt/RangingResult.java
@@ -16,13 +16,16 @@
package android.net.wifi.rtt;
+import android.annotation.IntDef;
+import android.net.wifi.aware.PeerHandle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
import libcore.util.HexEncoding;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -40,28 +43,61 @@ import java.util.Objects;
public final class RangingResult implements Parcelable {
private static final String TAG = "RangingResult";
+ /** @hide */
+ @IntDef({STATUS_SUCCESS, STATUS_FAIL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangeResultStatus {
+ }
+
+ /**
+ * Individual range request status, {@link #getStatus()}. Indicates ranging operation was
+ * successful and distance value is valid.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
+ * and the distance value is invalid.
+ */
+ public static final int STATUS_FAIL = 1;
+
private final int mStatus;
private final byte[] mMac;
- private final int mDistanceCm;
- private final int mDistanceStdDevCm;
+ private final PeerHandle mPeerHandle;
+ private final int mDistanceMm;
+ private final int mDistanceStdDevMm;
private final int mRssi;
private final long mTimestamp;
/** @hide */
- public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
- long timestamp) {
+ public RangingResult(@RangeResultStatus int status, byte[] mac, int distanceMm,
+ int distanceStdDevMm, int rssi, long timestamp) {
mStatus = status;
mMac = mac;
- mDistanceCm = distanceCm;
- mDistanceStdDevCm = distanceStdDevCm;
+ mPeerHandle = null;
+ mDistanceMm = distanceMm;
+ mDistanceStdDevMm = distanceStdDevMm;
+ mRssi = rssi;
+ mTimestamp = timestamp;
+ }
+
+ /** @hide */
+ public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
+ int distanceStdDevMm, int rssi, long timestamp) {
+ mStatus = status;
+ mMac = null;
+ mPeerHandle = peerHandle;
+ mDistanceMm = distanceMm;
+ mDistanceStdDevMm = distanceStdDevMm;
mRssi = rssi;
mTimestamp = timestamp;
}
/**
- * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in
- * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure.
+ * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
+ * {@link #STATUS_FAIL} in case of failure.
*/
+ @RangeResultStatus
public int getStatus() {
return mStatus;
}
@@ -70,57 +106,80 @@ public final class RangingResult implements Parcelable {
* @return The MAC address of the device whose range measurement was requested. Will correspond
* to the MAC address of the device in the {@link RangingRequest}.
* <p>
- * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL.
+ * Will return a {@code null} for results corresponding to requests issued using a {@code
+ * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
*/
public byte[] getMacAddress() {
return mMac;
}
/**
- * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+ * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
+ * to the PeerHandle of the devices requested using
+ * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Will return a {@code null} for results corresponding to requests issued using a MAC address.
*/
- public int getDistanceCm() {
- if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
- Log.e(TAG, "getDistanceCm(): invalid value retrieved");
+ public PeerHandle getPeerHandle() {
+ return mPeerHandle;
+ }
+
+ /**
+ * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
+ * {@link #getPeerHandle()}.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public int getDistanceMm() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
}
- return mDistanceCm;
+ return mDistanceMm;
}
/**
- * @return The standard deviation of the measured distance (in cm) to the device specified by
- * {@link #getMacAddress()}. The standard deviation is calculated over the measurements
- * executed in a single RTT burst.
+ * @return The standard deviation of the measured distance (in mm) to the device specified by
+ * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
+ * over the measurements executed in a single RTT burst.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
*/
- public int getDistanceStdDevCm() {
- if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
- Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+ public int getDistanceStdDevMm() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
}
- return mDistanceStdDevCm;
+ return mDistanceStdDevMm;
}
/**
* @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
*/
public int getRssi() {
- if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
- // TODO: should this be an exception?
- Log.e(TAG, "getRssi(): invalid value retrieved");
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
}
return mRssi;
}
/**
- * @return The timestamp (in us) at which the ranging operation was performed
+ * @return The timestamp, in us since boot, at which the ranging operation was performed.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
*/
- public long getRangingTimestamp() {
+ public long getRangingTimestampUs() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getRangingTimestamp(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
return mTimestamp;
}
@@ -135,8 +194,14 @@ public final class RangingResult implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mStatus);
dest.writeByteArray(mMac);
- dest.writeInt(mDistanceCm);
- dest.writeInt(mDistanceStdDevCm);
+ if (mPeerHandle == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ dest.writeInt(mPeerHandle.peerId);
+ }
+ dest.writeInt(mDistanceMm);
+ dest.writeInt(mDistanceStdDevMm);
dest.writeInt(mRssi);
dest.writeLong(mTimestamp);
}
@@ -152,11 +217,22 @@ public final class RangingResult implements Parcelable {
public RangingResult createFromParcel(Parcel in) {
int status = in.readInt();
byte[] mac = in.createByteArray();
- int distanceCm = in.readInt();
- int distanceStdDevCm = in.readInt();
+ boolean peerHandlePresent = in.readBoolean();
+ PeerHandle peerHandle = null;
+ if (peerHandlePresent) {
+ peerHandle = new PeerHandle(in.readInt());
+ }
+ int distanceMm = in.readInt();
+ int distanceStdDevMm = in.readInt();
int rssi = in.readInt();
long timestamp = in.readLong();
- return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+ if (peerHandlePresent) {
+ return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
+ timestamp);
+ } else {
+ return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
+ timestamp);
+ }
}
};
@@ -164,9 +240,10 @@ public final class RangingResult implements Parcelable {
@Override
public String toString() {
return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
- mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append(
- ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append(
- mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+ mMac == null ? "<null>" : new String(HexEncoding.encodeToString(mMac))).append(
+ ", peerHandle=").append(mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(
+ ", distanceMm=").append(mDistanceMm).append(", distanceStdDevMm=").append(
+ mDistanceStdDevMm).append(", rssi=").append(mRssi).append(", timestamp=").append(
mTimestamp).append("]").toString();
}
@@ -182,13 +259,15 @@ public final class RangingResult implements Parcelable {
RangingResult lhs = (RangingResult) o;
- return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac)
- && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm
- && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp;
+ return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) && Objects.equals(
+ mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
+ && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
+ && mTimestamp == lhs.mTimestamp;
}
@Override
public int hashCode() {
- return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+ return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
+ mTimestamp);
}
-} \ No newline at end of file
+}
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
index d7270ad2..7405e82e 100644
--- a/android/net/wifi/rtt/RangingResultCallback.java
+++ b/android/net/wifi/rtt/RangingResultCallback.java
@@ -16,35 +16,43 @@
package android.net.wifi.rtt;
+import android.annotation.IntDef;
import android.os.Handler;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Base class for ranging result callbacks. Should be extended by applications and set when calling
- * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single
- * result from a range request will be called in this object.
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. If the
+ * ranging operation fails in whole (not attempted) then {@link #onRangingFailure(int)} will be
+ * called with a failure code. If the ranging operation is performed for each of the requested
+ * peers then the {@link #onRangingResults(List)} will be called with the set of results (@link
+ * {@link RangingResult}, each of which has its own success/failure code
+ * {@link RangingResult#getStatus()}.
*
* @hide RTT_API
*/
public abstract class RangingResultCallback {
- /**
- * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
- * operation was successful and distance value is valid.
- */
- public static final int STATUS_SUCCESS = 0;
+ /** @hide */
+ @IntDef({STATUS_CODE_FAIL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangingOperationStatus {
+ }
/**
- * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
- * operation failed and the distance value is invalid.
+ * A failure code for the whole ranging request operation. Indicates a failure.
*/
- public static final int STATUS_FAIL = 1;
+ public static final int STATUS_CODE_FAIL = 1;
/**
* Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
* devices specified in the request was attempted.
+ *
+ * @param code A status code indicating the type of failure.
*/
- public abstract void onRangingFailure();
+ public abstract void onRangingFailure(@RangingOperationStatus int code);
/**
* Called when a ranging operation was executed. The list of results corresponds to devices
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
index a085de17..435bb377 100644
--- a/android/net/wifi/rtt/WifiRttManager.java
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -83,17 +83,18 @@ public class WifiRttManager {
}
@Override
- public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
- if (VDBG) {
- Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
- + results);
- }
+ public void onRangingFailure(int status) throws RemoteException {
+ if (VDBG) Log.v(TAG, "RttCallbackProxy: onRangingFailure: status=" + status);
mHandler.post(() -> {
- if (status == RangingResultCallback.STATUS_SUCCESS) {
- mCallback.onRangingResults(results);
- } else {
- mCallback.onRangingFailure();
- }
+ mCallback.onRangingFailure(status);
+ });
+ }
+
+ @Override
+ public void onRangingResults(List<RangingResult> results) throws RemoteException {
+ if (VDBG) Log.v(TAG, "RttCallbackProxy: onRanginResults: results=" + results);
+ mHandler.post(() -> {
+ mCallback.onRangingResults(results);
});
}
}
diff --git a/android/os/BatteryProperty.java b/android/os/BatteryProperty.java
index 84119bdc..b7e7b177 100644
--- a/android/os/BatteryProperty.java
+++ b/android/os/BatteryProperty.java
@@ -43,6 +43,13 @@ public class BatteryProperty implements Parcelable {
return mValueLong;
}
+ /**
+ * @hide
+ */
+ public void setLong(long val) {
+ mValueLong = val;
+ }
+
/*
* Parcel read/write code must be kept in sync with
* frameworks/native/services/batteryservice/BatteryProperty.cpp
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 98819279..8682c01e 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -447,8 +447,7 @@ public abstract class BatteryStats implements Parcelable {
/**
* Returns the max duration if it is being tracked.
- * Not all Timer subclasses track the max, total, current durations.
-
+ * Not all Timer subclasses track the max, total, and current durations.
*/
public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
return -1;
@@ -456,14 +455,14 @@ public abstract class BatteryStats implements Parcelable {
/**
* Returns the current time the timer has been active, if it is being tracked.
- * Not all Timer subclasses track the max, total, current durations.
+ * Not all Timer subclasses track the max, total, and current durations.
*/
public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
return -1;
}
/**
- * Returns the current time the timer has been active, if it is being tracked.
+ * Returns the total time the timer has been active, if it is being tracked.
*
* Returns the total cumulative duration (i.e. sum of past durations) that this timer has
* been on since reset.
@@ -471,7 +470,7 @@ public abstract class BatteryStats implements Parcelable {
* depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
* time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
* the actual total time.
- * Not all Timer subclasses track the max, total, current durations.
+ * Not all Timer subclasses track the max, total, and current durations.
*/
public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
return -1;
@@ -600,9 +599,17 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
public abstract int getWifiScanCount(int which);
+ /**
+ * Returns the timer keeping track of wifi scans.
+ */
+ public abstract Timer getWifiScanTimer();
public abstract int getWifiScanBackgroundCount(int which);
public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
+ /**
+ * Returns the timer keeping track of background wifi scans.
+ */
+ public abstract Timer getWifiScanBackgroundTimer();
public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
public abstract int getWifiBatchedScanCount(int csphBin, int which);
public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -1911,6 +1918,13 @@ public abstract class BatteryStats implements Parcelable {
long elapsedRealtimeUs, int which);
/**
+ * Returns the {@link Timer} object that tracks the given screen brightness.
+ *
+ * {@hide}
+ */
+ public abstract Timer getScreenBrightnessTimer(int brightnessBin);
+
+ /**
* Returns the time in microseconds that power save mode has been enabled while the device was
* running on battery.
*
@@ -2019,6 +2033,14 @@ public abstract class BatteryStats implements Parcelable {
long elapsedRealtimeUs, int which);
/**
+ * Returns the {@link Timer} object that tracks how much the phone has been trying to
+ * acquire a signal.
+ *
+ * {@hide}
+ */
+ public abstract Timer getPhoneSignalScanningTimer();
+
+ /**
* Returns the number of times the phone has entered the given signal strength.
*
* {@hide}
@@ -2026,6 +2048,12 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getPhoneSignalStrengthCount(int strengthBin, int which);
/**
+ * Return the {@link Timer} object used to track the given signal strength's duration and
+ * counts.
+ */
+ protected abstract Timer getPhoneSignalStrengthTimer(int strengthBin);
+
+ /**
* Returns the time in microseconds that the mobile network has been active
* (in a high power state).
*
@@ -2108,6 +2136,11 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract int getPhoneDataConnectionCount(int dataType, int which);
+ /**
+ * Returns the {@link Timer} object that tracks the phone's data connection type stats.
+ */
+ public abstract Timer getPhoneDataConnectionTimer(int dataType);
+
public static final int WIFI_SUPPL_STATE_INVALID = 0;
public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
@@ -2267,6 +2300,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getWifiStateCount(int wifiState, int which);
/**
+ * Returns the {@link Timer} object that tracks the given WiFi state.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiStateTimer(int wifiState);
+
+ /**
* Returns the time in microseconds that the wifi supplicant has been
* in a given state.
*
@@ -2282,6 +2322,13 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract int getWifiSupplStateCount(int state, int which);
+ /**
+ * Returns the {@link Timer} object that tracks the given wifi supplicant state.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiSupplStateTimer(int state);
+
public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5;
/**
@@ -2301,6 +2348,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getWifiSignalStrengthCount(int strengthBin, int which);
/**
+ * Returns the {@link Timer} object that tracks the given WIFI signal strength.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiSignalStrengthTimer(int strengthBin);
+
+ /**
* Returns the time in microseconds that the flashlight has been on while the device was
* running on battery.
*
@@ -2487,13 +2541,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getDischargeAmountScreenOffSinceCharge();
/**
- * Get the amount the battery has discharged while the screen was doze,
+ * Get the amount the battery has discharged while the screen was dozing,
* since the last time power was unplugged.
*/
public abstract int getDischargeAmountScreenDoze();
/**
- * Get the amount the battery has discharged while the screen was doze,
+ * Get the amount the battery has discharged while the screen was dozing,
* since the last time the device was charged.
*/
public abstract int getDischargeAmountScreenDozeSinceCharge();
@@ -2626,20 +2680,20 @@ public abstract class BatteryStats implements Parcelable {
* micro-Ampere-hours. This will be non-zero only if the device's battery has
* a coulomb counter.
*/
- public abstract long getMahDischargeScreenOff(int which);
+ public abstract long getUahDischargeScreenOff(int which);
/**
* Return the amount of battery discharge while the screen was in doze mode, measured in
* micro-Ampere-hours. This will be non-zero only if the device's battery has
* a coulomb counter.
*/
- public abstract long getMahDischargeScreenDoze(int which);
+ public abstract long getUahDischargeScreenDoze(int which);
/**
* Return the amount of battery discharge measured in micro-Ampere-hours. This will be
* non-zero only if the device's battery has a coulomb counter.
*/
- public abstract long getMahDischarge(int which);
+ public abstract long getUahDischarge(int which);
/**
* Returns the estimated real battery capacity, which may be less than the capacity
@@ -2777,6 +2831,10 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ private static long roundUsToMs(long timeUs) {
+ return (timeUs + 500) / 1000;
+ }
+
private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) {
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
@@ -2981,10 +3039,9 @@ public abstract class BatteryStats implements Parcelable {
Timer timer, long rawRealtime, int which) {
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
- / 1000;
+ final long totalTime = roundUsToMs(timer.getTotalTimeLocked(rawRealtime, which));
final int count = timer.getCountLocked(which);
- if (totalTime != 0) {
+ if (totalTime != 0 || count != 0) {
dumpLine(pw, uid, category, type, totalTime, count);
}
}
@@ -3000,17 +3057,31 @@ public abstract class BatteryStats implements Parcelable {
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
*/
private static void dumpTimer(ProtoOutputStream proto, long fieldId,
- Timer timer, long rawRealtime, int which) {
+ Timer timer, long rawRealtimeUs, int which) {
if (timer == null) {
return;
}
// Convert from microseconds to milliseconds with rounding
- final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ final long timeMs = roundUsToMs(timer.getTotalTimeLocked(rawRealtimeUs, which));
final int count = timer.getCountLocked(which);
- if (totalTimeMs != 0 || count != 0) {
+ final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs / 1000);
+ final long curDurationMs = timer.getCurrentDurationMsLocked(rawRealtimeUs / 1000);
+ final long totalDurationMs = timer.getTotalDurationMsLocked(rawRealtimeUs / 1000);
+ if (timeMs != 0 || count != 0 || maxDurationMs != -1 || curDurationMs != -1
+ || totalDurationMs != -1) {
final long token = proto.start(fieldId);
- proto.write(TimerProto.DURATION_MS, totalTimeMs);
+ proto.write(TimerProto.DURATION_MS, timeMs);
proto.write(TimerProto.COUNT, count);
+ // These values will be -1 for timers that don't implement the functionality.
+ if (maxDurationMs != -1) {
+ proto.write(TimerProto.MAX_DURATION_MS, maxDurationMs);
+ }
+ if (curDurationMs != -1) {
+ proto.write(TimerProto.CURRENT_DURATION_MS, curDurationMs);
+ }
+ if (totalDurationMs != -1) {
+ proto.write(TimerProto.TOTAL_DURATION_MS, totalDurationMs);
+ }
proto.end(token);
}
}
@@ -3114,71 +3185,104 @@ public abstract class BatteryStats implements Parcelable {
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
final long powerDrainMaMs = counter.getPowerCounter().getCountLocked(which);
+ // Battery real time
+ final long totalControllerActivityTimeMs
+ = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
long totalTxTimeMs = 0;
for (LongCounter txState : counter.getTxTimeCounters()) {
totalTxTimeMs += txState.getCountLocked(which);
}
+ final long sleepTimeMs
+ = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs);
- final long totalTimeMs = idleTimeMs + rxTimeMs + totalTxTimeMs;
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(controllerName);
+ sb.append(" Sleep time: ");
+ formatTimeMs(sb, sleepTimeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(sleepTimeMs, totalControllerActivityTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" ");
+ sb.append(" ");
sb.append(controllerName);
sb.append(" Idle time: ");
formatTimeMs(sb, idleTimeMs);
sb.append("(");
- sb.append(formatRatioLocked(idleTimeMs, totalTimeMs));
+ sb.append(formatRatioLocked(idleTimeMs, totalControllerActivityTimeMs));
sb.append(")");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" ");
+ sb.append(" ");
sb.append(controllerName);
sb.append(" Rx time: ");
formatTimeMs(sb, rxTimeMs);
sb.append("(");
- sb.append(formatRatioLocked(rxTimeMs, totalTimeMs));
+ sb.append(formatRatioLocked(rxTimeMs, totalControllerActivityTimeMs));
sb.append(")");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" ");
+ sb.append(" ");
sb.append(controllerName);
sb.append(" Tx time: ");
- formatTimeMs(sb, totalTxTimeMs);
- sb.append("(");
- sb.append(formatRatioLocked(totalTxTimeMs, totalTimeMs));
- sb.append(")");
- pw.println(sb.toString());
- final int numTxLvls = counter.getTxTimeCounters().length;
+ String [] powerLevel;
+ switch(controllerName) {
+ case "Cellular":
+ powerLevel = new String[] {
+ " less than 0dBm: ",
+ " 0dBm to 8dBm: ",
+ " 8dBm to 15dBm: ",
+ " 15dBm to 20dBm: ",
+ " above 20dBm: "};
+ break;
+ default:
+ powerLevel = new String[] {"[0]", "[1]", "[2]", "[3]", "[4]"};
+ break;
+ }
+ final int numTxLvls = Math.min(counter.getTxTimeCounters().length, powerLevel.length);
if (numTxLvls > 1) {
+ pw.println(sb.toString());
for (int lvl = 0; lvl < numTxLvls; lvl++) {
final long txLvlTimeMs = counter.getTxTimeCounters()[lvl].getCountLocked(which);
sb.setLength(0);
sb.append(prefix);
- sb.append(" [");
- sb.append(lvl);
- sb.append("] ");
+ sb.append(" ");
+ sb.append(powerLevel[lvl]);
+ sb.append(" ");
formatTimeMs(sb, txLvlTimeMs);
sb.append("(");
- sb.append(formatRatioLocked(txLvlTimeMs, totalTxTimeMs));
+ sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
sb.append(")");
pw.println(sb.toString());
}
+ } else {
+ final long txLvlTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
+ formatTimeMs(sb, txLvlTimeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
}
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" ");
- sb.append(controllerName);
- sb.append(" Power drain: ").append(
+ if (powerDrainMaMs > 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(controllerName);
+ sb.append(" Battery drain: ").append(
BatteryStatsHelper.makemAh(powerDrainMaMs / (double) (1000*60*60)));
- sb.append("mAh");
- pw.println(sb.toString());
+ sb.append("mAh");
+ pw.println(sb.toString());
+ }
}
/**
@@ -3191,13 +3295,13 @@ public abstract class BatteryStats implements Parcelable {
/**
* Checkin server version of dump to produce more compact, computer-readable log.
*
- * NOTE: all times are expressed in 'ms'.
+ * NOTE: all times are expressed in microseconds, unless specified otherwise.
*/
public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid,
boolean wifiOnly) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
- final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
- final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ final long rawRealtimeMs = SystemClock.elapsedRealtime();
+ final long rawRealtime = rawRealtimeMs * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
@@ -3220,9 +3324,9 @@ public abstract class BatteryStats implements Parcelable {
rawRealtime, which);
final int connChanges = getNumConnectivityChange(which);
final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
- final long dischargeCount = getMahDischarge(which);
- final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
- final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+ final long dischargeCount = getUahDischarge(which);
+ final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
+ final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
final StringBuilder sb = new StringBuilder(128);
@@ -3460,9 +3564,9 @@ public abstract class BatteryStats implements Parcelable {
BatteryStatsHelper.makemAh(helper.getComputedPower()),
BatteryStatsHelper.makemAh(helper.getMinDrainedPower()),
BatteryStatsHelper.makemAh(helper.getMaxDrainedPower()));
+ int uid = 0;
for (int i=0; i<sippers.size(); i++) {
final BatterySipper bs = sippers.get(i);
- int uid = 0;
String label;
switch (bs.drainType) {
case IDLE:
@@ -3503,6 +3607,9 @@ public abstract class BatteryStats implements Parcelable {
case CAMERA:
label = "camera";
break;
+ case MEMORY:
+ label = "memory";
+ break;
default:
label = "???";
}
@@ -3523,6 +3630,7 @@ public abstract class BatteryStats implements Parcelable {
dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
}
+ // Dump stats per UID.
for (int iu = 0; iu < NU; iu++) {
final int uid = uidStats.keyAt(iu);
if (reqUid >= 0 && uid != reqUid) {
@@ -3686,7 +3794,7 @@ public abstract class BatteryStats implements Parcelable {
linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW),
rawRealtime, "w", which, linePrefix);
- // Only log if we had at lease one wakelock...
+ // Only log if we had at least one wakelock...
if (sb.length() > 0) {
String name = wakelocks.keyAt(iw);
if (name.indexOf(',') >= 0) {
@@ -4020,7 +4128,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeCount = getMahDischarge(which);
+ final long dischargeCount = getUahDischarge(which);
if (dischargeCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4030,7 +4138,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
+ final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
if (dischargeScreenOffCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4040,7 +4148,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+ final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
if (dischargeScreenDozeCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4246,51 +4354,50 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ pw.println("");
pw.print(prefix);
- pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotalBytes));
- pw.print(", sent: "); pw.print(formatBytesLocked(mobileTxTotalBytes));
- pw.print(" (packets received "); pw.print(mobileRxTotalPackets);
- pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")");
sb.setLength(0);
sb.append(prefix);
- sb.append(" Phone signal levels:");
- didOne = false;
- for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
- if (time == 0) {
- continue;
- }
- sb.append("\n ");
- sb.append(prefix);
- didOne = true;
- sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]);
- sb.append(" ");
- formatTimeMs(sb, time/1000);
- sb.append("(");
- sb.append(formatRatioLocked(time, whichBatteryRealtime));
- sb.append(") ");
- sb.append(getPhoneSignalStrengthCount(i, which));
- sb.append("x");
- }
- if (!didOne) sb.append(" (no activity)");
+ sb.append(" CONNECTIVITY POWER SUMMARY START");
pw.println(sb.toString());
+ pw.print(prefix);
sb.setLength(0);
sb.append(prefix);
- sb.append(" Signal scanning time: ");
- formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000);
+ sb.append(" Logging duration for connectivity statistics: ");
+ formatTimeMs(sb, whichBatteryRealtime / 1000);
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Radio types:");
+ sb.append(" Cellular Statistics:");
+ pw.println(sb.toString());
+
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Cellular kernel active time: ");
+ final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
+ formatTimeMs(sb, mobileActiveTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
+ sb.append(")");
+ pw.println(sb.toString());
+
+ pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
+ pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
+ pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets);
+ pw.print(" Cellular packets sent: "); pw.println(mobileTxTotalPackets);
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Cellular Radio Access Technology:");
didOne = false;
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
final long time = getPhoneDataConnectionTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- sb.append("\n ");
+ sb.append("\n ");
sb.append(prefix);
didOne = true;
sb.append(DATA_CONNECTION_NAMES[i]);
@@ -4299,73 +4406,64 @@ public abstract class BatteryStats implements Parcelable {
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getPhoneDataConnectionCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Mobile radio active time: ");
- final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
- formatTimeMs(sb, mobileActiveTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
- sb.append(") "); sb.append(getMobileRadioActiveCount(which));
- sb.append("x");
- pw.println(sb.toString());
-
- final long mobileActiveUnknownTime = getMobileRadioActiveUnknownTime(which);
- if (mobileActiveUnknownTime != 0) {
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Mobile radio active unknown time: ");
- formatTimeMs(sb, mobileActiveUnknownTime / 1000);
- sb.append("(");
- sb.append(formatRatioLocked(mobileActiveUnknownTime, whichBatteryRealtime));
- sb.append(") "); sb.append(getMobileRadioActiveUnknownCount(which));
- sb.append("x");
- pw.println(sb.toString());
- }
-
- final long mobileActiveAdjustedTime = getMobileRadioActiveAdjustedTime(which);
- if (mobileActiveAdjustedTime != 0) {
- sb.setLength(0);
+ sb.append(" Cellular Rx signal strength (RSRP):");
+ final String[] cellularRxSignalStrengthDescription = new String[]{
+ "very poor (less than -128dBm): ",
+ "poor (-128dBm to -118dBm): ",
+ "moderate (-118dBm to -108dBm): ",
+ "good (-108dBm to -98dBm): ",
+ "great (greater than -98dBm): "};
+ didOne = false;
+ final int numCellularRxBins = Math.min(SignalStrength.NUM_SIGNAL_STRENGTH_BINS,
+ cellularRxSignalStrengthDescription.length);
+ for (int i=0; i<numCellularRxBins; i++) {
+ final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
+ if (time == 0) {
+ continue;
+ }
+ sb.append("\n ");
sb.append(prefix);
- sb.append(" Mobile radio active adjusted time: ");
- formatTimeMs(sb, mobileActiveAdjustedTime / 1000);
+ didOne = true;
+ sb.append(cellularRxSignalStrengthDescription[i]);
+ sb.append(" ");
+ formatTimeMs(sb, time/1000);
sb.append("(");
- sb.append(formatRatioLocked(mobileActiveAdjustedTime, whichBatteryRealtime));
- sb.append(")");
- pw.println(sb.toString());
+ sb.append(formatRatioLocked(time, whichBatteryRealtime));
+ sb.append(") ");
}
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
- printControllerActivity(pw, sb, prefix, "Radio", getModemControllerActivity(), which);
+ printControllerActivity(pw, sb, prefix, "Cellular",
+ getModemControllerActivity(), which);
pw.print(prefix);
- pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotalBytes));
- pw.print(", sent: "); pw.print(formatBytesLocked(wifiTxTotalBytes));
- pw.print(" (packets received "); pw.print(wifiRxTotalPackets);
- pw.print(", sent "); pw.print(wifiTxTotalPackets); pw.println(")");
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(wifiOnTime, whichBatteryRealtime));
- sb.append("), Wifi running: "); formatTimeMs(sb, wifiRunningTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(wifiRunningTime, whichBatteryRealtime));
- sb.append(")");
+ sb.append(" Wifi Statistics:");
pw.println(sb.toString());
+ pw.print(" Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes));
+ pw.print(" Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes));
+ pw.print(" Wifi packets received: "); pw.println(wifiRxTotalPackets);
+ pw.print(" Wifi packets sent: "); pw.println(wifiTxTotalPackets);
+
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi states:");
+ sb.append(" Wifi states:");
didOne = false;
for (int i=0; i<NUM_WIFI_STATES; i++) {
final long time = getWifiStateTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- sb.append("\n ");
+ sb.append("\n ");
didOne = true;
sb.append(WIFI_STATE_NAMES[i]);
sb.append(" ");
@@ -4373,22 +4471,20 @@ public abstract class BatteryStats implements Parcelable {
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getWifiStateCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi supplicant states:");
+ sb.append(" Wifi supplicant states:");
didOne = false;
for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
final long time = getWifiSupplStateTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- sb.append("\n ");
+ sb.append("\n ");
didOne = true;
sb.append(WIFI_SUPPL_STATE_NAMES[i]);
sb.append(" ");
@@ -4396,17 +4492,23 @@ public abstract class BatteryStats implements Parcelable {
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getWifiSupplStateCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi signal levels:");
+ sb.append(" Wifi Rx signal strength (RSSI):");
+ final String[] wifiRxSignalStrengthDescription = new String[]{
+ "very poor (less than -88.75dBm): ",
+ "poor (-88.75 to -77.5dBm): ",
+ "moderate (-77.5dBm to -66.25dBm): ",
+ "good (-66.25dBm to -55dBm): ",
+ "great (greater than -55dBm): "};
didOne = false;
- for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+ final int numWifiRxBins = Math.min(NUM_WIFI_SIGNAL_STRENGTH_BINS,
+ wifiRxSignalStrengthDescription.length);
+ for (int i=0; i<numWifiRxBins; i++) {
final long time = getWifiSignalStrengthTime(i, rawRealtime, which);
if (time == 0) {
continue;
@@ -4414,15 +4516,12 @@ public abstract class BatteryStats implements Parcelable {
sb.append("\n ");
sb.append(prefix);
didOne = true;
- sb.append("level(");
- sb.append(i);
- sb.append(") ");
+ sb.append(" ");
+ sb.append(wifiRxSignalStrengthDescription[i]);
formatTimeMs(sb, time/1000);
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getWifiSignalStrengthCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
@@ -4430,6 +4529,13 @@ public abstract class BatteryStats implements Parcelable {
printControllerActivity(pw, sb, prefix, "WiFi", getWifiControllerActivity(), which);
pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" CONNECTIVITY POWER SUMMARY END");
+ pw.println(sb.toString());
+ pw.println("");
+
+ pw.print(prefix);
pw.print(" Bluetooth total received: "); pw.print(formatBytesLocked(btRxTotalBytes));
pw.print(", sent: "); pw.println(formatBytesLocked(btTxTotalBytes));
@@ -6038,6 +6144,60 @@ public abstract class BatteryStats implements Parcelable {
return true;
}
+ private static void dumpDurationSteps(ProtoOutputStream proto, long fieldId,
+ LevelStepTracker steps) {
+ if (steps == null) {
+ return;
+ }
+ int count = steps.mNumStepDurations;
+ for (int i = 0; i < count; ++i) {
+ long token = proto.start(fieldId);
+ proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
+ proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
+
+ final long initMode = steps.getInitModeAt(i);
+ final long modMode = steps.getModModeAt(i);
+
+ int ds = SystemProto.BatteryLevelStep.DS_MIXED;
+ if ((modMode & STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+ switch ((int) (initMode & STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+ case Display.STATE_OFF:
+ ds = SystemProto.BatteryLevelStep.DS_OFF;
+ break;
+ case Display.STATE_ON:
+ ds = SystemProto.BatteryLevelStep.DS_ON;
+ break;
+ case Display.STATE_DOZE:
+ ds = SystemProto.BatteryLevelStep.DS_DOZE;
+ break;
+ case Display.STATE_DOZE_SUSPEND:
+ ds = SystemProto.BatteryLevelStep.DS_DOZE_SUSPEND;
+ break;
+ default:
+ ds = SystemProto.BatteryLevelStep.DS_ERROR;
+ break;
+ }
+ }
+ proto.write(SystemProto.BatteryLevelStep.DISPLAY_STATE, ds);
+
+ int psm = SystemProto.BatteryLevelStep.PSM_MIXED;
+ if ((modMode & STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+ psm = (initMode & STEP_LEVEL_MODE_POWER_SAVE) != 0
+ ? SystemProto.BatteryLevelStep.PSM_ON : SystemProto.BatteryLevelStep.PSM_OFF;
+ }
+ proto.write(SystemProto.BatteryLevelStep.POWER_SAVE_MODE, psm);
+
+ int im = SystemProto.BatteryLevelStep.IM_MIXED;
+ if ((modMode & STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
+ im = (initMode & STEP_LEVEL_MODE_DEVICE_IDLE) != 0
+ ? SystemProto.BatteryLevelStep.IM_ON : SystemProto.BatteryLevelStep.IM_OFF;
+ }
+ proto.write(SystemProto.BatteryLevelStep.IDLE_MODE, im);
+
+ proto.end(token);
+ }
+ }
+
public static final int DUMP_CHARGED_ONLY = 1<<1;
public static final int DUMP_DAILY_ONLY = 1<<2;
public static final int DUMP_HISTORY_ONLY = 1<<3;
@@ -6463,7 +6623,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- /** Dump batterystats data to a proto. @hide */
+ /** Dump #STATS_SINCE_CHARGED batterystats data to a proto. @hide */
public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
int flags, long historyStart) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
@@ -6484,11 +6644,802 @@ public abstract class BatteryStats implements Parcelable {
}
if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
- // TODO: implement dumpProtoAppsLocked(proto, apps);
- // TODO: implement dumpProtoSystemLocked(proto);
+ final BatteryStatsHelper helper = new BatteryStatsHelper(context, false,
+ (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+ helper.create(this);
+ helper.refreshStats(STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+
+ dumpProtoAppsLocked(proto, helper, apps);
+ dumpProtoSystemLocked(proto, helper);
}
proto.end(bToken);
proto.flush();
}
+
+ private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryStatsHelper helper,
+ List<ApplicationInfo> apps) {
+ final int which = STATS_SINCE_CHARGED;
+ final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtimeMs = SystemClock.elapsedRealtime();
+ final long rawRealtimeUs = rawRealtimeMs * 1000;
+ final long batteryUptimeUs = getBatteryUptime(rawUptimeUs);
+
+ SparseArray<ArrayList<String>> aidToPackages = new SparseArray<>();
+ if (apps != null) {
+ for (int i = 0; i < apps.size(); ++i) {
+ ApplicationInfo ai = apps.get(i);
+ int aid = UserHandle.getAppId(ai.uid);
+ ArrayList<String> pkgs = aidToPackages.get(aid);
+ if (pkgs == null) {
+ pkgs = new ArrayList<String>();
+ aidToPackages.put(aid, pkgs);
+ }
+ pkgs.add(ai.packageName);
+ }
+ }
+
+ SparseArray<BatterySipper> uidToSipper = new SparseArray<>();
+ final List<BatterySipper> sippers = helper.getUsageList();
+ if (sippers != null) {
+ for (int i = 0; i < sippers.size(); ++i) {
+ final BatterySipper bs = sippers.get(i);
+ if (bs.drainType != BatterySipper.DrainType.APP) {
+ // Others are handled by dumpProtoSystemLocked()
+ continue;
+ }
+ uidToSipper.put(bs.uidObj.getUid(), bs);
+ }
+ }
+
+ SparseArray<? extends Uid> uidStats = getUidStats();
+ final int n = uidStats.size();
+ for (int iu = 0; iu < n; ++iu) {
+ final long uTkn = proto.start(BatteryStatsProto.UIDS);
+ final Uid u = uidStats.valueAt(iu);
+
+ final int uid = uidStats.keyAt(iu);
+ proto.write(UidProto.UID, uid);
+
+ // Print packages and apk stats (UID_DATA & APK_DATA)
+ ArrayList<String> pkgs = aidToPackages.get(UserHandle.getAppId(uid));
+ if (pkgs == null) {
+ pkgs = new ArrayList<String>();
+ }
+ final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats =
+ u.getPackageStats();
+ for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+ String pkg = packageStats.keyAt(ipkg);
+ final ArrayMap<String, ? extends Uid.Pkg.Serv> serviceStats =
+ packageStats.valueAt(ipkg).getServiceStats();
+ if (serviceStats.size() == 0) {
+ // Due to the way ActivityManagerService logs wakeup alarms, some packages (for
+ // example, "android") may be included in the packageStats that aren't part of
+ // the UID. If they don't have any services, then they shouldn't be listed here.
+ // These packages won't be a part in the pkgs List.
+ continue;
+ }
+
+ final long pToken = proto.start(UidProto.PACKAGES);
+ proto.write(UidProto.Package.NAME, pkg);
+ // Remove from the packages list since we're logging it here.
+ pkgs.remove(pkg);
+
+ for (int isvc = serviceStats.size() - 1; isvc >= 0; --isvc) {
+ final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+ long sToken = proto.start(UidProto.Package.SERVICES);
+
+ proto.write(UidProto.Package.Service.NAME, serviceStats.keyAt(isvc));
+ proto.write(UidProto.Package.Service.START_DURATION_MS,
+ roundUsToMs(ss.getStartTime(batteryUptimeUs, which)));
+ proto.write(UidProto.Package.Service.START_COUNT, ss.getStarts(which));
+ proto.write(UidProto.Package.Service.LAUNCH_COUNT, ss.getLaunches(which));
+
+ proto.end(sToken);
+ }
+ proto.end(pToken);
+ }
+ // Print any remaining packages that weren't in the packageStats map. pkgs is pulled
+ // from PackageManager data. Packages are only included in packageStats if there was
+ // specific data tracked for them (services and wakeup alarms, etc.).
+ for (String p : pkgs) {
+ final long pToken = proto.start(UidProto.PACKAGES);
+ proto.write(UidProto.Package.NAME, p);
+ proto.end(pToken);
+ }
+
+ // Total wakelock data (AGGREGATED_WAKELOCK_DATA)
+ if (u.getAggregatedPartialWakelockTimer() != null) {
+ final Timer timer = u.getAggregatedPartialWakelockTimer();
+ // Times are since reset (regardless of 'which')
+ final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
+ final Timer bgTimer = timer.getSubTimer();
+ final long bgTimeMs = bgTimer != null
+ ? bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ final long awToken = proto.start(UidProto.AGGREGATED_WAKELOCK);
+ proto.write(UidProto.AggregatedWakelock.PARTIAL_DURATION_MS, totTimeMs);
+ proto.write(UidProto.AggregatedWakelock.BACKGROUND_PARTIAL_DURATION_MS, bgTimeMs);
+ proto.end(awToken);
+ }
+
+ // Audio (AUDIO_DATA)
+ dumpTimer(proto, UidProto.AUDIO, u.getAudioTurnedOnTimer(), rawRealtimeUs, which);
+
+ // Bluetooth Controller (BLUETOOTH_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, UidProto.BLUETOOTH_CONTROLLER,
+ u.getBluetoothControllerActivity(), which);
+
+ // BLE scans (BLUETOOTH_MISC_DATA) (uses totalDurationMsLocked and MaxDurationMsLocked)
+ final Timer bleTimer = u.getBluetoothScanTimer();
+ if (bleTimer != null) {
+ final long bmToken = proto.start(UidProto.BLUETOOTH_MISC);
+
+ dumpTimer(proto, UidProto.BluetoothMisc.APPORTIONED_BLE_SCAN, bleTimer,
+ rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN,
+ u.getBluetoothScanBackgroundTimer(), rawRealtimeUs, which);
+ // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+ dumpTimer(proto, UidProto.BluetoothMisc.UNOPTIMIZED_BLE_SCAN,
+ u.getBluetoothUnoptimizedScanTimer(), rawRealtimeUs, which);
+ // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+ dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_UNOPTIMIZED_BLE_SCAN,
+ u.getBluetoothUnoptimizedScanBackgroundTimer(), rawRealtimeUs, which);
+ // Result counters
+ proto.write(UidProto.BluetoothMisc.BLE_SCAN_RESULT_COUNT,
+ u.getBluetoothScanResultCounter() != null
+ ? u.getBluetoothScanResultCounter().getCountLocked(which) : 0);
+ proto.write(UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN_RESULT_COUNT,
+ u.getBluetoothScanResultBgCounter() != null
+ ? u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0);
+
+ proto.end(bmToken);
+ }
+
+ // Camera (CAMERA_DATA)
+ dumpTimer(proto, UidProto.CAMERA, u.getCameraTurnedOnTimer(), rawRealtimeUs, which);
+
+ // CPU stats (CPU_DATA & CPU_TIMES_AT_FREQ_DATA)
+ final long cpuToken = proto.start(UidProto.CPU);
+ proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
+ proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));
+
+ final long[] cpuFreqs = getCpuFreqs();
+ if (cpuFreqs != null) {
+ final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+ // If total cpuFreqTimes is null, then we don't need to check for
+ // screenOffCpuFreqTimes.
+ if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+ long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+ if (screenOffCpuFreqTimeMs == null) {
+ screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+ }
+ for (int ic = 0; ic < cpuFreqTimeMs.length; ++ic) {
+ long cToken = proto.start(UidProto.Cpu.BY_FREQUENCY);
+ proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+ proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+ cpuFreqTimeMs[ic]);
+ proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+ screenOffCpuFreqTimeMs[ic]);
+ proto.end(cToken);
+ }
+ }
+ }
+ proto.end(cpuToken);
+
+ // Flashlight (FLASHLIGHT_DATA)
+ dumpTimer(proto, UidProto.FLASHLIGHT, u.getFlashlightTurnedOnTimer(),
+ rawRealtimeUs, which);
+
+ // Foreground activity (FOREGROUND_ACTIVITY_DATA)
+ dumpTimer(proto, UidProto.FOREGROUND_ACTIVITY, u.getForegroundActivityTimer(),
+ rawRealtimeUs, which);
+
+ // Foreground service (FOREGROUND_SERVICE_DATA)
+ dumpTimer(proto, UidProto.FOREGROUND_SERVICE, u.getForegroundServiceTimer(),
+ rawRealtimeUs, which);
+
+ // Job completion (JOB_COMPLETION_DATA)
+ final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+ final int[] reasons = new int[]{
+ JobParameters.REASON_CANCELED,
+ JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+ JobParameters.REASON_PREEMPT,
+ JobParameters.REASON_TIMEOUT,
+ JobParameters.REASON_DEVICE_IDLE,
+ };
+ for (int ic = 0; ic < completions.size(); ++ic) {
+ SparseIntArray types = completions.valueAt(ic);
+ if (types != null) {
+ final long jcToken = proto.start(UidProto.JOB_COMPLETION);
+
+ proto.write(UidProto.JobCompletion.NAME, completions.keyAt(ic));
+
+ for (int r : reasons) {
+ long rToken = proto.start(UidProto.JobCompletion.REASON_COUNT);
+ proto.write(UidProto.JobCompletion.ReasonCount.NAME, r);
+ proto.write(UidProto.JobCompletion.ReasonCount.COUNT, types.get(r, 0));
+ proto.end(rToken);
+ }
+
+ proto.end(jcToken);
+ }
+ }
+
+ // Scheduled jobs (JOB_DATA)
+ final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+ for (int ij = jobs.size() - 1; ij >= 0; --ij) {
+ final Timer timer = jobs.valueAt(ij);
+ final Timer bgTimer = timer.getSubTimer();
+ final long jToken = proto.start(UidProto.JOBS);
+
+ proto.write(UidProto.Job.NAME, jobs.keyAt(ij));
+ // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+ dumpTimer(proto, UidProto.Job.TOTAL, timer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Job.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+ proto.end(jToken);
+ }
+
+ // Modem Controller (MODEM_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, UidProto.MODEM_CONTROLLER,
+ u.getModemControllerActivity(), which);
+
+ // Network stats (NETWORK_DATA)
+ final long nToken = proto.start(UidProto.NETWORK);
+ proto.write(UidProto.Network.MOBILE_BYTES_RX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_BYTES_TX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_RX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_TX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+ proto.write(UidProto.Network.BT_BYTES_RX,
+ u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+ proto.write(UidProto.Network.BT_BYTES_TX,
+ u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_RX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_TX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_RX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_TX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_ACTIVE_DURATION_MS,
+ roundUsToMs(u.getMobileRadioActiveTime(which)));
+ proto.write(UidProto.Network.MOBILE_ACTIVE_COUNT,
+ u.getMobileRadioActiveCount(which));
+ proto.write(UidProto.Network.MOBILE_WAKEUP_COUNT,
+ u.getMobileRadioApWakeupCount(which));
+ proto.write(UidProto.Network.WIFI_WAKEUP_COUNT,
+ u.getWifiRadioApWakeupCount(which));
+ proto.write(UidProto.Network.MOBILE_BYTES_BG_RX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_BG_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_BYTES_BG_TX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_BG_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_BG_RX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_BG_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_BG_TX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_BG_TX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_BG_RX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_BG_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_BG_TX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_BG_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_BG_RX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_BG_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_BG_TX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_BG_TX_DATA, which));
+ proto.end(nToken);
+
+ // Power use item (POWER_USE_ITEM_DATA)
+ BatterySipper bs = uidToSipper.get(uid);
+ if (bs != null) {
+ final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
+ proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+ proto.write(UidProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+ proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+ proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+ bs.proportionalSmearMah);
+ proto.end(bsToken);
+ }
+
+ // Processes (PROCESS_DATA)
+ final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats =
+ u.getProcessStats();
+ for (int ipr = processStats.size() - 1; ipr >= 0; --ipr) {
+ final Uid.Proc ps = processStats.valueAt(ipr);
+ final long prToken = proto.start(UidProto.PROCESS);
+
+ proto.write(UidProto.Process.NAME, processStats.keyAt(ipr));
+ proto.write(UidProto.Process.USER_DURATION_MS, ps.getUserTime(which));
+ proto.write(UidProto.Process.SYSTEM_DURATION_MS, ps.getSystemTime(which));
+ proto.write(UidProto.Process.FOREGROUND_DURATION_MS, ps.getForegroundTime(which));
+ proto.write(UidProto.Process.START_COUNT, ps.getStarts(which));
+ proto.write(UidProto.Process.ANR_COUNT, ps.getNumAnrs(which));
+ proto.write(UidProto.Process.CRASH_COUNT, ps.getNumCrashes(which));
+
+ proto.end(prToken);
+ }
+
+ // Sensors (SENSOR_DATA)
+ final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+ for (int ise = 0; ise < sensors.size(); ++ise) {
+ final Uid.Sensor se = sensors.valueAt(ise);
+ final Timer timer = se.getSensorTime();
+ if (timer == null) {
+ continue;
+ }
+ final Timer bgTimer = se.getSensorBackgroundTime();
+ final int sensorNumber = sensors.keyAt(ise);
+ final long seToken = proto.start(UidProto.SENSORS);
+
+ proto.write(UidProto.Sensor.ID, sensorNumber);
+ // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+ dumpTimer(proto, UidProto.Sensor.APPORTIONED, timer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Sensor.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+ proto.end(seToken);
+ }
+
+ // State times (STATE_TIME_DATA)
+ for (int ips = 0; ips < Uid.NUM_PROCESS_STATE; ++ips) {
+ long durMs = roundUsToMs(u.getProcessStateTime(ips, rawRealtimeUs, which));
+ if (durMs == 0) {
+ continue;
+ }
+ final long stToken = proto.start(UidProto.STATES);
+ proto.write(UidProto.StateTime.STATE, ips);
+ proto.write(UidProto.StateTime.DURATION_MS, durMs);
+ proto.end(stToken);
+ }
+
+ // Syncs (SYNC_DATA)
+ final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+ for (int isy = syncs.size() - 1; isy >= 0; --isy) {
+ final Timer timer = syncs.valueAt(isy);
+ final Timer bgTimer = timer.getSubTimer();
+ final long syToken = proto.start(UidProto.SYNCS);
+
+ proto.write(UidProto.Sync.NAME, syncs.keyAt(isy));
+ // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+ dumpTimer(proto, UidProto.Sync.TOTAL, timer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Sync.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+ proto.end(syToken);
+ }
+
+ // User activity (USER_ACTIVITY_DATA)
+ if (u.hasUserActivity()) {
+ for (int i = 0; i < Uid.NUM_USER_ACTIVITY_TYPES; ++i) {
+ int val = u.getUserActivityCount(i, which);
+ if (val != 0) {
+ final long uaToken = proto.start(UidProto.USER_ACTIVITY);
+ proto.write(UidProto.UserActivity.NAME, i);
+ proto.write(UidProto.UserActivity.COUNT, val);
+ proto.end(uaToken);
+ }
+ }
+ }
+
+ // Vibrator (VIBRATOR_DATA)
+ dumpTimer(proto, UidProto.VIBRATOR, u.getVibratorOnTimer(), rawRealtimeUs, which);
+
+ // Video (VIDEO_DATA)
+ dumpTimer(proto, UidProto.VIDEO, u.getVideoTurnedOnTimer(), rawRealtimeUs, which);
+
+ // Wakelocks (WAKELOCK_DATA)
+ final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
+ for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+ final Uid.Wakelock wl = wakelocks.valueAt(iw);
+ final long wToken = proto.start(UidProto.WAKELOCKS);
+ proto.write(UidProto.Wakelock.NAME, wakelocks.keyAt(iw));
+ dumpTimer(proto, UidProto.Wakelock.FULL, wl.getWakeTime(WAKE_TYPE_FULL),
+ rawRealtimeUs, which);
+ final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+ if (pTimer != null) {
+ dumpTimer(proto, UidProto.Wakelock.PARTIAL, pTimer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Wakelock.BACKGROUND_PARTIAL, pTimer.getSubTimer(),
+ rawRealtimeUs, which);
+ }
+ dumpTimer(proto, UidProto.Wakelock.WINDOW, wl.getWakeTime(WAKE_TYPE_WINDOW),
+ rawRealtimeUs, which);
+ proto.end(wToken);
+ }
+
+ // Wakeup alarms (WAKEUP_ALARM_DATA)
+ for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+ final Uid.Pkg ps = packageStats.valueAt(ipkg);
+ final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+ for (int iwa = alarms.size() - 1; iwa >= 0; --iwa) {
+ final long waToken = proto.start(UidProto.WAKEUP_ALARM);
+ proto.write(UidProto.WakeupAlarm.NAME, alarms.keyAt(iwa));
+ proto.write(UidProto.WakeupAlarm.COUNT,
+ alarms.valueAt(iwa).getCountLocked(which));
+ proto.end(waToken);
+ }
+ }
+
+ // Wifi Controller (WIFI_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, UidProto.WIFI_CONTROLLER,
+ u.getWifiControllerActivity(), which);
+
+ // Wifi data (WIFI_DATA)
+ final long wToken = proto.start(UidProto.WIFI);
+ proto.write(UidProto.Wifi.FULL_WIFI_LOCK_DURATION_MS,
+ roundUsToMs(u.getFullWifiLockTime(rawRealtimeUs, which)));
+ dumpTimer(proto, UidProto.Wifi.APPORTIONED_SCAN, u.getWifiScanTimer(),
+ rawRealtimeUs, which);
+ proto.write(UidProto.Wifi.RUNNING_DURATION_MS,
+ roundUsToMs(u.getWifiRunningTime(rawRealtimeUs, which)));
+ dumpTimer(proto, UidProto.Wifi.BACKGROUND_SCAN, u.getWifiScanBackgroundTimer(),
+ rawRealtimeUs, which);
+ proto.end(wToken);
+
+ proto.end(uTkn);
+ }
+ }
+
+ private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) {
+ final long sToken = proto.start(BatteryStatsProto.SYSTEM);
+ final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtimeMs = SystemClock.elapsedRealtime();
+ final long rawRealtimeUs = rawRealtimeMs * 1000;
+ final int which = STATS_SINCE_CHARGED;
+
+ // Battery data (BATTERY_DATA)
+ final long bToken = proto.start(SystemProto.BATTERY);
+ proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
+ proto.write(SystemProto.Battery.START_COUNT, getStartCount());
+ proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
+ computeRealtime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.TOTAL_UPTIME_MS,
+ computeUptime(rawUptimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.BATTERY_REALTIME_MS,
+ computeBatteryRealtime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.BATTERY_UPTIME_MS,
+ computeBatteryUptime(rawUptimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.SCREEN_OFF_REALTIME_MS,
+ computeBatteryScreenOffRealtime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.SCREEN_OFF_UPTIME_MS,
+ computeBatteryScreenOffUptime(rawUptimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.SCREEN_DOZE_DURATION_MS,
+ getScreenDozeTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.ESTIMATED_BATTERY_CAPACITY_MAH,
+ getEstimatedBatteryCapacity());
+ proto.write(SystemProto.Battery.MIN_LEARNED_BATTERY_CAPACITY_UAH,
+ getMinLearnedBatteryCapacity());
+ proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
+ getMaxLearnedBatteryCapacity());
+ proto.end(bToken);
+
+ // Battery discharge (BATTERY_DISCHARGE_DATA)
+ final long bdToken = proto.start(SystemProto.BATTERY_DISCHARGE);
+ proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
+ getLowDischargeAmountSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
+ getHighDischargeAmountSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.SCREEN_ON_SINCE_CHARGE,
+ getDischargeAmountScreenOnSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.SCREEN_OFF_SINCE_CHARGE,
+ getDischargeAmountScreenOffSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.SCREEN_DOZE_SINCE_CHARGE,
+ getDischargeAmountScreenDozeSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH,
+ getUahDischarge(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_OFF,
+ getUahDischargeScreenOff(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
+ getUahDischargeScreenDoze(which) / 1000);
+ proto.end(bdToken);
+
+ // Time remaining
+ long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
+ // These are part of a oneof, so we should only set one of them.
+ if (timeRemainingUs >= 0) {
+ // Charge time remaining (CHARGE_TIME_REMAIN_DATA)
+ proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+ } else {
+ timeRemainingUs = computeBatteryTimeRemaining(rawRealtimeUs);
+ // Discharge time remaining (DISCHARGE_TIME_REMAIN_DATA)
+ if (timeRemainingUs >= 0) {
+ proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+ } else {
+ proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, -1);
+ }
+ }
+
+ // Charge step (CHARGE_STEP_DATA)
+ dumpDurationSteps(proto, SystemProto.CHARGE_STEP, getChargeLevelStepTracker());
+
+ // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
+ for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
+ final long pdcToken = proto.start(SystemProto.DATA_CONNECTION);
+ proto.write(SystemProto.DataConnection.NAME, i);
+ dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
+ rawRealtimeUs, which);
+ proto.end(pdcToken);
+ }
+
+ // Discharge step (DISCHARGE_STEP_DATA)
+ dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());
+
+ // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
+ final long[] cpuFreqs = getCpuFreqs();
+ if (cpuFreqs != null) {
+ for (long i : cpuFreqs) {
+ proto.write(SystemProto.CPU_FREQUENCY, i);
+ }
+ }
+
+ // Bluetooth controller (GLOBAL_BLUETOOTH_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, SystemProto.GLOBAL_BLUETOOTH_CONTROLLER,
+ getBluetoothControllerActivity(), which);
+
+ // Modem controller (GLOBAL_MODEM_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, SystemProto.GLOBAL_MODEM_CONTROLLER,
+ getModemControllerActivity(), which);
+
+ // Global network data (GLOBAL_NETWORK_DATA)
+ final long gnToken = proto.start(SystemProto.GLOBAL_NETWORK);
+ proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
+ getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
+ getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_RX,
+ getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_TX,
+ getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_RX,
+ getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_TX,
+ getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_RX,
+ getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_TX,
+ getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.BT_BYTES_RX,
+ getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
+ getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+ proto.end(gnToken);
+
+ // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
+ getWifiControllerActivity(), which);
+
+
+ // Global wifi (GLOBAL_WIFI_DATA)
+ final long gwToken = proto.start(SystemProto.GLOBAL_WIFI);
+ proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
+ getWifiOnTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
+ getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
+ proto.end(gwToken);
+
+ // Kernel wakelock (KERNEL_WAKELOCK_DATA)
+ final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
+ for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
+ final long kwToken = proto.start(SystemProto.KERNEL_WAKELOCK);
+ proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
+ dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
+ rawRealtimeUs, which);
+ proto.end(kwToken);
+ }
+
+ // Misc (MISC_DATA)
+ // Calculate wakelock times across all uids.
+ long fullWakeLockTimeTotalUs = 0;
+ long partialWakeLockTimeTotalUs = 0;
+
+ final SparseArray<? extends Uid> uidStats = getUidStats();
+ for (int iu = 0; iu < uidStats.size(); iu++) {
+ final Uid u = uidStats.valueAt(iu);
+
+ final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks =
+ u.getWakelockStats();
+ for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+ final Uid.Wakelock wl = wakelocks.valueAt(iw);
+
+ final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
+ if (fullWakeTimer != null) {
+ fullWakeLockTimeTotalUs += fullWakeTimer.getTotalTimeLocked(rawRealtimeUs,
+ which);
+ }
+
+ final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+ if (partialWakeTimer != null) {
+ partialWakeLockTimeTotalUs += partialWakeTimer.getTotalTimeLocked(
+ rawRealtimeUs, which);
+ }
+ }
+ }
+ final long mToken = proto.start(SystemProto.MISC);
+ proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
+ getScreenOnTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
+ getPhoneOnTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.FULL_WAKELOCK_TOTAL_DURATION_MS,
+ fullWakeLockTimeTotalUs / 1000);
+ proto.write(SystemProto.Misc.PARTIAL_WAKELOCK_TOTAL_DURATION_MS,
+ partialWakeLockTimeTotalUs / 1000);
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_DURATION_MS,
+ getMobileRadioActiveTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_ADJUSTED_TIME_MS,
+ getMobileRadioActiveAdjustedTime(which) / 1000);
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_COUNT,
+ getMobileRadioActiveCount(which));
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_UNKNOWN_DURATION_MS,
+ getMobileRadioActiveUnknownTime(which) / 1000);
+ proto.write(SystemProto.Misc.INTERACTIVE_DURATION_MS,
+ getInteractiveTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.BATTERY_SAVER_MODE_ENABLED_DURATION_MS,
+ getPowerSaveModeEnabledTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.NUM_CONNECTIVITY_CHANGES,
+ getNumConnectivityChange(which));
+ proto.write(SystemProto.Misc.DEEP_DOZE_ENABLED_DURATION_MS,
+ getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.DEEP_DOZE_COUNT,
+ getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which));
+ proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_DURATION_MS,
+ getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_COUNT,
+ getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which));
+ proto.write(SystemProto.Misc.LONGEST_DEEP_DOZE_DURATION_MS,
+ getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
+ proto.write(SystemProto.Misc.LIGHT_DOZE_ENABLED_DURATION_MS,
+ getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.LIGHT_DOZE_COUNT,
+ getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
+ proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_DURATION_MS,
+ getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_COUNT,
+ getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
+ proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
+ getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
+ proto.end(mToken);
+
+ // Power use item (POWER_USE_ITEM_DATA)
+ final List<BatterySipper> sippers = helper.getUsageList();
+ if (sippers != null) {
+ for (int i = 0; i < sippers.size(); ++i) {
+ final BatterySipper bs = sippers.get(i);
+ int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
+ int uid = 0;
+ switch (bs.drainType) {
+ case IDLE:
+ n = SystemProto.PowerUseItem.IDLE;
+ break;
+ case CELL:
+ n = SystemProto.PowerUseItem.CELL;
+ break;
+ case PHONE:
+ n = SystemProto.PowerUseItem.PHONE;
+ break;
+ case WIFI:
+ n = SystemProto.PowerUseItem.WIFI;
+ break;
+ case BLUETOOTH:
+ n = SystemProto.PowerUseItem.BLUETOOTH;
+ break;
+ case SCREEN:
+ n = SystemProto.PowerUseItem.SCREEN;
+ break;
+ case FLASHLIGHT:
+ n = SystemProto.PowerUseItem.FLASHLIGHT;
+ break;
+ case APP:
+ // dumpProtoAppsLocked will handle this.
+ continue;
+ case USER:
+ n = SystemProto.PowerUseItem.USER;
+ uid = UserHandle.getUid(bs.userId, 0);
+ break;
+ case UNACCOUNTED:
+ n = SystemProto.PowerUseItem.UNACCOUNTED;
+ break;
+ case OVERCOUNTED:
+ n = SystemProto.PowerUseItem.OVERCOUNTED;
+ break;
+ case CAMERA:
+ n = SystemProto.PowerUseItem.CAMERA;
+ break;
+ case MEMORY:
+ n = SystemProto.PowerUseItem.MEMORY;
+ break;
+ }
+ final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
+ proto.write(SystemProto.PowerUseItem.NAME, n);
+ proto.write(SystemProto.PowerUseItem.UID, uid);
+ proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+ proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+ proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+ proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+ bs.proportionalSmearMah);
+ proto.end(puiToken);
+ }
+ }
+
+ // Power use summary (POWER_USE_SUMMARY_DATA)
+ final long pusToken = proto.start(SystemProto.POWER_USE_SUMMARY);
+ proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
+ helper.getPowerProfile().getBatteryCapacity());
+ proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
+ proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
+ proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
+ proto.end(pusToken);
+
+ // RPM stats (RESOURCE_POWER_MANAGER_DATA)
+ final Map<String, ? extends Timer> rpmStats = getRpmStats();
+ final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+ for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+ final long rpmToken = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
+ proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
+ dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
+ ent.getValue(), rawRealtimeUs, which);
+ dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
+ screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
+ proto.end(rpmToken);
+ }
+
+ // Screen brightness (SCREEN_BRIGHTNESS_DATA)
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
+ final long sbToken = proto.start(SystemProto.SCREEN_BRIGHTNESS);
+ proto.write(SystemProto.ScreenBrightness.NAME, i);
+ dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
+ rawRealtimeUs, which);
+ proto.end(sbToken);
+ }
+
+ // Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
+ dumpTimer(proto, SystemProto.SIGNAL_SCANNING, getPhoneSignalScanningTimer(), rawRealtimeUs,
+ which);
+
+ // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
+ for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
+ final long pssToken = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
+ proto.write(SystemProto.PhoneSignalStrength.NAME, i);
+ dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
+ rawRealtimeUs, which);
+ proto.end(pssToken);
+ }
+
+ // Wakeup reasons (WAKEUP_REASON_DATA)
+ final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
+ for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
+ final long wrToken = proto.start(SystemProto.WAKEUP_REASON);
+ proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
+ dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
+ proto.end(wrToken);
+ }
+
+ // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
+ for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
+ final long wssToken = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
+ proto.write(SystemProto.WifiSignalStrength.NAME, i);
+ dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
+ rawRealtimeUs, which);
+ proto.end(wssToken);
+ }
+
+ // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
+ for (int i = 0; i < NUM_WIFI_STATES; ++i) {
+ final long wsToken = proto.start(SystemProto.WIFI_STATE);
+ proto.write(SystemProto.WifiState.NAME, i);
+ dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
+ rawRealtimeUs, which);
+ proto.end(wsToken);
+ }
+
+ // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
+ for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
+ final long wssToken = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
+ proto.write(SystemProto.WifiSupplicantState.NAME, i);
+ dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
+ rawRealtimeUs, which);
+ proto.end(wssToken);
+ }
+
+ proto.end(sToken);
+ }
}
diff --git a/android/os/Debug.java b/android/os/Debug.java
index b46c6b16..017c2134 100644
--- a/android/os/Debug.java
+++ b/android/os/Debug.java
@@ -1748,22 +1748,26 @@ public final class Debug
public static final int MEMINFO_SHMEM = 4;
/** @hide */
public static final int MEMINFO_SLAB = 5;
+ /** @hide */
+ public static final int MEMINFO_SLAB_RECLAIMABLE = 6;
+ /** @hide */
+ public static final int MEMINFO_SLAB_UNRECLAIMABLE = 7;
/** @hide */
- public static final int MEMINFO_SWAP_TOTAL = 6;
+ public static final int MEMINFO_SWAP_TOTAL = 8;
/** @hide */
- public static final int MEMINFO_SWAP_FREE = 7;
+ public static final int MEMINFO_SWAP_FREE = 9;
/** @hide */
- public static final int MEMINFO_ZRAM_TOTAL = 8;
+ public static final int MEMINFO_ZRAM_TOTAL = 10;
/** @hide */
- public static final int MEMINFO_MAPPED = 9;
+ public static final int MEMINFO_MAPPED = 11;
/** @hide */
- public static final int MEMINFO_VM_ALLOC_USED = 10;
+ public static final int MEMINFO_VM_ALLOC_USED = 12;
/** @hide */
- public static final int MEMINFO_PAGE_TABLES = 11;
+ public static final int MEMINFO_PAGE_TABLES = 13;
/** @hide */
- public static final int MEMINFO_KERNEL_STACK = 12;
+ public static final int MEMINFO_KERNEL_STACK = 14;
/** @hide */
- public static final int MEMINFO_COUNT = 13;
+ public static final int MEMINFO_COUNT = 15;
/**
* Retrieves /proc/meminfo. outSizes is filled with fields
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
index c091420a..7f588adb 100644
--- a/android/os/ParcelFileDescriptor.java
+++ b/android/os/ParcelFileDescriptor.java
@@ -737,7 +737,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
private void closeWithStatus(int status, String msg) {
if (mClosed) return;
mClosed = true;
- mGuard.close();
+ if (mGuard != null) {
+ mGuard.close();
+ }
// Status MUST be sent before closing actual descriptor
writeCommStatusAndClose(status, msg);
IoUtils.closeQuietly(mFd);
diff --git a/android/os/PatternMatcher.java b/android/os/PatternMatcher.java
index 1f3a1e68..76b21426 100644
--- a/android/os/PatternMatcher.java
+++ b/android/os/PatternMatcher.java
@@ -16,7 +16,7 @@
package android.os;
-import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import java.util.Arrays;
@@ -131,7 +131,17 @@ public class PatternMatcher implements Parcelable {
}
return "PatternMatcher{" + type + mPattern + "}";
}
-
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(PatternMatcherProto.PATTERN, mPattern);
+ proto.write(PatternMatcherProto.TYPE, mType);
+ // PatternMatcherProto.PARSED_PATTERN is too much to dump, but the field is reserved to
+ // match the current data structure.
+ proto.end(token);
+ }
+
public int describeContents() {
return 0;
}
@@ -141,7 +151,7 @@ public class PatternMatcher implements Parcelable {
dest.writeInt(mType);
dest.writeIntArray(mParsedPattern);
}
-
+
public PatternMatcher(Parcel src) {
mPattern = src.readString();
mType = src.readInt();
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index f41848fa..34c78455 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,29 +16,9 @@
package android.os;
-import android.util.Log;
-
-import com.android.internal.os.BinderInternal;
-
-import java.util.HashMap;
import java.util.Map;
-/** @hide */
public final class ServiceManager {
- private static final String TAG = "ServiceManager";
- private static IServiceManager sServiceManager;
- private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
-
- private static IServiceManager getIServiceManager() {
- if (sServiceManager != null) {
- return sServiceManager;
- }
-
- // Find the service manager
- sServiceManager = ServiceManagerNative
- .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
- return sServiceManager;
- }
/**
* Returns a reference to a service with the given name.
@@ -47,32 +27,14 @@ public final class ServiceManager {
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
public static IBinder getService(String name) {
- try {
- IBinder service = sCache.get(name);
- if (service != null) {
- return service;
- } else {
- return Binder.allowBlocking(getIServiceManager().getService(name));
- }
- } catch (RemoteException e) {
- Log.e(TAG, "error in getService", e);
- }
return null;
}
/**
- * Returns a reference to a service with the given name, or throws
- * {@link NullPointerException} if none is found.
- *
- * @hide
+ * Is not supposed to return null, but that is fine for layoutlib.
*/
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
- final IBinder binder = getService(name);
- if (binder != null) {
- return binder;
- } else {
- throw new ServiceNotFoundException(name);
- }
+ throw new ServiceNotFoundException(name);
}
/**
@@ -83,39 +45,7 @@ public final class ServiceManager {
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- addService(name, service, false, IServiceManager.DUMP_PRIORITY_NORMAL);
- }
-
- /**
- * Place a new @a service called @a name into the service
- * manager.
- *
- * @param name the name of the new service
- * @param service the service object
- * @param allowIsolated set to true to allow isolated sandboxed processes
- * to access this service
- */
- public static void addService(String name, IBinder service, boolean allowIsolated) {
- addService(name, service, allowIsolated, IServiceManager.DUMP_PRIORITY_NORMAL);
- }
-
- /**
- * Place a new @a service called @a name into the service
- * manager.
- *
- * @param name the name of the new service
- * @param service the service object
- * @param allowIsolated set to true to allow isolated sandboxed processes
- * @param dumpPriority supported dump priority levels as a bitmask
- * to access this service
- */
- public static void addService(String name, IBinder service, boolean allowIsolated,
- int dumpPriority) {
- try {
- getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
- } catch (RemoteException e) {
- Log.e(TAG, "error in addService", e);
- }
+ // pass
}
/**
@@ -123,17 +53,7 @@ public final class ServiceManager {
* service manager. Non-blocking.
*/
public static IBinder checkService(String name) {
- try {
- IBinder service = sCache.get(name);
- if (service != null) {
- return service;
- } else {
- return Binder.allowBlocking(getIServiceManager().checkService(name));
- }
- } catch (RemoteException e) {
- Log.e(TAG, "error in checkService", e);
- return null;
- }
+ return null;
}
/**
@@ -142,12 +62,9 @@ public final class ServiceManager {
* case of an exception
*/
public static String[] listServices() {
- try {
- return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL);
- } catch (RemoteException e) {
- Log.e(TAG, "error in listServices", e);
- return null;
- }
+ // actual implementation returns null sometimes, so it's ok
+ // to return null instead of an empty list.
+ return null;
}
/**
@@ -159,10 +76,7 @@ public final class ServiceManager {
* @hide
*/
public static void initServiceCache(Map<String, IBinder> cache) {
- if (sCache.size() != 0) {
- throw new IllegalStateException("setServiceCache may only be called once");
- }
- sCache.putAll(cache);
+ // pass
}
/**
@@ -173,6 +87,7 @@ public final class ServiceManager {
* @hide
*/
public static class ServiceNotFoundException extends Exception {
+ // identical to the original implementation
public ServiceNotFoundException(String name) {
super("No service published for: " + name);
}
diff --git a/android/os/SomeService.java b/android/os/SomeService.java
new file mode 100644
index 00000000..bdfaa444
--- /dev/null
+++ b/android/os/SomeService.java
@@ -0,0 +1,42 @@
+package android.os;
+
+import android.app.Service;
+import android.content.Intent;
+import java.io.File;
+import java.io.IOException;
+
+/** Service in separate process available for calling over binder. */
+public class SomeService extends Service {
+
+ private File mTempFile;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ try {
+ mTempFile = File.createTempFile("foo", "bar");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final ISomeService.Stub mBinder =
+ new ISomeService.Stub() {
+ public void readDisk(int times) {
+ for (int i = 0; i < times; i++) {
+ mTempFile.exists();
+ }
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mTempFile.delete();
+ }
+}
diff --git a/android/os/StatsLogEventWrapper.java b/android/os/StatsLogEventWrapper.java
new file mode 100644
index 00000000..9491bec6
--- /dev/null
+++ b/android/os/StatsLogEventWrapper.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Wrapper class for sending data from Android OS to StatsD.
+ *
+ * @hide
+ */
+public final class StatsLogEventWrapper implements Parcelable {
+ private ByteArrayOutputStream mStorage = new ByteArrayOutputStream();
+
+ // Below are constants copied from log/log.h
+ private static final int EVENT_TYPE_INT = 0; /* int32_t */
+ private static final int EVENT_TYPE_LONG = 1; /* int64_t */
+ private static final int EVENT_TYPE_STRING = 2;
+ private static final int EVENT_TYPE_LIST = 3;
+ private static final int EVENT_TYPE_FLOAT = 4;
+
+ /**
+ * Creates a log_event that is binary-encoded as implemented in
+ * system/core/liblog/log_event_list.c; this allows us to use the same parsing logic in statsd
+ * for pushed and pulled data. The write* methods must be called in the same order as their
+ * field number. There is no checking that the correct number of write* methods is called.
+ * We also write an END_LIST character before beginning to write to parcel, but this END_LIST
+ * may be unnecessary.
+ *
+ * @param tag The integer representing the tag for this event.
+ * @param fields The number of fields specified in this event.
+ */
+ public StatsLogEventWrapper(int tag, int fields) {
+ // Write four bytes from tag, starting with least-significant bit.
+ write4Bytes(tag);
+ mStorage.write(EVENT_TYPE_LIST); // This is required to start the log entry.
+ mStorage.write(fields); // Indicate number of elements in this list.
+ }
+
+ /**
+ * Boilerplate for Parcel.
+ */
+ public static final Parcelable.Creator<StatsLogEventWrapper> CREATOR = new
+ Parcelable.Creator<StatsLogEventWrapper>() {
+ public StatsLogEventWrapper createFromParcel(Parcel in) {
+ return new StatsLogEventWrapper(in);
+ }
+
+ public StatsLogEventWrapper[] newArray(int size) {
+ return new StatsLogEventWrapper[size];
+ }
+ };
+
+ private void write4Bytes(int val) {
+ mStorage.write(val);
+ mStorage.write(val >>> 8);
+ mStorage.write(val >>> 16);
+ mStorage.write(val >>> 24);
+ }
+
+ private void write8Bytes(long val) {
+ write4Bytes((int) (val & 0xFFFFFFFF)); // keep the lowe 32-bits
+ write4Bytes((int) (val >>> 32)); // Write the high 32-bits.
+ }
+
+ /**
+ * Adds 32-bit integer to output.
+ */
+ public void writeInt(int val) {
+ mStorage.write(EVENT_TYPE_INT);
+ write4Bytes(val);
+ }
+
+ /**
+ * Adds 64-bit long to output.
+ */
+ public void writeLong(long val) {
+ mStorage.write(EVENT_TYPE_LONG);
+ write8Bytes(val);
+ }
+
+ /**
+ * Adds a 4-byte floating point value to output.
+ */
+ public void writeFloat(float val) {
+ int v = Float.floatToIntBits(val);
+ mStorage.write(EVENT_TYPE_FLOAT);
+ write4Bytes(v);
+ }
+
+ /**
+ * Adds a string to the output.
+ */
+ public void writeString(String val) {
+ mStorage.write(EVENT_TYPE_STRING);
+ write4Bytes(val.length());
+ byte[] bytes = val.getBytes(StandardCharsets.UTF_8);
+ mStorage.write(bytes, 0, bytes.length);
+ }
+
+ private StatsLogEventWrapper(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * Writes the stored fields to a byte array. Will first write a new-line character to denote
+ * END_LIST before writing contents to byte array.
+ */
+ public void writeToParcel(Parcel out, int flags) {
+ mStorage.write(10); // new-line character is same as END_LIST
+ out.writeByteArray(mStorage.toByteArray());
+ }
+
+ /**
+ * Not implemented.
+ */
+ public void readFromParcel(Parcel in) {
+ // Not needed since this java class is for sending to statsd only.
+ }
+
+ /**
+ * Boilerplate for Parcel.
+ */
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/os/StrictModeTest.java b/android/os/StrictModeTest.java
new file mode 100644
index 00000000..d973c204
--- /dev/null
+++ b/android/os/StrictModeTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR 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 android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import com.google.common.util.concurrent.SettableFuture;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class StrictModeTest {
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void timeVmViolation() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+ causeVmViolations(state);
+ }
+
+ @Test
+ public void timeVmViolationNoStrictMode() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ causeVmViolations(state);
+ }
+
+ private static void causeVmViolations(BenchmarkState state) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(Uri.parse("content://com.example/foobar"), "image/jpeg");
+ final Context context = InstrumentationRegistry.getTargetContext();
+ while (state.keepRunning()) {
+ context.startActivity(intent);
+ }
+ }
+
+ @Test
+ public void timeThreadViolation() throws IOException {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ causeThreadViolations(state);
+ }
+
+ @Test
+ public void timeThreadViolationNoStrictMode() throws IOException {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ causeThreadViolations(state);
+ }
+
+ private static void causeThreadViolations(BenchmarkState state) throws IOException {
+ final File test = File.createTempFile("foo", "bar");
+ while (state.keepRunning()) {
+ test.exists();
+ }
+ test.delete();
+ }
+
+ @Test
+ public void timeCrossBinderThreadViolation() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ causeCrossProcessThreadViolations(state);
+ }
+
+ @Test
+ public void timeCrossBinderThreadViolationNoStrictMode() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ causeCrossProcessThreadViolations(state);
+ }
+
+ private static void causeCrossProcessThreadViolations(BenchmarkState state)
+ throws ExecutionException, InterruptedException, RemoteException {
+ final Context context = InstrumentationRegistry.getTargetContext();
+
+ SettableFuture<IBinder> binder = SettableFuture.create();
+ ServiceConnection connection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ binder.set(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ binder.set(null);
+ }
+ };
+ context.bindService(
+ new Intent(context, SomeService.class), connection, Context.BIND_AUTO_CREATE);
+ ISomeService someService = ISomeService.Stub.asInterface(binder.get());
+ while (state.keepRunning()) {
+ // Violate strictmode heavily.
+ someService.readDisk(10);
+ }
+ context.unbindService(connection);
+ }
+}
diff --git a/android/os/SystemProperties.java b/android/os/SystemProperties.java
index 560b4b31..4f6d322b 100644
--- a/android/os/SystemProperties.java
+++ b/android/os/SystemProperties.java
@@ -84,9 +84,6 @@ public class SystemProperties {
/**
* Get the String value for the given {@code key}.
*
- * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
- * method will crash in native code.
- *
* @param key the key to lookup
* @return an empty string if the {@code key} isn't found
*/
@@ -99,9 +96,6 @@ public class SystemProperties {
/**
* Get the String value for the given {@code key}.
*
- * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
- * method will crash in native code.
- *
* @param key the key to lookup
* @param def the default value in case the property is not set or empty
* @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
@@ -163,7 +157,7 @@ public class SystemProperties {
* @throws IllegalArgumentException if the {@code val} exceeds 91 characters
*/
public static void set(@NonNull String key, @Nullable String val) {
- if (val != null && val.length() > PROP_VALUE_MAX) {
+ if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("value of system property '" + key
+ "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
}
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index 430a5e3e..8c688713 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -574,6 +574,25 @@ public class UserManager {
public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
/**
+ * Specifies that system error dialogs for crashed or unresponsive apps should not be shown.
+ * In this case, the system will force-stop the app as if the user chooses the "close app"
+ * option on the UI. No feedback report will be collected as there is no way for the user to
+ * provide explicit consent.
+ *
+ * When this user restriction is set by device owners, it's applied to all users; when it's set
+ * by profile owners, it's only applied to the relevant profiles.
+ * The default value is <code>false</code>.
+ *
+ * <p>This user restriction has no effect on managed profiles.
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+
+ /**
* Specifies if what is copied in the clipboard of this profile can
* be pasted in related profiles. Does not restrict if the clipboard of related profiles can be
* pasted in this profile.
diff --git a/android/preference/SeekBarVolumizer.java b/android/preference/SeekBarVolumizer.java
index ee8eed19..3d2e1d1f 100644
--- a/android/preference/SeekBarVolumizer.java
+++ b/android/preference/SeekBarVolumizer.java
@@ -206,8 +206,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
try {
mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
.getAudioAttributes())
- .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
- AudioAttributes.FLAG_BYPASS_MUTE)
+ .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
.build());
mRingtone.play();
} catch (Throwable e) {
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index a062db43..a27df3a7 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -442,6 +442,18 @@ public final class Settings {
"android.settings.ASSIST_GESTURE_SETTINGS";
/**
+ * Activity Action: Show settings to enroll fingerprints, and setup PIN/Pattern/Pass if
+ * necessary.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_FINGERPRINT_ENROLL =
+ "android.settings.FINGERPRINT_ENROLL";
+
+ /**
* Activity Action: Show settings to allow configuration of cast endpoints.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -5708,6 +5720,7 @@ public final class Settings {
*
* @hide
*/
+ @TestApi
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
"accessibility_display_magnification_enabled";
@@ -6792,14 +6805,6 @@ public final class Settings {
"lock_screen_show_notifications";
/**
- * This preference stores the last stack active task time for each user, which affects what
- * tasks will be visible in Overview.
- * @hide
- */
- public static final String OVERVIEW_LAST_STACK_ACTIVE_TIME =
- "overview_last_stack_active_time";
-
- /**
* List of TV inputs that are currently hidden. This is a string
* containing the IDs of all hidden TV inputs. Each ID is encoded by
* {@link android.net.Uri#encode(String)} and separated by ':'.
@@ -7610,6 +7615,13 @@ public final class Settings {
public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
/**
+ * An integer representing the Bluetooth Class of Device (CoD).
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
+
+ /**
* A Long representing a bitmap of profiles that should be disabled when bluetooth starts.
* See {@link android.bluetooth.BluetoothProfile}.
* {@hide}
@@ -9261,6 +9273,13 @@ public final class Settings {
*/
public static final String DEFAULT_DNS_SERVER = "default_dns_server";
+ /**
+ * Whether to disable DNS over TLS (boolean)
+ *
+ * @hide
+ */
+ public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+
/** {@hide} */
public static final String
BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
@@ -9575,6 +9594,22 @@ public final class Settings {
public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants";
/**
+ * TextClassifier specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * <pre>
+ * smart_selection_dark_launch (boolean)
+ * smart_selection_enabled_for_edit_text (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ * see also android.view.textclassifier.TextClassifierConstants
+ */
+ public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
+
+ /**
* Get the key that retrieves a bluetooth headset's priority.
* @hide
*/
@@ -9621,7 +9656,7 @@ public final class Settings {
* Get the key that retrieves a bluetooth Input Device's priority.
* @hide
*/
- public static final String getBluetoothInputDevicePriorityKey(String address) {
+ public static final String getBluetoothHidHostPriorityKey(String address) {
return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
}
diff --git a/android/security/net/config/ManifestConfigSource.java b/android/security/net/config/ManifestConfigSource.java
index 8fcd5ab5..79115a5a 100644
--- a/android/security/net/config/ManifestConfigSource.java
+++ b/android/security/net/config/ManifestConfigSource.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.Log;
import android.util.Pair;
+
import java.util.Set;
/** @hide */
@@ -29,21 +30,14 @@ public class ManifestConfigSource implements ConfigSource {
private final Object mLock = new Object();
private final Context mContext;
- private final int mApplicationInfoFlags;
- private final int mTargetSdkVersion;
- private final int mConfigResourceId;
- private final int mTargetSandboxVesrsion;
+ private final ApplicationInfo mApplicationInfo;
private ConfigSource mConfigSource;
public ManifestConfigSource(Context context) {
mContext = context;
- // Cache values because ApplicationInfo is mutable and apps do modify it :(
- ApplicationInfo info = context.getApplicationInfo();
- mApplicationInfoFlags = info.flags;
- mTargetSdkVersion = info.targetSdkVersion;
- mConfigResourceId = info.networkSecurityConfigRes;
- mTargetSandboxVesrsion = info.targetSandboxVersion;
+ // Cache the info because ApplicationInfo is mutable and apps do modify it :(
+ mApplicationInfo = new ApplicationInfo(context.getApplicationInfo());
}
@Override
@@ -61,17 +55,18 @@ public class ManifestConfigSource implements ConfigSource {
if (mConfigSource != null) {
return mConfigSource;
}
-
+ int configResource = mApplicationInfo.networkSecurityConfigRes;
ConfigSource source;
- if (mConfigResourceId != 0) {
- boolean debugBuild = (mApplicationInfoFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ if (configResource != 0) {
+ boolean debugBuild =
+ (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
if (DBG) {
Log.d(LOG_TAG, "Using Network Security Config from resource "
- + mContext.getResources().getResourceEntryName(mConfigResourceId)
+ + mContext.getResources()
+ .getResourceEntryName(configResource)
+ " debugBuild: " + debugBuild);
}
- source = new XmlConfigSource(mContext, mConfigResourceId, debugBuild,
- mTargetSdkVersion, mTargetSandboxVesrsion);
+ source = new XmlConfigSource(mContext, configResource, mApplicationInfo);
} else {
if (DBG) {
Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
@@ -79,10 +74,9 @@ public class ManifestConfigSource implements ConfigSource {
// the legacy FLAG_USES_CLEARTEXT_TRAFFIC is not supported for Ephemeral apps, they
// should use the network security config.
boolean usesCleartextTraffic =
- (mApplicationInfoFlags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
- && mTargetSandboxVesrsion < 2;
- source = new DefaultConfigSource(usesCleartextTraffic, mTargetSdkVersion,
- mTargetSandboxVesrsion);
+ (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
+ && mApplicationInfo.targetSandboxVersion < 2;
+ source = new DefaultConfigSource(usesCleartextTraffic, mApplicationInfo);
}
mConfigSource = source;
return mConfigSource;
@@ -93,10 +87,8 @@ public class ManifestConfigSource implements ConfigSource {
private final NetworkSecurityConfig mDefaultConfig;
- public DefaultConfigSource(boolean usesCleartextTraffic, int targetSdkVersion,
- int targetSandboxVesrsion) {
- mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion,
- targetSandboxVesrsion)
+ DefaultConfigSource(boolean usesCleartextTraffic, ApplicationInfo info) {
+ mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(info)
.setCleartextTrafficPermitted(usesCleartextTraffic)
.build();
}
diff --git a/android/security/net/config/NetworkSecurityConfig.java b/android/security/net/config/NetworkSecurityConfig.java
index 789fc273..b9e55054 100644
--- a/android/security/net/config/NetworkSecurityConfig.java
+++ b/android/security/net/config/NetworkSecurityConfig.java
@@ -16,9 +16,11 @@
package android.security.net.config;
+import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
+
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
@@ -28,8 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import javax.net.ssl.X509TrustManager;
-
/**
* @hide
*/
@@ -170,22 +170,24 @@ public final class NetworkSecurityConfig {
* <li>No certificate pinning is used.</li>
* <li>The system certificate store is trusted for connections.</li>
* <li>If the application targets API level 23 (Android M) or lower then the user certificate
- * store is trusted by default as well.</li>
+ * store is trusted by default as well for non-privileged applications.</li>
+ * <li>Privileged applications do not trust the user certificate store on Android P and higher.
+ * </li>
* </ol>
*
* @hide
*/
- public static final Builder getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion) {
+ public static Builder getDefaultBuilder(ApplicationInfo info) {
Builder builder = new Builder()
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
- final boolean cleartextTrafficPermitted = targetSandboxVesrsion < 2;
+ final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
- if (targetSdkVersion <= Build.VERSION_CODES.M) {
+ if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
// User certificate store, does not bypass static pins.
builder.addCertificatesEntryRef(
new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
diff --git a/android/security/net/config/XmlConfigSource.java b/android/security/net/config/XmlConfigSource.java
index a111fbce..02be403a 100644
--- a/android/security/net/config/XmlConfigSource.java
+++ b/android/security/net/config/XmlConfigSource.java
@@ -1,13 +1,13 @@
package android.security.net.config;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import android.os.Build;
import android.util.ArraySet;
import android.util.Base64;
import android.util.Pair;
-import com.android.internal.annotations.VisibleForTesting;
+
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -36,37 +36,19 @@ public class XmlConfigSource implements ConfigSource {
private final Object mLock = new Object();
private final int mResourceId;
private final boolean mDebugBuild;
- private final int mTargetSdkVersion;
- private final int mTargetSandboxVesrsion;
+ private final ApplicationInfo mApplicationInfo;
private boolean mInitialized;
private NetworkSecurityConfig mDefaultConfig;
private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
private Context mContext;
- @VisibleForTesting
- public XmlConfigSource(Context context, int resourceId) {
- this(context, resourceId, false);
- }
-
- @VisibleForTesting
- public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
- this(context, resourceId, debugBuild, Build.VERSION_CODES.CUR_DEVELOPMENT);
- }
-
- @VisibleForTesting
- public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
- int targetSdkVersion) {
- this(context, resourceId, debugBuild, targetSdkVersion, 1 /*targetSandboxVersion*/);
- }
-
- public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
- int targetSdkVersion, int targetSandboxVesrsion) {
- mResourceId = resourceId;
+ public XmlConfigSource(Context context, int resourceId, ApplicationInfo info) {
mContext = context;
- mDebugBuild = debugBuild;
- mTargetSdkVersion = targetSdkVersion;
- mTargetSandboxVesrsion = targetSandboxVesrsion;
+ mResourceId = resourceId;
+ mApplicationInfo = new ApplicationInfo(info);
+
+ mDebugBuild = (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
@@ -365,7 +347,7 @@ public class XmlConfigSource implements ConfigSource {
// Use the platform default as the parent of the base config for any values not provided
// there. If there is no base config use the platform default.
NetworkSecurityConfig.Builder platformDefaultBuilder =
- NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion, mTargetSandboxVesrsion);
+ NetworkSecurityConfig.getDefaultBuilder(mApplicationInfo);
addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
if (baseConfigBuilder != null) {
baseConfigBuilder.setParent(platformDefaultBuilder);
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
index 2e59f6c5..953501c7 100644
--- a/android/service/autofill/AutofillService.java
+++ b/android/service/autofill/AutofillService.java
@@ -65,7 +65,7 @@ import com.android.internal.os.SomeArgs;
* <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}.
* <li>The Android System calls {@link #onDisconnected()} and unbinds from the
* {@code AutofillService}.
- * <li>The Android System displays an UI affordance with the options sent by the service.
+ * <li>The Android System displays an autofill UI with the options sent by the service.
* <li>The user picks an option.
* <li>The proper views are autofilled.
* </ol>
@@ -365,6 +365,81 @@ import com.android.internal.os.SomeArgs;
* <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
* verifications above, as long as the service can verify the authenticity of the browser app by
* checking its signing certificate.
+ *
+ * <a name="MultipleStepsSave"></a>
+ * <h3>Saving when data is split in multiple screens</h3>
+ *
+ * Apps often split the user data in multiple screens in the same activity, specially in
+ * activities used to create a new user account. For example, the first screen asks for a username,
+ * and if the username is available, it moves to a second screen, which asks for a password.
+ *
+ * <p>It's tricky to handle save for autofill in these situations, because the autofill service must
+ * wait until the user enters both fields before the autofill save UI can be shown. But it can be
+ * done by following the steps below:
+ *
+ * <ol>
+ * <li>In the first
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in
+ * the response, containing the autofill ids of the partial fields present in the screen.
+ * <li>In the second
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids
+ * set in the previous request from the client state, and adds these ids and the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second
+ * response.
+ * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the
+ * proper {@link FillContext fill contexts} to get the value of each field (there is one fill
+ * context per fill request).
+ * </ol>
+ *
+ * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow
+ * would be:
+ * <pre class="prettyprint">
+ * // On first fill request
+ * AutofillId usernameId = // parse from AssistStructure;
+ * Bundle clientState = new Bundle();
+ * clientState.putParcelable("usernameId", usernameId);
+ * fillCallback.onSuccess(
+ * new FillResponse.Builder()
+ * .setClientState(clientState)
+ * .setSaveInfo(new SaveInfo
+ * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId})
+ * .build())
+ * .build());
+ *
+ * // On second fill request
+ * Bundle clientState = fillRequest.getClientState();
+ * AutofillId usernameId = clientState.getParcelable("usernameId");
+ * AutofillId passwordId = // parse from AssistStructure
+ * clientState.putParcelable("passwordId", passwordId);
+ * fillCallback.onSuccess(
+ * new FillResponse.Builder()
+ * .setClientState(clientState)
+ * .setSaveInfo(new SaveInfo
+ * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ * new AutofillId[] {usernameId, passwordId})
+ * .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ * .build())
+ * .build());
+ *
+ * // On save request
+ * Bundle clientState = saveRequest.getClientState();
+ * AutofillId usernameId = clientState.getParcelable("usernameId");
+ * AutofillId passwordId = clientState.getParcelable("passwordId");
+ * List<FillContext> fillContexts = saveRequest.getFillContexts();
+ *
+ * FillContext usernameContext = fillContexts.get(0);
+ * ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId);
+ * AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString();
+ *
+ * FillContext passwordContext = fillContexts.get(1);
+ * ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId);
+ * AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
+ *
+ * save(username, password);
+ *
+ * </pre>
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/android/service/autofill/BatchUpdates.java b/android/service/autofill/BatchUpdates.java
new file mode 100644
index 00000000..90acc881
--- /dev/null
+++ b/android/service/autofill/BatchUpdates.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Defines actions to be applied to a {@link RemoteViews template presentation}.
+ *
+ *
+ * <p>It supports 2 types of actions:
+ *
+ * <ol>
+ * <li>{@link RemoteViews Actions} to be applied to the template.
+ * <li>{@link Transformation Transformations} to be applied on child views.
+ * </ol>
+ *
+ * <p>Typically used on {@link CustomDescription custom descriptions} to conditionally display
+ * differents views based on user input - see
+ * {@link CustomDescription.Builder#batchUpdate(Validator, BatchUpdates)} for more information.
+ */
+public final class BatchUpdates implements Parcelable {
+
+ private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+ private final RemoteViews mUpdates;
+
+ private BatchUpdates(Builder builder) {
+ mTransformations = builder.mTransformations;
+ mUpdates = builder.mUpdates;
+ }
+
+ /** @hide */
+ @Nullable
+ public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+ return mTransformations;
+ }
+
+ /** @hide */
+ @Nullable
+ public RemoteViews getUpdates() {
+ return mUpdates;
+ }
+
+ /**
+ * Builder for {@link BatchUpdates} objects.
+ */
+ public static class Builder {
+ private RemoteViews mUpdates;
+
+ private boolean mDestroyed;
+ private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+
+ /**
+ * Applies the {@code updates} in the underlying presentation template.
+ *
+ * <p><b>Note:</b> The updates are applied before the
+ * {@link #transformChild(int, Transformation) transformations} are applied to the children
+ * views.
+ *
+ * @param updates a {@link RemoteViews} with the updated actions to be applied in the
+ * underlying presentation template.
+ *
+ * @return this builder
+ * @throws IllegalArgumentException if {@code condition} is not a class provided
+ * by the Android System.
+ */
+ public Builder updateTemplate(@NonNull RemoteViews updates) {
+ throwIfDestroyed();
+ mUpdates = Preconditions.checkNotNull(updates);
+ return this;
+ }
+
+ /**
+ * Adds a transformation to replace the value of a child view with the fields in the
+ * screen.
+ *
+ * <p>When multiple transformations are added for the same child view, they are applied
+ * in the same order as added.
+ *
+ * <p><b>Note:</b> The transformations are applied after the
+ * {@link #updateTemplate(RemoteViews) updates} are applied to the presentation template.
+ *
+ * @param id view id of the children view.
+ * @param transformation an implementation provided by the Android System.
+ * @return this builder.
+ * @throws IllegalArgumentException if {@code transformation} is not a class provided
+ * by the Android System.
+ */
+ public Builder transformChild(int id, @NonNull Transformation transformation) {
+ throwIfDestroyed();
+ Preconditions.checkArgument((transformation instanceof InternalTransformation),
+ "not provided by Android System: " + transformation);
+ if (mTransformations == null) {
+ mTransformations = new ArrayList<>();
+ }
+ mTransformations.add(new Pair<>(id, (InternalTransformation) transformation));
+ return this;
+ }
+
+ /**
+ * Creates a new {@link BatchUpdates} instance.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called before or no call
+ * to {@link #updateTemplate(RemoteViews)} or {@link #transformChild(int, Transformation)}
+ * has been made.
+ */
+ public BatchUpdates build() {
+ throwIfDestroyed();
+ Preconditions.checkState(mUpdates != null || mTransformations != null,
+ "must call either updateTemplate() or transformChild() at least once");
+ mDestroyed = true;
+ return new BatchUpdates(this);
+ }
+
+ private void throwIfDestroyed() {
+ if (mDestroyed) {
+ throw new IllegalStateException("Already called #build()");
+ }
+ }
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return new StringBuilder("BatchUpdates: [")
+ .append(", transformations=")
+ .append(mTransformations == null ? "N/A" : mTransformations.size())
+ .append(", updates=").append(mUpdates)
+ .append("]").toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mTransformations == null) {
+ dest.writeIntArray(null);
+ } else {
+ final int size = mTransformations.size();
+ final int[] ids = new int[size];
+ final InternalTransformation[] values = new InternalTransformation[size];
+ for (int i = 0; i < size; i++) {
+ final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
+ ids[i] = pair.first;
+ values[i] = pair.second;
+ }
+ dest.writeIntArray(ids);
+ dest.writeParcelableArray(values, flags);
+ }
+ dest.writeParcelable(mUpdates, flags);
+ }
+ public static final Parcelable.Creator<BatchUpdates> CREATOR =
+ new Parcelable.Creator<BatchUpdates>() {
+ @Override
+ public BatchUpdates createFromParcel(Parcel parcel) {
+ // Always go through the builder to ensure the data ingested by
+ // the system obeys the contract of the builder to avoid attacks
+ // using specially crafted parcels.
+ final Builder builder = new Builder();
+ final int[] ids = parcel.createIntArray();
+ if (ids != null) {
+ final InternalTransformation[] values =
+ parcel.readParcelableArray(null, InternalTransformation.class);
+ final int size = ids.length;
+ for (int i = 0; i < size; i++) {
+ builder.transformChild(ids[i], values[i]);
+ }
+ }
+ final RemoteViews updates = parcel.readParcelable(null);
+ if (updates != null) {
+ builder.updateTemplate(updates);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public BatchUpdates[] newArray(int size) {
+ return new BatchUpdates[size];
+ }
+ };
+}
diff --git a/android/service/autofill/CustomDescription.java b/android/service/autofill/CustomDescription.java
index 9a4cbc41..fd30857d 100644
--- a/android/service/autofill/CustomDescription.java
+++ b/android/service/autofill/CustomDescription.java
@@ -19,11 +19,11 @@ package android.service.autofill;
import static android.view.autofill.Helper.sDebug;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.PendingIntent;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
import android.util.Pair;
import android.widget.RemoteViews;
@@ -67,18 +67,18 @@ import java.util.ArrayList;
* // Image child - different logo for each bank, based on credit card prefix
* builder.addChild(R.id.templateccLogo,
* new ImageTransformation.Builder(ccNumberId)
- * .addOption(Pattern.compile(""^4815.*$"), R.drawable.ic_credit_card_logo1)
- * .addOption(Pattern.compile(""^1623.*$"), R.drawable.ic_credit_card_logo2)
- * .addOption(Pattern.compile(""^42.*$"), R.drawable.ic_credit_card_logo3)
+ * .addOption(Pattern.compile("^4815.*$"), R.drawable.ic_credit_card_logo1)
+ * .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2)
+ * .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3)
* .build();
* // Masked credit card number (as .....LAST_4_DIGITS)
* builder.addChild(R.id.templateCcNumber, new CharSequenceTransformation
- * .Builder(ccNumberId, Pattern.compile(""^.*(\\d\\d\\d\\d)$"), "...$1")
+ * .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
* .build();
* // Expiration date as MM / YYYY:
* builder.addChild(R.id.templateExpDate, new CharSequenceTransformation
- * .Builder(ccExpMonthId, Pattern.compile(""^(\\d\\d)$"), "Exp: $1")
- * .addField(ccExpYearId, Pattern.compile(""^(\\d\\d)$"), "/$1")
+ * .Builder(ccExpMonthId, Pattern.compile("^(\\d\\d)$"), "Exp: $1")
+ * .addField(ccExpYearId, Pattern.compile("^(\\d\\d)$"), "/$1")
* .build();
* </pre>
*
@@ -87,47 +87,43 @@ import java.util.ArrayList;
*/
public final class CustomDescription implements Parcelable {
- private static final String TAG = "CustomDescription";
-
private final RemoteViews mPresentation;
private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+ private final ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
private CustomDescription(Builder builder) {
mPresentation = builder.mPresentation;
mTransformations = builder.mTransformations;
+ mUpdates = builder.mUpdates;
}
/** @hide */
- public RemoteViews getPresentation(ValueFinder finder) {
- if (mTransformations != null) {
- final int size = mTransformations.size();
- if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
- for (int i = 0; i < size; i++) {
- final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
- final int id = pair.first;
- final InternalTransformation transformation = pair.second;
- if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
-
- try {
- transformation.apply(finder, mPresentation, id);
- } catch (Exception e) {
- // Do not log full exception to avoid PII leaking
- Log.e(TAG, "Could not apply transformation " + transformation + ": "
- + e.getClass());
- return null;
- }
- }
- }
+ @Nullable
+ public RemoteViews getPresentation() {
return mPresentation;
}
+ /** @hide */
+ @Nullable
+ public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+ return mTransformations;
+ }
+
+ /** @hide */
+ @Nullable
+ public ArrayList<Pair<InternalValidator, BatchUpdates>> getUpdates() {
+ return mUpdates;
+ }
+
/**
* Builder for {@link CustomDescription} objects.
*/
public static class Builder {
private final RemoteViews mPresentation;
+ private boolean mDestroyed;
private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+ private ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
/**
* Default constructor.
@@ -145,9 +141,11 @@ public final class CustomDescription implements Parcelable {
* </ul>
*
* @param parentPresentation template presentation with (optional) children views.
+ * @throws NullPointerException if {@code parentPresentation} is null (on Android
+ * {@link android.os.Build.VERSION_CODES#P} or higher).
*/
- public Builder(RemoteViews parentPresentation) {
- mPresentation = parentPresentation;
+ public Builder(@NonNull RemoteViews parentPresentation) {
+ mPresentation = Preconditions.checkNotNull(parentPresentation);
}
/**
@@ -164,6 +162,7 @@ public final class CustomDescription implements Parcelable {
* by the Android System.
*/
public Builder addChild(int id, @NonNull Transformation transformation) {
+ throwIfDestroyed();
Preconditions.checkArgument((transformation instanceof InternalTransformation),
"not provided by Android System: " + transformation);
if (mTransformations == null) {
@@ -174,11 +173,109 @@ public final class CustomDescription implements Parcelable {
}
/**
+ * Updates the {@link RemoteViews presentation template} when a condition is satisfied.
+ *
+ * <p>The updates are applied in the sequence they are added, after the
+ * {@link #addChild(int, Transformation) transformations} are applied to the children
+ * views.
+ *
+ * <p>For example, to make children views visible when fields are not empty:
+ *
+ * <pre class="prettyprint">
+ * RemoteViews template = new RemoteViews(pgkName, R.layout.my_full_template);
+ *
+ * Pattern notEmptyPattern = Pattern.compile(".+");
+ * Validator hasAddress = new RegexValidator(addressAutofillId, notEmptyPattern);
+ * Validator hasCcNumber = new RegexValidator(ccNumberAutofillId, notEmptyPattern);
+ *
+ * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+ * addressUpdates.setViewVisibility(R.id.address, View.VISIBLE);
+ *
+ * // Make address visible
+ * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+ * .updateTemplate(addressUpdates)
+ * .build();
+ *
+ * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+ * ccUpdates.setViewVisibility(R.id.cc_number, View.VISIBLE);
+ *
+ * // Mask credit card number (as .....LAST_4_DIGITS) and make it visible
+ * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+ * .updateTemplate(ccUpdates)
+ * .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+ * .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+ * .build())
+ * .build();
+ *
+ * CustomDescription customDescription = new CustomDescription.Builder(template)
+ * .batchUpdate(hasAddress, addressBatchUpdates)
+ * .batchUpdate(hasCcNumber, ccBatchUpdates)
+ * .build();
+ * </pre>
+ *
+ * <p>Another approach is to add a child first, then apply the transformations. Example:
+ *
+ * <pre class="prettyprint">
+ * RemoteViews template = new RemoteViews(pgkName, R.layout.my_base_template);
+ *
+ * RemoteViews addressPresentation = new RemoteViews(pgkName, R.layout.address)
+ * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_template)
+ * addressUpdates.addView(R.id.parentId, addressPresentation);
+ * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+ * .updateTemplate(addressUpdates)
+ * .build();
+ *
+ * RemoteViews ccPresentation = new RemoteViews(pgkName, R.layout.cc)
+ * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_template)
+ * ccUpdates.addView(R.id.parentId, ccPresentation);
+ * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+ * .updateTemplate(ccUpdates)
+ * .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+ * .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+ * .build())
+ * .build();
+ *
+ * CustomDescription customDescription = new CustomDescription.Builder(template)
+ * .batchUpdate(hasAddress, addressBatchUpdates)
+ * .batchUpdate(hasCcNumber, ccBatchUpdates)
+ * .build();
+ * </pre>
+ *
+ * @param condition condition used to trigger the updates.
+ * @param updates actions to be applied to the
+ * {@link #CustomDescription.Builder(RemoteViews) template presentation} when the condition
+ * is satisfied.
+ *
+ * @return this builder
+ * @throws IllegalArgumentException if {@code condition} is not a class provided
+ * by the Android System.
+ */
+ public Builder batchUpdate(@NonNull Validator condition, @NonNull BatchUpdates updates) {
+ throwIfDestroyed();
+ Preconditions.checkArgument((condition instanceof InternalValidator),
+ "not provided by Android System: " + condition);
+ Preconditions.checkNotNull(updates);
+ if (mUpdates == null) {
+ mUpdates = new ArrayList<>();
+ }
+ mUpdates.add(new Pair<>((InternalValidator) condition, updates));
+ return this;
+ }
+
+ /**
* Creates a new {@link CustomDescription} instance.
*/
public CustomDescription build() {
+ throwIfDestroyed();
+ mDestroyed = true;
return new CustomDescription(this);
}
+
+ private void throwIfDestroyed() {
+ if (mDestroyed) {
+ throw new IllegalStateException("Already called #build()");
+ }
+ }
}
/////////////////////////////////////
@@ -190,7 +287,10 @@ public final class CustomDescription implements Parcelable {
return new StringBuilder("CustomDescription: [presentation=")
.append(mPresentation)
- .append(", transformations=").append(mTransformations)
+ .append(", transformations=")
+ .append(mTransformations == null ? "N/A" : mTransformations.size())
+ .append(", updates=")
+ .append(mUpdates == null ? "N/A" : mUpdates.size())
.append("]").toString();
}
@@ -205,6 +305,8 @@ public final class CustomDescription implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mPresentation, flags);
+ if (mPresentation == null) return;
+
if (mTransformations == null) {
dest.writeIntArray(null);
} else {
@@ -219,6 +321,21 @@ public final class CustomDescription implements Parcelable {
dest.writeIntArray(ids);
dest.writeParcelableArray(values, flags);
}
+ if (mUpdates == null) {
+ dest.writeParcelableArray(null, flags);
+ } else {
+ final int size = mUpdates.size();
+ final InternalValidator[] conditions = new InternalValidator[size];
+ final BatchUpdates[] updates = new BatchUpdates[size];
+
+ for (int i = 0; i < size; i++) {
+ final Pair<InternalValidator, BatchUpdates> pair = mUpdates.get(i);
+ conditions[i] = pair.first;
+ updates[i] = pair.second;
+ }
+ dest.writeParcelableArray(conditions, flags);
+ dest.writeParcelableArray(updates, flags);
+ }
}
public static final Parcelable.Creator<CustomDescription> CREATOR =
new Parcelable.Creator<CustomDescription>() {
@@ -227,7 +344,10 @@ public final class CustomDescription implements Parcelable {
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
- final Builder builder = new Builder(parcel.readParcelable(null));
+ final RemoteViews parentPresentation = parcel.readParcelable(null);
+ if (parentPresentation == null) return null;
+
+ final Builder builder = new Builder(parentPresentation);
final int[] ids = parcel.createIntArray();
if (ids != null) {
final InternalTransformation[] values =
@@ -237,6 +357,15 @@ public final class CustomDescription implements Parcelable {
builder.addChild(ids[i], values[i]);
}
}
+ final InternalValidator[] conditions =
+ parcel.readParcelableArray(null, InternalValidator.class);
+ if (conditions != null) {
+ final BatchUpdates[] updates = parcel.readParcelableArray(null, BatchUpdates.class);
+ final int size = conditions.length;
+ for (int i = 0; i < size; i++) {
+ builder.batchUpdate(conditions[i], updates[i]);
+ }
+ }
return builder.build();
}
diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java
index ef9598aa..331130e7 100644
--- a/android/service/autofill/Dataset.java
+++ b/android/service/autofill/Dataset.java
@@ -150,8 +150,16 @@ public final class Dataset implements Parcelable {
public String toString() {
if (!sDebug) return super.toString();
- return new StringBuilder("Dataset " + mId + " [")
- .append("fieldIds=").append(mFieldIds)
+ final StringBuilder builder = new StringBuilder("Dataset[id=");
+ if (mId == null) {
+ builder.append("null");
+ } else {
+ // Cannot disclose id because it could contain PII.
+ builder.append(mId.length()).append("_chars");
+ }
+
+ return builder
+ .append(", fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
.append(", fieldPresentations=")
.append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
@@ -201,7 +209,8 @@ public final class Dataset implements Parcelable {
* Creates a new builder for a dataset where each field will be visualized independently.
*
* <p>When using this constructor, fields must be set through
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+ * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
*/
public Builder() {
}
@@ -280,19 +289,24 @@ public final class Dataset implements Parcelable {
/**
* Sets the value of a field.
*
+ * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
+ * throw an {@link IllegalStateException} if this builder was constructed without a
+ * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
+ * higher removed this restriction because datasets used as an
+ * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
+ * authentication result} do not need a presentation. But if you don't set the presentation
+ * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
+ * for this field will not be displayed.
+ *
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
* @param value value to be autofilled. Pass {@code null} if you do not have the value
* but the target view is a logical part of the dataset. For example, if
* the dataset needs authentication and you have no access to the value.
* @return this builder.
- * @throws IllegalStateException if the builder was constructed without a
- * {@link RemoteViews presentation}.
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
throwIfDestroyed();
- Preconditions.checkState(mPresentation != null,
- "Dataset presentation not set on constructor");
setLifeTheUniverseAndEverything(id, value, null, null);
return this;
}
@@ -334,7 +348,7 @@ public final class Dataset implements Parcelable {
*
* @return this builder.
* @throws IllegalStateException if the builder was constructed without a
- * {@link RemoteViews presentation}.
+ * {@link RemoteViews presentation}.
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull Pattern filter) {
diff --git a/android/service/autofill/FillEventHistory.java b/android/service/autofill/FillEventHistory.java
index 3b719ac7..b1857b30 100644
--- a/android/service/autofill/FillEventHistory.java
+++ b/android/service/autofill/FillEventHistory.java
@@ -17,19 +17,27 @@
package android.service.autofill;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Describes what happened after the last
@@ -121,6 +129,11 @@ public final class FillEventHistory implements Parcelable {
}
@Override
+ public String toString() {
+ return mEvents == null ? "no events" : mEvents.toString();
+ }
+
+ @Override
public int describeContents() {
return 0;
}
@@ -136,9 +149,21 @@ public final class FillEventHistory implements Parcelable {
int numEvents = mEvents.size();
for (int i = 0; i < numEvents; i++) {
Event event = mEvents.get(i);
- dest.writeInt(event.getType());
- dest.writeString(event.getDatasetId());
- dest.writeBundle(event.getClientState());
+ dest.writeInt(event.mEventType);
+ dest.writeString(event.mDatasetId);
+ dest.writeBundle(event.mClientState);
+ dest.writeStringList(event.mSelectedDatasetIds);
+ dest.writeArraySet(event.mIgnoredDatasetIds);
+ dest.writeTypedList(event.mChangedFieldIds);
+ dest.writeStringList(event.mChangedDatasetIds);
+
+ dest.writeTypedList(event.mManuallyFilledFieldIds);
+ if (event.mManuallyFilledFieldIds != null) {
+ final int size = event.mManuallyFilledFieldIds.size();
+ for (int j = 0; j < size; j++) {
+ dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
+ }
+ }
}
}
}
@@ -176,12 +201,40 @@ public final class FillEventHistory implements Parcelable {
/** A save UI was shown. */
public static final int TYPE_SAVE_SHOWN = 3;
+ /**
+ * A committed autofill context for which the autofill service provided datasets.
+ *
+ * <p>This event is useful to track:
+ * <ul>
+ * <li>Which datasets (if any) were selected by the user
+ * ({@link #getSelectedDatasetIds()}).
+ * <li>Which datasets (if any) were NOT selected by the user
+ * ({@link #getIgnoredDatasetIds()}).
+ * <li>Which fields in the selected datasets were changed by the user after the dataset
+ * was selected ({@link #getChangedFields()}.
+ * </ul>
+ *
+ * <p><b>Note: </b>This event is only generated when:
+ * <ul>
+ * <li>The autofill context is committed.
+ * <li>The service provides at least one dataset in the
+ * {@link FillResponse fill responses} associated with the context.
+ * <li>The last {@link FillResponse fill responses} associated with the context has the
+ * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag.
+ * </ul>
+ *
+ * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
+ * contexts.
+ */
+ public static final int TYPE_CONTEXT_COMMITTED = 4;
+
/** @hide */
@IntDef(
value = {TYPE_DATASET_SELECTED,
TYPE_DATASET_AUTHENTICATION_SELECTED,
TYPE_AUTHENTICATION_SELECTED,
- TYPE_SAVE_SHOWN})
+ TYPE_SAVE_SHOWN,
+ TYPE_CONTEXT_COMMITTED})
@Retention(RetentionPolicy.SOURCE)
@interface EventIds{}
@@ -189,6 +242,17 @@ public final class FillEventHistory implements Parcelable {
@Nullable private final String mDatasetId;
@Nullable private final Bundle mClientState;
+ // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already
+ // stores it as List
+ @Nullable private final List<String> mSelectedDatasetIds;
+ @Nullable private final ArraySet<String> mIgnoredDatasetIds;
+
+ @Nullable private final ArrayList<AutofillId> mChangedFieldIds;
+ @Nullable private final ArrayList<String> mChangedDatasetIds;
+
+ @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
+ @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+
/**
* Returns the type of the event.
*
@@ -220,25 +284,202 @@ public final class FillEventHistory implements Parcelable {
}
/**
+ * Returns which datasets were selected by the user.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ */
+ @NonNull public Set<String> getSelectedDatasetIds() {
+ return mSelectedDatasetIds == null ? Collections.emptySet()
+ : new ArraySet<>(mSelectedDatasetIds);
+ }
+
+ /**
+ * Returns which datasets were NOT selected by the user.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ */
+ @NonNull public Set<String> getIgnoredDatasetIds() {
+ return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds;
+ }
+
+ /**
+ * Returns which fields in the selected datasets were changed by the user after the dataset
+ * was selected.
+ *
+ * <p>For example, server provides:
+ *
+ * <pre class="prettyprint">
+ * FillResponse response = new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder(presentation1)
+ * .setId("4815")
+ * .setValue(usernameId, AutofillValue.forText("MrPlow"))
+ * .build())
+ * .addDataset(new Dataset.Builder(presentation2)
+ * .setId("162342")
+ * .setValue(passwordId, AutofillValue.forText("D'OH"))
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>User select both datasets (for username and password) but after the fields are
+ * autofilled, user changes them to:
+ *
+ * <pre class="prettyprint">
+ * username = "ElBarto";
+ * password = "AyCaramba";
+ * </pre>
+ *
+ * <p>Then the result is the following map:
+ *
+ * <pre class="prettyprint">
+ * usernameId => "4815"
+ * passwordId => "162342"
+ * </pre>
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ *
+ * @return map map whose key is the id of the change fields, and value is the id of
+ * dataset that has that field and was selected by the user.
+ */
+ @NonNull public Map<AutofillId, String> getChangedFields() {
+ if (mChangedFieldIds == null || mChangedDatasetIds == null) {
+ return Collections.emptyMap();
+ }
+
+ final int size = mChangedFieldIds.size();
+ final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size);
+ for (int i = 0; i < size; i++) {
+ changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i));
+ }
+ return changedFields;
+ }
+
+ /**
+ * Returns which fields were available on datasets provided by the service but manually
+ * entered by the user.
+ *
+ * <p>For example, server provides:
+ *
+ * <pre class="prettyprint">
+ * FillResponse response = new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder(presentation1)
+ * .setId("4815")
+ * .setValue(usernameId, AutofillValue.forText("MrPlow"))
+ * .setValue(passwordId, AutofillValue.forText("AyCaramba"))
+ * .build())
+ * .addDataset(new Dataset.Builder(presentation2)
+ * .setId("162342")
+ * .setValue(usernameId, AutofillValue.forText("ElBarto"))
+ * .setValue(passwordId, AutofillValue.forText("D'OH"))
+ * .build())
+ * .addDataset(new Dataset.Builder(presentation3)
+ * .setId("108")
+ * .setValue(usernameId, AutofillValue.forText("MrPlow"))
+ * .setValue(passwordId, AutofillValue.forText("D'OH"))
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>User doesn't select a dataset but manually enters:
+ *
+ * <pre class="prettyprint">
+ * username = "MrPlow";
+ * password = "D'OH";
+ * </pre>
+ *
+ * <p>Then the result is the following map:
+ *
+ * <pre class="prettyprint">
+ * usernameId => { "4815", "108"}
+ * passwordId => { "162342", "108" }
+ * </pre>
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ *
+ * @return map map whose key is the id of the manually-entered field, and value is the
+ * ids of the datasets that have that value but were not selected by the user.
+ */
+ @Nullable public Map<AutofillId, Set<String>> getManuallyEnteredField() {
+ if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) {
+ return Collections.emptyMap();
+ }
+
+ final int size = mManuallyFilledFieldIds.size();
+ final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size);
+ for (int i = 0; i < size; i++) {
+ final AutofillId fieldId = mManuallyFilledFieldIds.get(i);
+ final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i);
+ manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds));
+ }
+ return manuallyFilledFields;
+ }
+
+ /**
* Creates a new event.
*
* @param eventType The type of the event
* @param datasetId The dataset the event was on, or {@code null} if the event was on the
* whole response.
* @param clientState The client state associated with the event.
+ * @param selectedDatasetIds The ids of datasets selected by the user.
+ * @param ignoredDatasetIds The ids of datasets NOT select by the user.
+ * @param changedFieldIds The ids of fields changed by the user.
+ * @param changedDatasetIds The ids of the datasets that havd values matching the
+ * respective entry on {@code changedFieldIds}.
+ * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
+ * 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
+ * {@code manuallyFilledDatasetIds} doesn't match.
*
* @hide
*/
- public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState) {
- mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
+ 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) {
+ mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
"eventType");
mDatasetId = datasetId;
mClientState = clientState;
+ mSelectedDatasetIds = selectedDatasetIds;
+ mIgnoredDatasetIds = ignoredDatasetIds;
+ if (changedFieldIds != null) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds)
+ && changedDatasetIds != null
+ && changedFieldIds.size() == changedDatasetIds.size(),
+ "changed ids must have same length and not be empty");
+ }
+ mChangedFieldIds = changedFieldIds;
+ mChangedDatasetIds = changedDatasetIds;
+ if (manuallyFilledFieldIds != null) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds)
+ && manuallyFilledDatasetIds != null
+ && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(),
+ "manually filled ids must have same length and not be empty");
+ }
+ mManuallyFilledFieldIds = manuallyFilledFieldIds;
+ mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
}
@Override
public String toString() {
- return "FillEvent [datasetId=" + mDatasetId + ", type=" + mEventType + "]";
+ return "FillEvent [datasetId=" + mDatasetId
+ + ", type=" + mEventType
+ + ", selectedDatasets=" + mSelectedDatasetIds
+ + ", ignoredDatasetIds=" + mIgnoredDatasetIds
+ + ", changedFieldIds=" + mChangedFieldIds
+ + ", changedDatasetsIds=" + mChangedDatasetIds
+ + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+ + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+ + "]";
}
}
@@ -248,12 +489,37 @@ public final class FillEventHistory implements Parcelable {
public FillEventHistory createFromParcel(Parcel parcel) {
FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
- int numEvents = parcel.readInt();
+ final int numEvents = parcel.readInt();
for (int i = 0; i < numEvents; i++) {
- selection.addEvent(new Event(parcel.readInt(), parcel.readString(),
- parcel.readBundle()));
- }
+ final int eventType = parcel.readInt();
+ final String datasetId = parcel.readString();
+ final Bundle clientState = parcel.readBundle();
+ final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList();
+ @SuppressWarnings("unchecked")
+ final ArraySet<String> ignoredDatasets =
+ (ArraySet<String>) parcel.readArraySet(null);
+ final ArrayList<AutofillId> changedFieldIds =
+ parcel.createTypedArrayList(AutofillId.CREATOR);
+ final ArrayList<String> changedDatasetIds = parcel.createStringArrayList();
+
+ final ArrayList<AutofillId> manuallyFilledFieldIds =
+ parcel.createTypedArrayList(AutofillId.CREATOR);
+ final ArrayList<ArrayList<String>> manuallyFilledDatasetIds;
+ if (manuallyFilledFieldIds != null) {
+ final int size = manuallyFilledFieldIds.size();
+ manuallyFilledDatasetIds = new ArrayList<>(size);
+ for (int j = 0; j < size; j++) {
+ manuallyFilledDatasetIds.add(parcel.createStringArrayList());
+ }
+ } else {
+ manuallyFilledDatasetIds = null;
+ }
+ selection.addEvent(new Event(eventType, datasetId, clientState,
+ selectedDatasetIds, ignoredDatasets,
+ changedFieldIds, changedDatasetIds,
+ manuallyFilledFieldIds, manuallyFilledDatasetIds));
+ }
return selection;
}
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index 6d8a9599..d2033fa9 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -19,6 +19,7 @@ package android.service.autofill;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.Helper.sDebug;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -30,6 +31,8 @@ import android.os.Parcelable;
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -42,6 +45,19 @@ import java.util.List;
*/
public final class FillResponse implements Parcelable {
+ /**
+ * Must be set in the last response to generate
+ * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} events.
+ */
+ public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+
+ /** @hide */
+ @IntDef(flag = true, value = {
+ FLAG_TRACK_CONTEXT_COMMITED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface FillResponseFlags {}
+
private final @Nullable ParceledListSlice<Dataset> mDatasets;
private final @Nullable SaveInfo mSaveInfo;
private final @Nullable Bundle mClientState;
@@ -49,16 +65,18 @@ public final class FillResponse implements Parcelable {
private final @Nullable IntentSender mAuthentication;
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
+ private final int mFlags;
private int mRequestId;
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
mSaveInfo = builder.mSaveInfo;
- mClientState = builder.mCLientState;
+ mClientState = builder.mClientState;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
+ mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -97,6 +115,11 @@ public final class FillResponse implements Parcelable {
return mIgnoredIds;
}
+ /** @hide */
+ public int getFlags() {
+ return mFlags;
+ }
+
/**
* Associates a {@link FillResponse} to a request.
*
@@ -122,11 +145,12 @@ public final class FillResponse implements Parcelable {
public static final class Builder {
private ArrayList<Dataset> mDatasets;
private SaveInfo mSaveInfo;
- private Bundle mCLientState;
+ private Bundle mClientState;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
+ private int mFlags;
private boolean mDestroyed;
/**
@@ -264,7 +288,20 @@ public final class FillResponse implements Parcelable {
*/
public Builder setClientState(@Nullable Bundle clientState) {
throwIfDestroyed();
- mCLientState = clientState;
+ mClientState = clientState;
+ return this;
+ }
+
+ /**
+ * Sets flags changing the response behavior.
+ *
+ * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}.
+ *
+ * @return This builder.
+ */
+ public Builder setFlags(@FillResponseFlags int flags) {
+ throwIfDestroyed();
+ mFlags = flags;
return this;
}
@@ -311,6 +348,7 @@ public final class FillResponse implements Parcelable {
.append(", hasAuthentication=").append(mAuthentication != null)
.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+ .append(", flags=").append(mFlags)
.append("]")
.toString();
}
@@ -333,6 +371,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
+ parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -363,8 +402,9 @@ public final class FillResponse implements Parcelable {
}
builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
- final FillResponse response = builder.build();
+ builder.setFlags(parcel.readInt());
+ final FillResponse response = builder.build();
response.setRequestId(parcel.readInt());
return response;
diff --git a/android/service/autofill/InternalTransformation.java b/android/service/autofill/InternalTransformation.java
index 974397b3..c9864a0e 100644
--- a/android/service/autofill/InternalTransformation.java
+++ b/android/service/autofill/InternalTransformation.java
@@ -15,17 +15,27 @@
*/
package android.service.autofill;
+import static android.view.autofill.Helper.sDebug;
+
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.os.Parcelable;
+import android.util.Log;
+import android.util.Pair;
import android.widget.RemoteViews;
+import java.util.ArrayList;
+
/**
* Superclass of all transformation the system understands. As this is not public all
* subclasses have to implement {@link Transformation} again.
*
* @hide
*/
-abstract class InternalTransformation implements Transformation, Parcelable {
+@TestApi
+public abstract class InternalTransformation implements Transformation, Parcelable {
+
+ private static final String TAG = "InternalTransformation";
/**
* Applies this transformation to a child view of a {@link android.widget.RemoteViews
@@ -39,4 +49,37 @@ abstract class InternalTransformation implements Transformation, Parcelable {
*/
abstract void apply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
int childViewId) throws Exception;
+
+ /**
+ * Applies multiple transformations to the children views of a
+ * {@link android.widget.RemoteViews presentation template}.
+ *
+ * @param finder object used to find the value of a field in the screen.
+ * @param template the {@link RemoteViews presentation template}.
+ * @param transformations map of resource id of the child view inside the template to
+ * transformation.
+ *
+ * @hide
+ */
+ public static boolean batchApply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
+ @NonNull ArrayList<Pair<Integer, InternalTransformation>> transformations) {
+ final int size = transformations.size();
+ if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
+ for (int i = 0; i < size; i++) {
+ final Pair<Integer, InternalTransformation> pair = transformations.get(i);
+ final int id = pair.first;
+ final InternalTransformation transformation = pair.second;
+ if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
+
+ try {
+ transformation.apply(finder, template, id);
+ } catch (Exception e) {
+ // Do not log full exception to avoid PII leaking
+ Log.e(TAG, "Could not apply transformation " + transformation + ": "
+ + e.getClass());
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/android/service/autofill/InternalValidator.java b/android/service/autofill/InternalValidator.java
index e11cf6ad..e08bb6c1 100644
--- a/android/service/autofill/InternalValidator.java
+++ b/android/service/autofill/InternalValidator.java
@@ -16,6 +16,7 @@
package android.service.autofill;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.os.Parcelable;
/**
@@ -24,6 +25,7 @@ import android.os.Parcelable;
*
* @hide
*/
+@TestApi
public abstract class InternalValidator implements Validator, Parcelable {
/**
@@ -34,5 +36,6 @@ public abstract class InternalValidator implements Validator, Parcelable {
*
* @hide
*/
+ @TestApi
public abstract boolean isValid(@NonNull ValueFinder finder);
}
diff --git a/android/service/autofill/LuhnChecksumValidator.java b/android/service/autofill/LuhnChecksumValidator.java
index 0b5930df..c56ae84b 100644
--- a/android/service/autofill/LuhnChecksumValidator.java
+++ b/android/service/autofill/LuhnChecksumValidator.java
@@ -27,6 +27,8 @@ import android.view.autofill.AutofillId;
import com.android.internal.util.Preconditions;
+import java.util.Arrays;
+
/**
* Validator that returns {@code true} if the number created by concatenating all given fields
* pass a Luhn algorithm checksum. All non-digits are ignored.
@@ -86,17 +88,27 @@ public final class LuhnChecksumValidator extends InternalValidator implements Va
public boolean isValid(@NonNull ValueFinder finder) {
if (mIds == null || mIds.length == 0) return false;
- final StringBuilder number = new StringBuilder();
+ final StringBuilder builder = new StringBuilder();
for (AutofillId id : mIds) {
final String partialNumber = finder.findByAutofillId(id);
if (partialNumber == null) {
if (sDebug) Log.d(TAG, "No partial number for id " + id);
return false;
}
- number.append(partialNumber);
+ builder.append(partialNumber);
}
- return isLuhnChecksumValid(number.toString());
+ final String number = builder.toString();
+ boolean valid = isLuhnChecksumValid(number);
+ if (sDebug) Log.d(TAG, "isValid(" + number.length() + " chars): " + valid);
+ return valid;
+ }
+
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return "LuhnChecksumValidator: [ids=" + Arrays.toString(mIds) + "]";
}
/////////////////////////////////////
diff --git a/android/service/autofill/OptionalValidators.java b/android/service/autofill/OptionalValidators.java
index f7edd6e4..7aec59f6 100644
--- a/android/service/autofill/OptionalValidators.java
+++ b/android/service/autofill/OptionalValidators.java
@@ -21,6 +21,7 @@ import static android.view.autofill.Helper.sDebug;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -34,6 +35,8 @@ import com.android.internal.util.Preconditions;
*/
final class OptionalValidators extends InternalValidator {
+ private static final String TAG = "OptionalValidators";
+
@NonNull private final InternalValidator[] mValidators;
OptionalValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@ final class OptionalValidators extends InternalValidator {
public boolean isValid(@NonNull ValueFinder finder) {
for (InternalValidator validator : mValidators) {
final boolean valid = validator.isValid(finder);
+ if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
if (valid) return true;
}
diff --git a/android/service/autofill/RequiredValidators.java b/android/service/autofill/RequiredValidators.java
index ac85c284..9e1db2bc 100644
--- a/android/service/autofill/RequiredValidators.java
+++ b/android/service/autofill/RequiredValidators.java
@@ -21,6 +21,7 @@ import static android.view.autofill.Helper.sDebug;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -34,6 +35,8 @@ import com.android.internal.util.Preconditions;
*/
final class RequiredValidators extends InternalValidator {
+ private static final String TAG = "RequiredValidators";
+
@NonNull private final InternalValidator[] mValidators;
RequiredValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@ final class RequiredValidators extends InternalValidator {
public boolean isValid(@NonNull ValueFinder finder) {
for (InternalValidator validator : mValidators) {
final boolean valid = validator.isValid(finder);
+ if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
if (!valid) return false;
}
return true;
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index 1b9240cc..fde2416f 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -68,7 +68,7 @@ import java.util.Arrays;
* .build();
* </pre>
*
- * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
+ * <p>The save type flags are used to display the appropriate strings in the autofill save UI.
* You can pass multiple values, but try to keep it short if possible. In the above example, just
* {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
*
@@ -103,13 +103,17 @@ import java.util.Arrays;
* .build();
* </pre>
*
+ * <a name="TriggeringSaveRequest"></a>
+ * <h3>Triggering a save request</h3>
+ *
* <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
* any of the following events:
* <ul>
* <li>The {@link Activity} finishes.
- * <li>The app explicitly called {@link AutofillManager#commit()}.
- * <li>All required views became invisible (if the {@link SaveInfo} was created with the
+ * <li>The app explicitly calls {@link AutofillManager#commit()}.
+ * <li>All required views become invisible (if the {@link SaveInfo} was created with the
* {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
+ * <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}.
* </ul>
*
* <p>But it is only triggered when all conditions below are met:
@@ -123,10 +127,13 @@ import java.util.Arrays;
* <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
* screen state (i.e., all required and optional fields in the dataset have the same value as
* the fields in the screen).
- * <li>The user explicitly tapped the UI affordance asking to save data for autofill.
+ * <li>The user explicitly tapped the autofill save UI asking to save data for autofill.
* </ul>
*
- * <p>The service can also customize some aspects of the save UI affordance:
+ * <a name="CustomizingSaveUI"></a>
+ * <h3>Customizing the autofill save UI</h3>
+ *
+ * <p>The service can also customize some aspects of the autofill save UI:
* <ul>
* <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
* <li>Add a customized subtitle by calling
@@ -212,16 +219,25 @@ public final class SaveInfo implements Parcelable {
@interface SaveDataType{}
/**
- * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
- * is called once the {@link Activity} finishes. If this flag is set it is called once all
- * saved views become invisible.
+ * Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a>
+ * once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views
+ * become invisible.
*/
public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
+ /**
+ * By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a>
+ * once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't
+ * trigger a save request.
+ *
+ * <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}.
+ */
+ public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2;
+
/** @hide */
@IntDef(
flag = true,
- value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
+ value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE, FLAG_DONT_SAVE_ON_FINISH})
@Retention(RetentionPolicy.SOURCE)
@interface SaveInfoFlags{}
@@ -236,6 +252,7 @@ public final class SaveInfo implements Parcelable {
private final InternalValidator mValidator;
private final InternalSanitizer[] mSanitizerKeys;
private final AutofillId[][] mSanitizerValues;
+ private final AutofillId mTriggerId;
private SaveInfo(Builder builder) {
mType = builder.mType;
@@ -259,6 +276,7 @@ public final class SaveInfo implements Parcelable {
mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
}
}
+ mTriggerId = builder.mTriggerId;
}
/** @hide */
@@ -320,6 +338,12 @@ public final class SaveInfo implements Parcelable {
return mSanitizerValues;
}
+ /** @hide */
+ @Nullable
+ public AutofillId getTriggerId() {
+ return mTriggerId;
+ }
+
/**
* A builder for {@link SaveInfo} objects.
*/
@@ -338,6 +362,7 @@ public final class SaveInfo implements Parcelable {
private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
// Set used to validate against duplicate ids.
private ArraySet<AutofillId> mSanitizerIds;
+ private AutofillId mTriggerId;
/**
* Creates a new builder.
@@ -394,13 +419,15 @@ public final class SaveInfo implements Parcelable {
/**
* Sets flags changing the save behavior.
*
- * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
+ * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
+ * {@link #FLAG_DONT_SAVE_ON_FINISH}, or {@code 0}.
* @return This builder.
*/
public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
throwIfDestroyed();
- mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
+ mFlags = Preconditions.checkFlagsArgument(flags,
+ FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH);
return this;
}
@@ -493,8 +520,8 @@ public final class SaveInfo implements Parcelable {
}
/**
- * Sets an object used to validate the user input - if the input is not valid, the Save UI
- * affordance is not shown.
+ * Sets an object used to validate the user input - if the input is not valid, the
+ * autofill save UI is not shown.
*
* <p>Typically used to validate credit card numbers. Examples:
*
@@ -520,7 +547,7 @@ public final class SaveInfo implements Parcelable {
* );
* </pre>
*
- * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator
+ * <p><b>Note:</b> the example above is just for illustrative purposes; the same validator
* could be created using a single regex for the {@code OR} part:
*
* <pre class="prettyprint">
@@ -615,6 +642,27 @@ public final class SaveInfo implements Parcelable {
return this;
}
+ /**
+ * Explicitly defines the view that should commit the autofill context when clicked.
+ *
+ * <p>Usually, the save request is only automatically
+ * <a href="#TriggeringSaveRequest">triggered</a> after the activity is
+ * finished or all relevant views become invisible, but there are scenarios where the
+ * autofill context is automatically commited too late
+ * &mdash;for example, when the activity manually clears the autofillable views when a
+ * button is tapped. This method can be used to trigger the autofill save UI earlier in
+ * these scenarios.
+ *
+ * <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow
+ * is not enough, otherwise it could trigger the autofill save UI when it should not&mdash;
+ * for example, when the user entered invalid credentials for the autofillable views.
+ */
+ public @NonNull Builder setTriggerId(@NonNull AutofillId id) {
+ throwIfDestroyed();
+ mTriggerId = Preconditions.checkNotNull(id);
+ return this;
+ }
+
/**
* Builds a new {@link SaveInfo} instance.
*
@@ -652,13 +700,14 @@ public final class SaveInfo implements Parcelable {
.append(", description=").append(mDescription)
.append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
mNegativeButtonStyle))
- .append(", mFlags=").append(mFlags)
- .append(", mCustomDescription=").append(mCustomDescription)
- .append(", validation=").append(mValidator)
+ .append(", flags=").append(mFlags)
+ .append(", customDescription=").append(mCustomDescription)
+ .append(", validator=").append(mValidator)
.append(", sanitizerKeys=")
.append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
.append(", sanitizerValues=")
.append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
+ .append(", triggerId=").append(mTriggerId)
.append("]").toString();
}
@@ -687,6 +736,7 @@ public final class SaveInfo implements Parcelable {
parcel.writeParcelableArray(mSanitizerValues[i], flags);
}
}
+ parcel.writeParcelable(mTriggerId, flags);
parcel.writeInt(mFlags);
}
@@ -727,6 +777,10 @@ public final class SaveInfo implements Parcelable {
builder.addSanitizer(sanitizers[i], autofillIds);
}
}
+ final AutofillId triggerId = parcel.readParcelable(null);
+ if (triggerId != null) {
+ builder.setTriggerId(triggerId);
+ }
builder.setFlags(parcel.readInt());
return builder.build();
}
diff --git a/android/service/autofill/SaveRequest.java b/android/service/autofill/SaveRequest.java
index 65fdb5c4..f53967bd 100644
--- a/android/service/autofill/SaveRequest.java
+++ b/android/service/autofill/SaveRequest.java
@@ -19,7 +19,6 @@ package android.service.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
@@ -60,9 +59,14 @@ public final class SaveRequest implements Parcelable {
}
/**
- * Gets the extra client state returned from the last {@link
- * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}
- * fill request}.
+ * Gets the latest client state extra returned from the service.
+ *
+ * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+ * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} where considered. On
+ * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+ * an authenticated request through the
+ * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+ * also considered (and take precedence when set).
*
* @return The client state.
*/
diff --git a/android/service/autofill/Validator.java b/android/service/autofill/Validator.java
index 854aa1e6..a4036f25 100644
--- a/android/service/autofill/Validator.java
+++ b/android/service/autofill/Validator.java
@@ -16,9 +16,9 @@
package android.service.autofill;
/**
- * Helper class used to define whether the contents of a screen are valid.
+ * Class used to define whether a condition is satisfied.
*
- * <p>Typically used to avoid displaying the Save UI affordance when the user input is invalid.
+ * <p>Typically used to avoid displaying the save UI when the user input is invalid.
*/
public interface Validator {
}
diff --git a/android/service/notification/ZenModeConfig.java b/android/service/notification/ZenModeConfig.java
index 7bec898a..735b8223 100644
--- a/android/service/notification/ZenModeConfig.java
+++ b/android/service/notification/ZenModeConfig.java
@@ -76,10 +76,13 @@ public class ZenModeConfig implements Parcelable {
private static final int DAY_MINUTES = 24 * 60;
private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
- private static final boolean DEFAULT_ALLOW_CALLS = true;
+ // Default allow categories set in readXml() from default_zen_mode_config.xml, fallback values:
+ private static final boolean DEFAULT_ALLOW_ALARMS = true;
+ private static final boolean DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER = true;
+ private static final boolean DEFAULT_ALLOW_CALLS = false;
private static final boolean DEFAULT_ALLOW_MESSAGES = false;
- private static final boolean DEFAULT_ALLOW_REMINDERS = true;
- private static final boolean DEFAULT_ALLOW_EVENTS = true;
+ private static final boolean DEFAULT_ALLOW_REMINDERS = false;
+ private static final boolean DEFAULT_ALLOW_EVENTS = false;
private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
private static final boolean DEFAULT_ALLOW_SCREEN_OFF = true;
private static final boolean DEFAULT_ALLOW_SCREEN_ON = true;
@@ -89,6 +92,8 @@ public class ZenModeConfig implements Parcelable {
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
private static final String ALLOW_TAG = "allow";
+ private static final String ALLOW_ATT_ALARMS = "alarms";
+ private static final String ALLOW_ATT_MEDIA_SYSTEM_OTHER = "media_system_other";
private static final String ALLOW_ATT_CALLS = "calls";
private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
private static final String ALLOW_ATT_MESSAGES = "messages";
@@ -100,8 +105,6 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
- private static final String CONDITION_TAG = "condition";
- private static final String CONDITION_ATT_COMPONENT = "component";
private static final String CONDITION_ATT_ID = "id";
private static final String CONDITION_ATT_SUMMARY = "summary";
private static final String CONDITION_ATT_LINE1 = "line1";
@@ -123,6 +126,8 @@ public class ZenModeConfig implements Parcelable {
private static final String RULE_ATT_CREATION_TIME = "creationTime";
private static final String RULE_ATT_ENABLER = "enabler";
+ public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
+ public boolean allowMediaSystemOther = DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER;
public boolean allowCalls = DEFAULT_ALLOW_CALLS;
public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
public boolean allowMessages = DEFAULT_ALLOW_MESSAGES;
@@ -161,6 +166,8 @@ public class ZenModeConfig implements Parcelable {
}
allowWhenScreenOff = source.readInt() == 1;
allowWhenScreenOn = source.readInt() == 1;
+ allowAlarms = source.readInt() == 1;
+ allowMediaSystemOther = source.readInt() == 1;
}
@Override
@@ -190,19 +197,23 @@ public class ZenModeConfig implements Parcelable {
}
dest.writeInt(allowWhenScreenOff ? 1 : 0);
dest.writeInt(allowWhenScreenOn ? 1 : 0);
+ dest.writeInt(allowAlarms ? 1 : 0);
+ dest.writeInt(allowMediaSystemOther ? 1 : 0);
}
@Override
public String toString() {
return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
.append("user=").append(user)
+ .append(",allowAlarms=").append(allowAlarms)
+ .append(",allowMediaSystemOther=").append(allowMediaSystemOther)
+ .append(",allowReminders=").append(allowReminders)
+ .append(",allowEvents=").append(allowEvents)
.append(",allowCalls=").append(allowCalls)
.append(",allowRepeatCallers=").append(allowRepeatCallers)
.append(",allowMessages=").append(allowMessages)
.append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
.append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
- .append(",allowReminders=").append(allowReminders)
- .append(",allowEvents=").append(allowEvents)
.append(",allowWhenScreenOff=").append(allowWhenScreenOff)
.append(",allowWhenScreenOn=").append(allowWhenScreenOn)
.append(",automaticRules=").append(automaticRules)
@@ -218,9 +229,21 @@ public class ZenModeConfig implements Parcelable {
if (user != to.user) {
d.addLine("user", user, to.user);
}
+ if (allowAlarms != to.allowAlarms) {
+ d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
+ }
+ if (allowMediaSystemOther != to.allowMediaSystemOther) {
+ d.addLine("allowMediaSystemOther", allowMediaSystemOther, to.allowMediaSystemOther);
+ }
if (allowCalls != to.allowCalls) {
d.addLine("allowCalls", allowCalls, to.allowCalls);
}
+ if (allowReminders != to.allowReminders) {
+ d.addLine("allowReminders", allowReminders, to.allowReminders);
+ }
+ if (allowEvents != to.allowEvents) {
+ d.addLine("allowEvents", allowEvents, to.allowEvents);
+ }
if (allowRepeatCallers != to.allowRepeatCallers) {
d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
}
@@ -233,12 +256,6 @@ public class ZenModeConfig implements Parcelable {
if (allowMessagesFrom != to.allowMessagesFrom) {
d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
}
- if (allowReminders != to.allowReminders) {
- d.addLine("allowReminders", allowReminders, to.allowReminders);
- }
- if (allowEvents != to.allowEvents) {
- d.addLine("allowEvents", allowEvents, to.allowEvents);
- }
if (allowWhenScreenOff != to.allowWhenScreenOff) {
d.addLine("allowWhenScreenOff", allowWhenScreenOff, to.allowWhenScreenOff);
}
@@ -335,7 +352,9 @@ public class ZenModeConfig implements Parcelable {
if (!(o instanceof ZenModeConfig)) return false;
if (o == this) return true;
final ZenModeConfig other = (ZenModeConfig) o;
- return other.allowCalls == allowCalls
+ return other.allowAlarms == allowAlarms
+ && other.allowMediaSystemOther == allowMediaSystemOther
+ && other.allowCalls == allowCalls
&& other.allowRepeatCallers == allowRepeatCallers
&& other.allowMessages == allowMessages
&& other.allowCallsFrom == allowCallsFrom
@@ -351,10 +370,10 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
- allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff,
- allowWhenScreenOn,
- user, automaticRules, manualRule);
+ return Objects.hash(allowAlarms, allowMediaSystemOther, allowCalls,
+ allowRepeatCallers, allowMessages,
+ allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
+ allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule);
}
private static String toDayList(int[] days) {
@@ -413,10 +432,12 @@ public class ZenModeConfig implements Parcelable {
}
if (type == XmlPullParser.START_TAG) {
if (ALLOW_TAG.equals(tag)) {
- rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
+ DEFAULT_ALLOW_CALLS);
rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
DEFAULT_ALLOW_REPEAT_CALLERS);
- rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES,
+ DEFAULT_ALLOW_MESSAGES);
rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
DEFAULT_ALLOW_REMINDERS);
rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
@@ -438,6 +459,9 @@ public class ZenModeConfig implements Parcelable {
safeBoolean(parser, ALLOW_ATT_SCREEN_OFF, DEFAULT_ALLOW_SCREEN_OFF);
rt.allowWhenScreenOn =
safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON);
+ rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS);
+ rt.allowMediaSystemOther = safeBoolean(parser, ALLOW_ATT_MEDIA_SYSTEM_OTHER,
+ DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
} else if (AUTOMATIC_TAG.equals(tag)) {
@@ -468,6 +492,8 @@ public class ZenModeConfig implements Parcelable {
out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
out.attribute(null, ALLOW_ATT_SCREEN_OFF, Boolean.toString(allowWhenScreenOff));
out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowWhenScreenOn));
+ out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
+ out.attribute(null, ALLOW_ATT_MEDIA_SYSTEM_OTHER, Boolean.toString(allowMediaSystemOther));
out.endTag(null, ALLOW_TAG);
if (manualRule != null) {
@@ -654,6 +680,12 @@ public class ZenModeConfig implements Parcelable {
if (!allowWhenScreenOn) {
suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON;
}
+ if (allowAlarms) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
+ }
+ if (allowMediaSystemOther) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER;
+ }
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
@@ -680,10 +712,13 @@ public class ZenModeConfig implements Parcelable {
public void applyNotificationPolicy(Policy policy) {
if (policy == null) return;
- allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
- allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
+ allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
+ allowMediaSystemOther = (policy.priorityCategories
+ & Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0;
allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
+ allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
+ allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
!= 0;
allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
diff --git a/android/service/settings/suggestions/SuggestionService.java b/android/service/settings/suggestions/SuggestionService.java
index 2a4c84c2..ce9501d6 100644
--- a/android/service/settings/suggestions/SuggestionService.java
+++ b/android/service/settings/suggestions/SuggestionService.java
@@ -48,12 +48,20 @@ public abstract class SuggestionService extends Service {
}
@Override
- public void dismissSuggestion(Suggestion suggestion) {
+ public void dismissSuggestion(Suggestion suggestion) {
if (DEBUG) {
Log.d(TAG, "dismissSuggestion() " + getPackageName());
}
onSuggestionDismissed(suggestion);
}
+
+ @Override
+ public void launchSuggestion(Suggestion suggestion) {
+ if (DEBUG) {
+ Log.d(TAG, "launchSuggestion() " + getPackageName());
+ }
+ onSuggestionLaunched(suggestion);
+ }
};
}
@@ -65,7 +73,12 @@ public abstract class SuggestionService extends Service {
/**
* Dismiss a suggestion. The suggestion will not be included in future
* {@link #onGetSuggestions()} calls.
- * @param suggestion
*/
public abstract void onSuggestionDismissed(Suggestion suggestion);
+
+ /**
+ * This is the opposite signal to {@link #onSuggestionDismissed}, indicating a suggestion has
+ * been launched.
+ */
+ public abstract void onSuggestionLaunched(Suggestion suggestion);
}
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index a046d95e..2f5730a2 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-beta2");
+ private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-rc1");
/**
* Version code for Room
@@ -45,15 +45,17 @@ public class LibraryVersions {
*/
public static final Version PAGING = new Version("1.0.0-alpha3");
+ private static final Version LIFECYCLES = new Version("1.0.3");
+
/**
* Version code for Lifecycle libs that are required by the support library
*/
- public static final Version LIFECYCLES_CORE = new Version("1.0.2");
+ public static final Version LIFECYCLES_CORE = LIFECYCLES;
/**
* Version code for Lifecycle runtime libs that are required by the support library
*/
- public static final Version LIFECYCLES_RUNTIME = new Version("1.0.0");
+ public static final Version LIFECYCLES_RUNTIME = LIFECYCLES;
/**
* Version code for shared code of flatfoot
diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java
new file mode 100644
index 00000000..7100218a
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerActivity.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car.drawer;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Common base Activity for car apps that need to present a Drawer.
+ *
+ * <p>This Activity manages the overall layout. To use it, sub-classes need to:
+ *
+ * <ul>
+ * <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}.
+ * <li>Add their main content using {@link #setMainContent(int)} or {@link #setMainContent(View)}.
+ * They can also add fragments to the main-content container by obtaining its id using
+ * {@link #getContentContainerId()}
+ * </ul>
+ *
+ * <p>This class will take care of drawer toggling and display.
+ *
+ * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
+ * CarDrawerAdapter for the next level to
+ * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}.
+ *
+ * <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
+ * derivative.
+ */
+public abstract class CarDrawerActivity extends AppCompatActivity {
+ private CarDrawerController mDrawerController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.car_drawer_activity);
+
+ DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
+ ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
+ this /* activity */,
+ drawerLayout, /* DrawerLayout object */
+ R.string.car_drawer_open,
+ R.string.car_drawer_close);
+
+ Toolbar toolbar = findViewById(R.id.car_toolbar);
+ setSupportActionBar(toolbar);
+
+ mDrawerController = new CarDrawerController(toolbar, drawerLayout, drawerToggle);
+ mDrawerController.setRootAdapter(getRootAdapter());
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+ }
+
+ /**
+ * Returns the {@link CarDrawerController} that is responsible for handling events relating
+ * to the drawer in this Activity.
+ *
+ * @return The {@link CarDrawerController} linked to this Activity. This value will be
+ * {@code null} if this method is called before {@code onCreate()} has been called.
+ */
+ @Nullable
+ protected CarDrawerController getDrawerController() {
+ return mDrawerController;
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ mDrawerController.syncState();
+ }
+
+ /**
+ * @return Adapter for root content of the Drawer.
+ */
+ protected abstract CarDrawerAdapter getRootAdapter();
+
+ /**
+ * Set main content to display in this Activity. It will be added to R.id.content_frame in
+ * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
+ *
+ * @param view View to display as main content.
+ */
+ public void setMainContent(View view) {
+ ViewGroup parent = findViewById(getContentContainerId());
+ parent.addView(view);
+ }
+
+ /**
+ * Set main content to display in this Activity. It will be added to R.id.content_frame in
+ * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
+ *
+ * @param resourceId Layout to display as main content.
+ */
+ public void setMainContent(@LayoutRes int resourceId) {
+ ViewGroup parent = findViewById(getContentContainerId());
+ LayoutInflater inflater = getLayoutInflater();
+ inflater.inflate(resourceId, parent, true);
+ }
+
+ /**
+ * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
+ * content/fragments inside here.
+ *
+ * @return Id of FrameLayout where main content of the subclass Activity can be added.
+ */
+ protected int getContentContainerId() {
+ return R.id.content_frame;
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mDrawerController.closeDrawer();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDrawerController.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
+ }
+}
diff --git a/android/support/car/drawer/CarDrawerAdapter.java b/android/support/car/drawer/CarDrawerAdapter.java
new file mode 100644
index 00000000..b0fd965d
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerAdapter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car.drawer;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.car.widget.PagedListView;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base adapter for displaying items in the car navigation drawer, which uses a
+ * {@link PagedListView}.
+ *
+ * <p>Subclasses must set the title that will be displayed when displaying the contents of the
+ * drawer via {@link #setTitle(CharSequence)}. The title can be updated at any point later on. The
+ * title of the root adapter will also be the main title showed in the toolbar when the drawer is
+ * closed. See {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} for more information.
+ *
+ * <p>This class also takes care of implementing the PageListView.ItemCamp contract and subclasses
+ * should implement {@link #getActualItemCount()}.
+ */
+public abstract class CarDrawerAdapter extends RecyclerView.Adapter<DrawerItemViewHolder>
+ implements PagedListView.ItemCap, DrawerItemClickListener {
+ private final boolean mShowDisabledListOnEmpty;
+ private final Drawable mEmptyListDrawable;
+ private int mMaxItems = PagedListView.ItemCap.UNLIMITED;
+ private CharSequence mTitle;
+ private TitleChangeListener mTitleChangeListener;
+
+ /**
+ * Interface for a class that will be notified a new title has been set on this adapter.
+ */
+ interface TitleChangeListener {
+ /**
+ * Called when {@link #setTitle(CharSequence)} has been called and the title has been
+ * changed.
+ */
+ void onTitleChanged(CharSequence newTitle);
+ }
+
+ protected CarDrawerAdapter(Context context, boolean showDisabledListOnEmpty) {
+ mShowDisabledListOnEmpty = showDisabledListOnEmpty;
+
+ mEmptyListDrawable = context.getDrawable(R.drawable.ic_list_view_disable);
+ mEmptyListDrawable.setColorFilter(context.getColor(R.color.car_tint),
+ PorterDuff.Mode.SRC_IN);
+ }
+
+ /** Returns the title set via {@link #setTitle(CharSequence)}. */
+ CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** Updates the title to display in the toolbar for this Adapter. */
+ public final void setTitle(@NonNull CharSequence title) {
+ if (title == null) {
+ throw new IllegalArgumentException("setTitle() cannot be passed a null title!");
+ }
+
+ mTitle = title;
+
+ if (mTitleChangeListener != null) {
+ mTitleChangeListener.onTitleChanged(mTitle);
+ }
+ }
+
+ /** Sets a listener to be notified whenever the title of this adapter has been changed. */
+ void setTitleChangeListener(@Nullable TitleChangeListener listener) {
+ mTitleChangeListener = listener;
+ }
+
+ @Override
+ public final void setMaxItems(int maxItems) {
+ mMaxItems = maxItems;
+ }
+
+ @Override
+ public final int getItemCount() {
+ if (shouldShowDisabledListItem()) {
+ return 1;
+ }
+ return mMaxItems >= 0 ? Math.min(mMaxItems, getActualItemCount()) : getActualItemCount();
+ }
+
+ /**
+ * Returns the absolute number of items that can be displayed in the list.
+ *
+ * <p>A class should implement this method to supply the number of items to be displayed.
+ * Returning 0 from this method will cause an empty list icon to be displayed in the drawer.
+ *
+ * <p>A class should override this method rather than {@link #getItemCount()} because that
+ * method is handling the logic of when to display the empty list icon. It will return 1 when
+ * {@link #getActualItemCount()} returns 0.
+ *
+ * @return The number of items to be displayed in the list.
+ */
+ protected abstract int getActualItemCount();
+
+ @Override
+ public final int getItemViewType(int position) {
+ if (shouldShowDisabledListItem()) {
+ return R.layout.car_drawer_list_item_empty;
+ }
+
+ return usesSmallLayout(position)
+ ? R.layout.car_drawer_list_item_small
+ : R.layout.car_drawer_list_item_normal;
+ }
+
+ /**
+ * Used to indicate the layout used for the Drawer item at given position. Subclasses can
+ * override this to use normal layout which includes text element below title.
+ *
+ * <p>A small layout is presented by the layout {@code R.layout.car_drawer_list_item_small}.
+ * Otherwise, the layout {@code R.layout.car_drawer_list_item_normal} will be used.
+ *
+ * @param position Adapter position of item.
+ * @return Whether the item at this position will use a small layout (default) or normal layout.
+ */
+ protected boolean usesSmallLayout(int position) {
+ return true;
+ }
+
+ @Override
+ public final DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ return new DrawerItemViewHolder(view);
+ }
+
+ @Override
+ public final void onBindViewHolder(DrawerItemViewHolder holder, int position) {
+ if (shouldShowDisabledListItem()) {
+ holder.getTitle().setText(null);
+ holder.getIcon().setImageDrawable(mEmptyListDrawable);
+ holder.setItemClickListener(null);
+ } else {
+ holder.setItemClickListener(this);
+ populateViewHolder(holder, position);
+ }
+ }
+
+ /**
+ * Whether or not this adapter should be displaying an empty list icon. The icon is shown if it
+ * has been configured to show and there are no items to be displayed.
+ */
+ private boolean shouldShowDisabledListItem() {
+ return mShowDisabledListOnEmpty && getActualItemCount() == 0;
+ }
+
+ /**
+ * Subclasses should set all elements in {@code holder} to populate the drawer-item. If some
+ * element is not used, it should be nulled out since these ViewHolder/View's are recycled.
+ */
+ protected abstract void populateViewHolder(DrawerItemViewHolder holder, int position);
+
+ /**
+ * Called when this adapter has been popped off the stack and is no longer needed. Subclasses
+ * can override to do any necessary cleanup.
+ */
+ public void cleanup() {}
+}
diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java
new file mode 100644
index 00000000..4d9f4e99
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerController.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car.drawer;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.car.widget.PagedListView;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.Toolbar;
+import android.view.Gravity;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import java.util.Stack;
+
+/**
+ * A controller that will handle the set up of the navigation drawer. It will hook up the
+ * necessary buttons for up navigation, as well as expose methods to allow for a drill down
+ * navigation.
+ */
+public class CarDrawerController {
+ /** The amount that the drawer has been opened before its color should be switched. */
+ private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
+
+ /**
+ * A representation of the hierarchy of navigation being displayed in the list. The ordering of
+ * this stack is the order that the user has visited each level. When the user navigates up,
+ * the adapters are poopped from this list.
+ */
+ private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
+
+ private final Context mContext;
+
+ private final Toolbar mToolbar;
+ private final DrawerLayout mDrawerLayout;
+ private final ActionBarDrawerToggle mDrawerToggle;
+
+ private final PagedListView mDrawerList;
+ private final ProgressBar mProgressBar;
+ private final View mDrawerContent;
+
+ /**
+ * Creates a {@link CarDrawerController} that will control the navigation of the drawer given by
+ * {@code drawerLayout}.
+ *
+ * <p>The given {@code drawerLayout} should either have a child View that is inflated from
+ * {@code R.layout.car_drawer} or ensure that it three children that have the IDs found in that
+ * layout.
+ *
+ * @param toolbar The {@link Toolbar} that will serve as the action bar for an Activity.
+ * @param drawerLayout The top-level container for the window content that shows the
+ * interactive drawer.
+ * @param drawerToggle The {@link ActionBarDrawerToggle} that bridges the given {@code toolbar}
+ * and {@code drawerLayout}.
+ */
+ public CarDrawerController(Toolbar toolbar,
+ DrawerLayout drawerLayout,
+ ActionBarDrawerToggle drawerToggle) {
+ mToolbar = toolbar;
+ mContext = drawerLayout.getContext();
+
+ mDrawerLayout = drawerLayout;
+
+ mDrawerContent = drawerLayout.findViewById(R.id.drawer_content);
+ mDrawerList = drawerLayout.findViewById(R.id.drawer_list);
+ mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
+
+ mProgressBar = drawerLayout.findViewById(R.id.drawer_progress);
+
+ mDrawerToggle = drawerToggle;
+ setupDrawerToggling();
+ }
+
+ /**
+ * Sets the {@link CarDrawerAdapter} that will function as the root adapter. The contents of
+ * this root adapter are shown when the drawer is first opened. It is also the top-most level of
+ * navigation in the drawer.
+ *
+ * @param rootAdapter The adapter that will act as the root. If this value is {@code null}, then
+ * this method will do nothing.
+ */
+ public void setRootAdapter(@Nullable CarDrawerAdapter rootAdapter) {
+ if (rootAdapter == null) {
+ return;
+ }
+
+ mAdapterStack.push(rootAdapter);
+ setToolbarTitleFrom(rootAdapter);
+ mDrawerList.setAdapter(rootAdapter);
+ }
+
+ /**
+ * Switches to use the given {@link CarDrawerAdapter} as the one to supply the list to display
+ * in the navigation drawer. The title will also be updated from the adapter.
+ *
+ * <p>This switch is treated as a navigation to the next level in the drawer. Navigation away
+ * from this level will pop the given adapter off and surface contents of the previous adapter
+ * that was set via this method. If no such adapter exists, then the root adapter set by
+ * {@link #setRootAdapter(CarDrawerAdapter)} will be used instead.
+ *
+ * @param adapter Adapter for next level of content in the drawer.
+ */
+ public final void switchToAdapter(CarDrawerAdapter adapter) {
+ mAdapterStack.peek().setTitleChangeListener(null);
+ mAdapterStack.push(adapter);
+ switchToAdapterInternal(adapter);
+ }
+
+ /** Close the drawer. */
+ public void closeDrawer() {
+ if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+ mDrawerLayout.closeDrawer(Gravity.LEFT);
+ }
+ }
+
+ /** Opens the drawer. */
+ public void openDrawer() {
+ if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+ mDrawerLayout.openDrawer(Gravity.LEFT);
+ }
+ }
+
+ /** Sets a listener to be notified of Drawer events. */
+ public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+ mDrawerLayout.addDrawerListener(listener);
+ }
+
+ /** Removes a listener to be notified of Drawer events. */
+ public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+ mDrawerLayout.removeDrawerListener(listener);
+ }
+
+ /**
+ * Sets whether the loading progress bar is displayed in the navigation drawer. If {@code true},
+ * the progress bar is displayed and the navigation list is hidden and vice versa.
+ */
+ public void showLoadingProgressBar(boolean show) {
+ mDrawerList.setVisibility(show ? View.INVISIBLE : View.VISIBLE);
+ mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ /** Scroll to given position in the list. */
+ public void scrollToPosition(int position) {
+ mDrawerList.getRecyclerView().smoothScrollToPosition(position);
+ }
+
+ /**
+ * Retrieves the title from the given {@link CarDrawerAdapter} and set its as the title of this
+ * controller's internal Toolbar.
+ */
+ private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
+ if (adapter.getTitle() == null) {
+ throw new RuntimeException("CarDrawerAdapter must supply a title via setTitle()");
+ }
+
+ mToolbar.setTitle(adapter.getTitle());
+ adapter.setTitleChangeListener(mToolbar::setTitle);
+ }
+
+ /**
+ * Sets up the necessary listeners for {@link DrawerLayout} so that the navigation drawer
+ * hierarchy is properly displayed.
+ */
+ private void setupDrawerToggling() {
+ mDrawerLayout.addDrawerListener(mDrawerToggle);
+ mDrawerLayout.addDrawerListener(
+ new DrawerLayout.DrawerListener() {
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ // Correctly set the title and arrow colors as they are different between
+ // the open and close states.
+ updateTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET);
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ // If drawer is closed, revert stack/drawer to initial root state.
+ cleanupStackAndShowRoot();
+ scrollToPosition(0);
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {}
+
+ @Override
+ public void onDrawerStateChanged(int newState) {}
+ });
+ }
+
+ /** Sets the title and arrow color of the drawer depending on if it is open or not. */
+ private void updateTitleAndArrowColor(boolean drawerOpen) {
+ // When the drawer is open, use car_title, which resolves to appropriate color depending on
+ // day-night mode. When drawer is closed, we always use light color.
+ int titleColorResId = drawerOpen ? R.color.car_title : R.color.car_title_light;
+ int titleColor = mContext.getColor(titleColorResId);
+ mToolbar.setTitleTextColor(titleColor);
+ mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor);
+ }
+
+ /**
+ * Synchronizes the display of the drawer with its linked {@link DrawerLayout}.
+ *
+ * <p>This should be called from the associated Activity's
+ * {@link android.support.v7.app.AppCompatActivity#onPostCreate(Bundle)} method to synchronize
+ * after teh DRawerLayout's instance state has been restored, and any other time when the
+ * state may have diverged in such a way that this controller's associated
+ * {@link ActionBarDrawerToggle} had not been notified.
+ */
+ public void syncState() {
+ mDrawerToggle.syncState();
+
+ // In case we're restarting after a config change (e.g. day, night switch), set colors
+ // again. Doing it here so that Drawer state is fully synced and we know if its open or not.
+ // NOTE: isDrawerOpen must be passed the second child of the DrawerLayout.
+ updateTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent));
+ }
+
+ /**
+ * Notify this controller that device configurations may have changed.
+ *
+ * <p>This method should be called from the associated Activity's
+ * {@code onConfigurationChanged()} method.
+ */
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Pass any configuration change to the drawer toggle.
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * An analog to an Activity's {@code onOptionsItemSelected()}. This method should be called
+ * when the Activity's method is called and will return {@code true} if the selection has
+ * been handled.
+ *
+ * @return {@code true} if the item processing was handled by this class.
+ */
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle home-click and see if we can navigate up in the drawer.
+ if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
+ return true;
+ }
+
+ // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
+ return mDrawerToggle.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Sets the navigation drawer's title to be the one supplied by the given adapter and updates
+ * the navigation drawer list with the adapter's contents.
+ */
+ private void switchToAdapterInternal(CarDrawerAdapter adapter) {
+ setToolbarTitleFrom(adapter);
+ // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
+ // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
+ mDrawerList.getRecyclerView().setAdapter(adapter);
+ scrollToPosition(0);
+ }
+
+ /**
+ * Switches to the previous level in the drawer hierarchy if the current list being displayed
+ * is not the root adapter. This is analogous to a navigate up.
+ *
+ * @return {@code true} if a navigate up was possible and executed. {@code false} otherwise.
+ */
+ private boolean maybeHandleUpClick() {
+ // Check if already at the root level.
+ if (mAdapterStack.size() <= 1) {
+ return false;
+ }
+
+ CarDrawerAdapter adapter = mAdapterStack.pop();
+ adapter.setTitleChangeListener(null);
+ adapter.cleanup();
+ switchToAdapterInternal(mAdapterStack.peek());
+ return true;
+ }
+
+ /** Clears stack down to root adapter and switches to root adapter. */
+ private void cleanupStackAndShowRoot() {
+ while (mAdapterStack.size() > 1) {
+ CarDrawerAdapter adapter = mAdapterStack.pop();
+ adapter.setTitleChangeListener(null);
+ adapter.cleanup();
+ }
+ switchToAdapterInternal(mAdapterStack.peek());
+ }
+}
diff --git a/android/arch/paging/User.java b/android/support/car/drawer/DrawerItemClickListener.java
index a6d965a2..d707dbd0 100644
--- a/android/arch/paging/User.java
+++ b/android/support/car/drawer/DrawerItemClickListener.java
@@ -14,19 +14,16 @@
* limitations under the License.
*/
-package android.arch.paging;
+package android.support.car.drawer;
-public class User {
- public final String name;
- public final String info;
-
- public User(String name, String info) {
- this.name = name;
- this.info = info;
- }
-
- @Override
- public String toString() {
- return name;
- }
+/**
+ * Listener for handling clicks on items/views managed by {@link DrawerItemViewHolder}.
+ */
+public interface DrawerItemClickListener {
+ /**
+ * Callback when item is clicked.
+ *
+ * @param position Adapter position of the clicked item.
+ */
+ void onItemClick(int position);
}
diff --git a/android/support/car/drawer/DrawerItemViewHolder.java b/android/support/car/drawer/DrawerItemViewHolder.java
new file mode 100644
index 00000000..d016b2de
--- /dev/null
+++ b/android/support/car/drawer/DrawerItemViewHolder.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car.drawer;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Re-usable {@link RecyclerView.ViewHolder} for displaying items in the
+ * {@link android.support.car.drawer.CarDrawerAdapter}.
+ */
+public class DrawerItemViewHolder extends RecyclerView.ViewHolder {
+ private final ImageView mIcon;
+ private final TextView mTitle;
+ private final TextView mText;
+ private final ImageView mEndIcon;
+
+ DrawerItemViewHolder(View view) {
+ super(view);
+ mIcon = view.findViewById(R.id.icon);
+ if (mIcon == null) {
+ throw new IllegalArgumentException("Icon view cannot be null!");
+ }
+
+ mTitle = view.findViewById(R.id.title);
+ if (mTitle == null) {
+ throw new IllegalArgumentException("Title view cannot be null!");
+ }
+
+ // Next two are optional and may be null.
+ mText = view.findViewById(R.id.text);
+ mEndIcon = view.findViewById(R.id.end_icon);
+ }
+
+ /** Returns the view that should be used to display the main icon. */
+ @NonNull
+ public ImageView getIcon() {
+ return mIcon;
+ }
+
+ /** Returns the view that will display the main title. */
+ @NonNull
+ public TextView getTitle() {
+ return mTitle;
+ }
+
+ /** Returns the view that is used for text that is smaller than the title text. */
+ @Nullable
+ public TextView getText() {
+ return mText;
+ }
+
+ /** Returns the icon that is displayed at the end of the view. */
+ @Nullable
+ public ImageView getEndIcon() {
+ return mEndIcon;
+ }
+
+ /**
+ * Sets the listener that will be notified when the view held by this ViewHolder has been
+ * clicked. Passing {@code null} will clear any previously set listeners.
+ */
+ void setItemClickListener(@Nullable DrawerItemClickListener listener) {
+ itemView.setOnClickListener(listener != null
+ ? v -> listener.onItemClick(getAdapterPosition())
+ : null);
+ }
+}
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 8527c659..46527001 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -27,7 +27,6 @@ import android.os.Handler;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.Px;
import android.support.annotation.RestrictTo;
import android.support.annotation.UiThread;
import android.support.car.R;
@@ -61,7 +60,6 @@ public class PagedListView extends FrameLayout {
protected final CarLayoutManager mLayoutManager;
protected final Handler mHandler = new Handler();
private final boolean mScrollBarEnabled;
- private final boolean mRightGutterEnabled;
private final PagedScrollBarView mScrollBarView;
private int mRowsPerPage = -1;
@@ -98,6 +96,11 @@ public class PagedListView extends FrameLayout {
*/
public interface ItemCap {
/**
+ * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit.
+ */
+ int UNLIMITED = -1;
+
+ /**
* Sets the maximum number of items available in the adapter. A value less than '0' means
* the list should not be capped.
*/
@@ -139,7 +142,6 @@ public class PagedListView extends FrameLayout {
}
LayoutInflater.from(context).inflate(layoutId, this /*root*/, true /*attachToRoot*/);
- FrameLayout maxWidthLayout = (FrameLayout) findViewById(R.id.recycler_view_container);
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PagedListView, defStyleAttrs, defStyleRes);
mRecyclerView = (CarRecyclerView) findViewById(R.id.recycler_view);
@@ -156,6 +158,16 @@ public class PagedListView extends FrameLayout {
mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager));
+ boolean offsetScrollBar = a.getBoolean(R.styleable.PagedListView_offsetScrollBar, false);
+ if (offsetScrollBar) {
+ MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
+ params.setMarginStart(getResources().getDimensionPixelSize(
+ R.dimen.car_screen_margin_size));
+ params.setMarginEnd(
+ a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0));
+ mRecyclerView.setLayoutParams(params);
+ }
+
if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) {
int dividerStartMargin = a.getDimensionPixelSize(
R.styleable.PagedListView_dividerStartMargin, 0);
@@ -199,47 +211,20 @@ public class PagedListView extends FrameLayout {
}
}
});
+
mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
- // Modify the layout if the Gutter or the Scroll Bar are not visible.
- mRightGutterEnabled = a.getBoolean(R.styleable.PagedListView_rightGutterEnabled, false);
- if (mRightGutterEnabled || !mScrollBarEnabled) {
- FrameLayout.LayoutParams maxWidthLayoutLayoutParams =
- (FrameLayout.LayoutParams) maxWidthLayout.getLayoutParams();
- if (mRightGutterEnabled) {
- maxWidthLayoutLayoutParams.rightMargin =
- getResources().getDimensionPixelSize(R.dimen.car_card_margin);
- }
- if (!mScrollBarEnabled) {
- maxWidthLayoutLayoutParams.setMarginStart(0);
- }
- maxWidthLayout.setLayoutParams(maxWidthLayoutLayoutParams);
+ // Modify the layout the Scroll Bar is not visible.
+ if (!mScrollBarEnabled) {
+ MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
+ params.setMarginStart(0);
+ mRecyclerView.setLayoutParams(params);
}
setDayNightStyle(DayNightStyle.AUTO);
a.recycle();
}
- /**
- * Sets the starting and ending padding for each view in the list.
- *
- * @param start The start padding.
- * @param end The end padding.
- */
- public void setListViewStartEndPadding(@Px int start, @Px int end) {
- int carCardMargin = getResources().getDimensionPixelSize(R.dimen.car_card_margin);
- int startGutter = mScrollBarEnabled ? carCardMargin : 0;
- int startPadding = Math.max(start - startGutter, 0);
- int endGutter = mRightGutterEnabled ? carCardMargin : 0;
- int endPadding = Math.max(end - endGutter, 0);
- mRecyclerView.setPaddingRelative(startPadding, mRecyclerView.getPaddingTop(),
- endPadding, mRecyclerView.getPaddingBottom());
-
- // Since we're setting padding we'll need to set the clip to padding to the same
- // value as clip children to ensure that the cards fly off the screen.
- mRecyclerView.setClipToPadding(mRecyclerView.getClipChildren());
- }
-
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
diff --git a/android/support/media/tv/BasePreviewProgram.java b/android/support/media/tv/BasePreviewProgram.java
index 1423d9d6..39c30140 100644
--- a/android/support/media/tv/BasePreviewProgram.java
+++ b/android/support/media/tv/BasePreviewProgram.java
@@ -23,14 +23,13 @@ import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.PreviewProgramColumns;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.AspectRatio;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Availability;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.InteractionType;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Type;
import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -55,6 +54,89 @@ public abstract class BasePreviewProgram extends BaseProgram {
private static final int IS_LIVE = 1;
private static final int IS_BROWSABLE = 1;
+ /** @hide */
+ @IntDef({
+ TYPE_UNKNOWN,
+ PreviewProgramColumns.TYPE_MOVIE,
+ PreviewProgramColumns.TYPE_TV_SERIES,
+ PreviewProgramColumns.TYPE_TV_SEASON,
+ PreviewProgramColumns.TYPE_TV_EPISODE,
+ PreviewProgramColumns.TYPE_CLIP,
+ PreviewProgramColumns.TYPE_EVENT,
+ PreviewProgramColumns.TYPE_CHANNEL,
+ PreviewProgramColumns.TYPE_TRACK,
+ PreviewProgramColumns.TYPE_ALBUM,
+ PreviewProgramColumns.TYPE_ARTIST,
+ PreviewProgramColumns.TYPE_PLAYLIST,
+ PreviewProgramColumns.TYPE_STATION,
+ PreviewProgramColumns.TYPE_GAME
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface Type {}
+
+ /**
+ * The unknown program type.
+ */
+ private static final int TYPE_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ ASPECT_RATIO_UNKNOWN,
+ PreviewProgramColumns.ASPECT_RATIO_16_9,
+ PreviewProgramColumns.ASPECT_RATIO_3_2,
+ PreviewProgramColumns.ASPECT_RATIO_4_3,
+ PreviewProgramColumns.ASPECT_RATIO_1_1,
+ PreviewProgramColumns.ASPECT_RATIO_2_3,
+ PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface AspectRatio {}
+
+ /**
+ * The aspect ratio for unknown aspect ratios.
+ */
+ private static final int ASPECT_RATIO_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ AVAILABILITY_UNKNOWN,
+ PreviewProgramColumns.AVAILABILITY_AVAILABLE,
+ PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION,
+ PreviewProgramColumns.AVAILABILITY_PAID_CONTENT,
+ PreviewProgramColumns.AVAILABILITY_PURCHASED,
+ PreviewProgramColumns.AVAILABILITY_FREE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface Availability {}
+
+ /**
+ * The unknown availability.
+ */
+ private static final int AVAILABILITY_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ INTERACTION_TYPE_UNKNOWN,
+ PreviewProgramColumns.INTERACTION_TYPE_VIEWS,
+ PreviewProgramColumns.INTERACTION_TYPE_LISTENS,
+ PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS,
+ PreviewProgramColumns.INTERACTION_TYPE_FANS,
+ PreviewProgramColumns.INTERACTION_TYPE_LIKES,
+ PreviewProgramColumns.INTERACTION_TYPE_THUMBS,
+ PreviewProgramColumns.INTERACTION_TYPE_VIEWERS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface InteractionType {}
+
+ /**
+ * The unknown interaction type.
+ */
+ private static final int INTERACTION_TYPE_UNKNOWN = -1;
+
BasePreviewProgram(Builder builder) {
super(builder);
}
@@ -127,7 +209,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @Type int getType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? TYPE_UNKNOWN : i;
}
/**
@@ -137,7 +219,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @AspectRatio int getPosterArtAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? ASPECT_RATIO_UNKNOWN : i;
}
/**
@@ -147,7 +229,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @AspectRatio int getThumbnailAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? ASPECT_RATIO_UNKNOWN : i;
}
/**
@@ -165,7 +247,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @Availability int getAvailability() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_AVAILABILITY);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? AVAILABILITY_UNKNOWN : i;
}
/**
@@ -216,7 +298,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @InteractionType int getInteractionType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_INTERACTION_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? INTERACTION_TYPE_UNKNOWN : i;
}
/**
diff --git a/android/support/media/tv/BaseProgram.java b/android/support/media/tv/BaseProgram.java
index e4ce9d1f..23b5cf9c 100644
--- a/android/support/media/tv/BaseProgram.java
+++ b/android/support/media/tv/BaseProgram.java
@@ -22,13 +22,16 @@ import android.database.Cursor;
import android.media.tv.TvContentRating;
import android.net.Uri;
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.BaseTvColumns;
import android.support.media.tv.TvContractCompat.ProgramColumns;
-import android.support.media.tv.TvContractCompat.ProgramColumns.ReviewRatingStyle;
import android.support.media.tv.TvContractCompat.Programs;
import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Base class for derived classes that want to have common fields for programs defined in
* {@link TvContractCompat}.
@@ -46,6 +49,22 @@ public abstract class BaseProgram {
private static final int IS_SEARCHABLE = 1;
/** @hide */
+ @IntDef({
+ REVIEW_RATING_STYLE_UNKNOWN,
+ ProgramColumns.REVIEW_RATING_STYLE_STARS,
+ ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
+ ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ @interface ReviewRatingStyle {}
+
+ /**
+ * The unknown review rating style.
+ */
+ private static final int REVIEW_RATING_STYLE_UNKNOWN = -1;
+
+ /** @hide */
@RestrictTo(LIBRARY_GROUP)
protected ContentValues mValues;
@@ -254,7 +273,7 @@ public abstract class BaseProgram {
*/
public @ReviewRatingStyle int getReviewRatingStyle() {
Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? REVIEW_RATING_STYLE_UNKNOWN : i;
}
/**
diff --git a/android/support/media/tv/Program.java b/android/support/media/tv/Program.java
index 4e3bd7ac..233f1bab 100644
--- a/android/support/media/tv/Program.java
+++ b/android/support/media/tv/Program.java
@@ -25,6 +25,7 @@ import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.Programs;
+import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
/**
* A convenience class to access {@link TvContractCompat.Programs} entries in the system content
@@ -282,7 +283,7 @@ public final class Program extends BaseProgram implements Comparable<Program> {
* @return This Builder object to allow for chaining of calls to builder methods.
* @see Programs#COLUMN_BROADCAST_GENRE
*/
- public Builder setBroadcastGenres(String[] genres) {
+ public Builder setBroadcastGenres(@Genre String[] genres) {
mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres));
return this;
}
diff --git a/android/support/media/tv/TvContractCompat.java b/android/support/media/tv/TvContractCompat.java
index 5a46e791..de4fd04f 100644
--- a/android/support/media/tv/TvContractCompat.java
+++ b/android/support/media/tv/TvContractCompat.java
@@ -30,7 +30,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.BaseColumns;
-import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
@@ -606,16 +605,6 @@ public final class TvContractCompat {
*/
@RestrictTo(LIBRARY_GROUP)
interface ProgramColumns {
- /** @hide */
- @IntDef({
- REVIEW_RATING_STYLE_STARS,
- REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
- REVIEW_RATING_STYLE_PERCENTAGE,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- @interface ReviewRatingStyle {}
-
/**
* The review rating style for five star rating.
*
@@ -934,27 +923,6 @@ public final class TvContractCompat {
*/
@RestrictTo(LIBRARY_GROUP)
public interface PreviewProgramColumns {
-
- /** @hide */
- @IntDef({
- TYPE_MOVIE,
- TYPE_TV_SERIES,
- TYPE_TV_SEASON,
- TYPE_TV_EPISODE,
- TYPE_CLIP,
- TYPE_EVENT,
- TYPE_CHANNEL,
- TYPE_TRACK,
- TYPE_ALBUM,
- TYPE_ARTIST,
- TYPE_PLAYLIST,
- TYPE_STATION,
- TYPE_GAME
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface Type {}
-
/**
* The program type for movie.
*
@@ -1046,19 +1014,6 @@ public final class TvContractCompat {
*/
int TYPE_GAME = 12;
- /** @hide */
- @IntDef({
- ASPECT_RATIO_16_9,
- ASPECT_RATIO_3_2,
- ASPECT_RATIO_4_3,
- ASPECT_RATIO_1_1,
- ASPECT_RATIO_2_3,
- ASPECT_RATIO_MOVIE_POSTER,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface AspectRatio {}
-
/**
* The aspect ratio for 16:9.
*
@@ -1107,18 +1062,6 @@ public final class TvContractCompat {
*/
int ASPECT_RATIO_MOVIE_POSTER = 5;
- /** @hide */
- @IntDef({
- AVAILABILITY_AVAILABLE,
- AVAILABILITY_FREE_WITH_SUBSCRIPTION,
- AVAILABILITY_PAID_CONTENT,
- AVAILABILITY_PURCHASED,
- AVAILABILITY_FREE,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface Availability {}
-
/**
* The availability for "available to this user".
*
@@ -1155,20 +1098,6 @@ public final class TvContractCompat {
*/
int AVAILABILITY_FREE = 4;
- /** @hide */
- @IntDef({
- INTERACTION_TYPE_VIEWS,
- INTERACTION_TYPE_LISTENS,
- INTERACTION_TYPE_FOLLOWERS,
- INTERACTION_TYPE_FANS,
- INTERACTION_TYPE_LIKES,
- INTERACTION_TYPE_THUMBS,
- INTERACTION_TYPE_VIEWERS,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface InteractionType {}
-
/**
* The interaction type for "views".
*
@@ -2895,17 +2824,6 @@ public final class TvContractCompat {
/** The MIME type of a single preview TV program. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watch_next_program";
- /** @hide */
- @IntDef({
- WATCH_NEXT_TYPE_CONTINUE,
- WATCH_NEXT_TYPE_NEXT,
- WATCH_NEXT_TYPE_NEW,
- WATCH_NEXT_TYPE_WATCHLIST,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface WatchNextType {}
-
/**
* The watch next type for CONTINUE. Use this type when the user has already watched more
* than 1 minute of this content.
diff --git a/android/support/media/tv/WatchNextProgram.java b/android/support/media/tv/WatchNextProgram.java
index f4665846..c192745c 100644
--- a/android/support/media/tv/WatchNextProgram.java
+++ b/android/support/media/tv/WatchNextProgram.java
@@ -22,12 +22,15 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.media.tv.TvContentRating; // For javadoc gen of super class
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.PreviewPrograms; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.Programs; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.Programs.Genres; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.WatchNextPrograms;
-import android.support.media.tv.TvContractCompat.WatchNextPrograms.WatchNextType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* A convenience class to access {@link WatchNextPrograms} entries in the system content
@@ -87,16 +90,34 @@ public final class WatchNextProgram extends BasePreviewProgram {
private static final long INVALID_LONG_VALUE = -1;
private static final int INVALID_INT_VALUE = -1;
+ /** @hide */
+ @IntDef({
+ WATCH_NEXT_TYPE_UNKNOWN,
+ WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE,
+ WatchNextPrograms.WATCH_NEXT_TYPE_NEXT,
+ WatchNextPrograms.WATCH_NEXT_TYPE_NEW,
+ WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface WatchNextType {}
+
+ /**
+ * The unknown watch next type. Use this type when the actual type is not known.
+ */
+ public static final int WATCH_NEXT_TYPE_UNKNOWN = -1;
+
private WatchNextProgram(Builder builder) {
super(builder);
}
/**
- * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program.
+ * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program,
+ * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown.
*/
public @WatchNextType int getWatchNextType() {
Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i;
}
/**
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
index bc35935e..b0e3d5fe 100644
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ b/android/support/mediacompat/testlib/IntentConstants.java
@@ -17,11 +17,18 @@
package android.support.mediacompat.testlib;
/**
- * Constants used for sending intent between client and service apks.
+ * 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/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java
new file mode 100644
index 00000000..1de00efc
--- /dev/null
+++ b/android/support/mediacompat/testlib/MediaControllerConstants.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 android.support.mediacompat.testlib;
+
+/**
+ * Constants for testing the media controller.
+ */
+public class MediaControllerConstants {
+
+ // MediaControllerCompat methods.
+ public static final int SEND_COMMAND = 201;
+ public static final int ADD_QUEUE_ITEM = 202;
+ public static final int ADD_QUEUE_ITEM_WITH_INDEX = 203;
+ public static final int REMOVE_QUEUE_ITEM = 204;
+
+ // TransportControls methods.
+ public static final int PLAY = 301;
+ public static final int PAUSE = 302;
+ public static final int STOP = 303;
+ public static final int FAST_FORWARD = 304;
+ public static final int REWIND = 305;
+ public static final int SKIP_TO_PREVIOUS = 306;
+ public static final int SKIP_TO_NEXT = 307;
+ public static final int SEEK_TO = 308;
+ public static final int SET_RATING = 309;
+ public static final int PLAY_FROM_MEDIA_ID = 310;
+ public static final int PLAY_FROM_SEARCH = 311;
+ public static final int PLAY_FROM_URI = 312;
+ public static final int SEND_CUSTOM_ACTION = 313;
+ public static final int SEND_CUSTOM_ACTION_PARCELABLE = 314;
+ public static final int SKIP_TO_QUEUE_ITEM = 315;
+ public static final int PREPARE = 316;
+ public static final int PREPARE_FROM_MEDIA_ID = 317;
+ public static final int PREPARE_FROM_SEARCH = 318;
+ public static final int PREPARE_FROM_URI = 319;
+ public static final int SET_CAPTIONING_ENABLED = 320;
+ public static final int SET_REPEAT_MODE = 321;
+ public static final int SET_SHUFFLE_MODE = 322;
+}
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
new file mode 100644
index 00000000..79381e5e
--- /dev/null
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib;
+
+/**
+ * Constants for testing the media session.
+ */
+public class MediaSessionConstants {
+
+ // MediaSessionCompat methods.
+ public static final int SET_EXTRAS = 101;
+ public static final int SET_FLAGS = 102;
+ public static final int SET_METADATA = 103;
+ public static final int SET_PLAYBACK_STATE = 104;
+ public static final int SET_QUEUE = 105;
+ public static final int SET_QUEUE_TITLE = 106;
+ public static final int SET_SESSION_ACTIVITY = 107;
+ public static final int SET_CAPTIONING_ENABLED = 108;
+ public static final int SET_REPEAT_MODE = 109;
+ public static final int SET_SHUFFLE_MODE = 110;
+ public static final int SEND_SESSION_EVENT = 112;
+ public static final int SET_ACTIVE = 113;
+ public static final int RELEASE = 114;
+ public static final int SET_PLAYBACK_TO_LOCAL = 115;
+ public static final int SET_PLAYBACK_TO_REMOTE = 116;
+ public static final int SET_RATING_TYPE = 117;
+
+ public static final String 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";
+ public static final String TEST_COMMAND = "test-command";
+ public static final int TEST_FLAGS = 5;
+ public static final int TEST_CURRENT_VOLUME = 10;
+ public static final int TEST_MAX_VOLUME = 11;
+ public static final long TEST_QUEUE_ID_1 = 10L;
+ public static final long TEST_QUEUE_ID_2 = 20L;
+ public static final String TEST_MEDIA_ID_1 = "media_id_1";
+ public static final String TEST_MEDIA_ID_2 = "media_id_2";
+ public static final long TEST_ACTION = 55L;
+
+ public static final int TEST_ERROR_CODE = 0x3;
+ public static final String TEST_ERROR_MSG = "test-error-msg";
+}
diff --git a/android/support/transition/AutoTransition.java b/android/support/transition/AutoTransition.java
index 02b49e26..bf39c3c3 100644
--- a/android/support/transition/AutoTransition.java
+++ b/android/support/transition/AutoTransition.java
@@ -45,9 +45,9 @@ public class AutoTransition extends TransitionSet {
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
- addTransition(new Fade(Fade.OUT)).
- addTransition(new ChangeBounds()).
- addTransition(new Fade(Fade.IN));
+ addTransition(new Fade(Fade.OUT))
+ .addTransition(new ChangeBounds())
+ .addTransition(new Fade(Fade.IN));
}
}
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index 81431972..af37f77a 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2240,10 +2240,24 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary);
appendVisibleItems();
prependVisibleItems();
- removeInvisibleViewsAtFront();
- removeInvisibleViewsAtEnd();
+ // b/67370222: do not removeInvisibleViewsAtFront/End() in the loop, otherwise
+ // loop may bounce between scroll forward and scroll backward forever. Example:
+ // Assuming there are 19 items, child#18 and child#19 are both in RV, we are
+ // trying to focus to child#18 and there are 200px remaining scroll distance.
+ // 1 focusToViewInLayout() tries scroll forward 50 px to align focused child#18 on
+ // right edge, but there to compensate remaining scroll 200px, also scroll
+ // backward 200px, 150px pushes last child#19 out side of right edge.
+ // 2 removeInvisibleViewsAtEnd() remove last child#19, updateScrollLimits()
+ // invalidates scroll max
+ // 3 In next iteration, when scroll max/min is unknown, focusToViewInLayout() will
+ // align focused child#18 at center of screen.
+ // 4 Because #18 is aligned at center, appendVisibleItems() will fill child#19 to
+ // the right.
+ // 5 (back to 1 and loop forever)
} while (mGrid.getFirstVisibleIndex() != oldFirstVisible
|| mGrid.getLastVisibleIndex() != oldLastVisible);
+ removeInvisibleViewsAtFront();
+ removeInvisibleViewsAtEnd();
if (state.willRunPredictiveAnimations()) {
fillScrapViewsInPostLayout();
diff --git a/android/support/v4/content/res/ResourcesCompat.java b/android/support/v4/content/res/ResourcesCompat.java
index 4c70ce93..15b8ce9a 100644
--- a/android/support/v4/content/res/ResourcesCompat.java
+++ b/android/support/v4/content/res/ResourcesCompat.java
@@ -307,11 +307,11 @@ public final class ResourcesCompat {
*/
@RestrictTo(LIBRARY_GROUP)
public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
- int style) throws NotFoundException {
+ int style, @Nullable FontCallback fontCallback) throws NotFoundException {
if (context.isRestricted()) {
return null;
}
- return loadFont(context, id, value, style, null /* callback */, null /* handler */,
+ return loadFont(context, id, value, style, fontCallback, null /* handler */,
true /* isXmlRequest */);
}
diff --git a/android/support/v4/media/RatingCompat.java b/android/support/v4/media/RatingCompat.java
index b538cac4..e70243f8 100644
--- a/android/support/v4/media/RatingCompat.java
+++ b/android/support/v4/media/RatingCompat.java
@@ -18,6 +18,7 @@ package android.support.v4.media;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.media.Rating;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -326,25 +327,25 @@ public final class RatingCompat implements Parcelable {
*/
public static RatingCompat fromRating(Object ratingObj) {
if (ratingObj != null && Build.VERSION.SDK_INT >= 19) {
- final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj);
+ final int ratingStyle = ((Rating) ratingObj).getRatingStyle();
final RatingCompat rating;
- if (RatingCompatKitkat.isRated(ratingObj)) {
+ if (((Rating) ratingObj).isRated()) {
switch (ratingStyle) {
case RATING_HEART:
- rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj));
+ rating = newHeartRating(((Rating) ratingObj).hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj));
+ rating = newThumbRating(((Rating) ratingObj).isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
rating = newStarRating(ratingStyle,
- RatingCompatKitkat.getStarRating(ratingObj));
+ ((Rating) ratingObj).getStarRating());
break;
case RATING_PERCENTAGE:
rating = newPercentageRating(
- RatingCompatKitkat.getPercentRating(ratingObj));
+ ((Rating) ratingObj).getPercentRating());
break;
default:
return null;
@@ -372,25 +373,25 @@ public final class RatingCompat implements Parcelable {
if (isRated()) {
switch (mRatingStyle) {
case RATING_HEART:
- mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart());
+ mRatingObj = Rating.newHeartRating(hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp());
+ mRatingObj = Rating.newThumbRating(isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
- mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle,
+ mRatingObj = Rating.newStarRating(mRatingStyle,
getStarRating());
break;
case RATING_PERCENTAGE:
- mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating());
+ mRatingObj = Rating.newPercentageRating(getPercentRating());
break;
default:
return null;
}
} else {
- mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle);
+ mRatingObj = Rating.newUnratedRating(mRatingStyle);
}
}
return mRatingObj;
diff --git a/android/support/v4/media/RatingCompatKitkat.java b/android/support/v4/media/RatingCompatKitkat.java
deleted file mode 100644
index 1d3fa505..00000000
--- a/android/support/v4/media/RatingCompatKitkat.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.media.Rating;
-import android.support.annotation.RequiresApi;
-
-@RequiresApi(19)
-class RatingCompatKitkat {
- public static Object newUnratedRating(int ratingStyle) {
- return Rating.newUnratedRating(ratingStyle);
- }
-
- public static Object newHeartRating(boolean hasHeart) {
- return Rating.newHeartRating(hasHeart);
- }
-
- public static Object newThumbRating(boolean thumbIsUp) {
- return Rating.newThumbRating(thumbIsUp);
- }
-
- public static Object newStarRating(int starRatingStyle, float starRating) {
- return Rating.newStarRating(starRatingStyle, starRating);
- }
-
- public static Object newPercentageRating(float percent) {
- return Rating.newPercentageRating(percent);
- }
-
- public static boolean isRated(Object ratingObj) {
- return ((Rating)ratingObj).isRated();
- }
-
- public static int getRatingStyle(Object ratingObj) {
- return ((Rating)ratingObj).getRatingStyle();
- }
-
- public static boolean hasHeart(Object ratingObj) {
- return ((Rating)ratingObj).hasHeart();
- }
-
- public static boolean isThumbUp(Object ratingObj) {
- return ((Rating)ratingObj).isThumbUp();
- }
-
- public static float getStarRating(Object ratingObj) {
- return ((Rating)ratingObj).getStarRating();
- }
-
- public static float getPercentRating(Object ratingObj) {
- return ((Rating)ratingObj).getPercentRating();
- }
-}
diff --git a/android/support/v4/provider/FontsContractCompat.java b/android/support/v4/provider/FontsContractCompat.java
index 9ef1b0b0..09261869 100644
--- a/android/support/v4/provider/FontsContractCompat.java
+++ b/android/support/v4/provider/FontsContractCompat.java
@@ -303,6 +303,9 @@ public class FontsContractCompat {
final ArrayList<ReplyCallback<TypefaceResult>> replies;
synchronized (sLock) {
replies = sPendingReplies.get(id);
+ if (replies == null) {
+ return; // Nobody requested replies. Do nothing.
+ }
sPendingReplies.remove(id);
}
for (int i = 0; i < replies.size(); ++i) {
diff --git a/android/support/v7/app/MediaRouteButton.java b/android/support/v7/app/MediaRouteButton.java
index d3f7020b..fdbcf9ad 100644
--- a/android/support/v7/app/MediaRouteButton.java
+++ b/android/support/v7/app/MediaRouteButton.java
@@ -121,8 +121,7 @@ public class MediaRouteButton extends View {
}
public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(MediaRouterThemeHelper.createThemedContext(context, defStyleAttr), attrs,
- defStyleAttr);
+ super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/android/support/v7/app/MediaRouteChooserDialog.java b/android/support/v7/app/MediaRouteChooserDialog.java
index 0ab2eb11..17364efb 100644
--- a/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/android/support/v7/app/MediaRouteChooserDialog.java
@@ -92,10 +92,8 @@ public class MediaRouteChooserDialog extends AppCompatDialog {
}
public MediaRouteChooserDialog(Context context, int theme) {
- // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
- // which may override our style settings. Passes our uppermost theme ID to prevent this.
- super(MediaRouterThemeHelper.createThemedContext(context, theme),
- theme == 0 ? MediaRouterThemeHelper.createThemeForDialog(context, theme) : theme);
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/android/support/v7/app/MediaRouteControllerDialog.java b/android/support/v7/app/MediaRouteControllerDialog.java
index 4b9a17a3..d89bf21e 100644
--- a/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/android/support/v7/app/MediaRouteControllerDialog.java
@@ -201,12 +201,8 @@ public class MediaRouteControllerDialog extends AlertDialog {
}
public MediaRouteControllerDialog(Context context, int theme) {
- // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
- // which may override our style settings. Passes our uppermost theme ID to prevent this.
- super(MediaRouterThemeHelper.createThemedContext(context,
- MediaRouterThemeHelper.getAlertDialogResolvedTheme(context, theme)), theme == 0
- ? MediaRouterThemeHelper.createThemeForDialog(context, MediaRouterThemeHelper
- .getAlertDialogResolvedTheme(context, theme)) : theme);
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
mContext = getContext();
mControllerCallback = new MediaControllerCallback();
diff --git a/android/support/v7/app/MediaRouterThemeHelper.java b/android/support/v7/app/MediaRouterThemeHelper.java
index 9ef218e0..69e40ac7 100644
--- a/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/android/support/v7/app/MediaRouterThemeHelper.java
@@ -42,47 +42,76 @@ final class MediaRouterThemeHelper {
private MediaRouterThemeHelper() {
}
- /**
- * Creates a themed context based on the explicit style resource or the parent context's default
- * theme.
- * <p>
- * The theme which will be applied on top of the parent {@code context}'s theme is determined
- * by the primary color defined in the given {@code style}, or in the parent {@code context}.
+ static Context createThemedButtonContext(Context context) {
+ // Apply base Media Router theme.
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+
+ // Apply custom Media Router theme.
+ int style = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (style != 0) {
+ context = new ContextThemeWrapper(context, style);
+ }
+
+ return context;
+ }
+
+ /*
+ * The following two methods are to be used in conjunction. They should be used to prepare
+ * the context and theme for a super class constructor (the latter method relies on the
+ * former method to properly prepare the context):
+ * super(context = createThemedDialogContext(context, theme),
+ * createThemedDialogStyle(context));
*
- * @param context the parent context
- * @param style the resource ID of the style against which to inflate this context, or
- * {@code 0} to use the parent {@code context}'s default theme.
- * @return The themed context.
+ * It will apply theme in the following order (style lookups will be done in reverse):
+ * 1) Current theme
+ * 2) Supplied theme
+ * 3) Base Media Router theme
+ * 4) Custom Media Router theme, if provided
*/
- static Context createThemedContext(Context context, int style) {
- // First, apply dialog property overlay.
- Context themedContext =
- new ContextThemeWrapper(context, getStyledRouterThemeId(context, style));
- int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
- return customizedThemeId == 0 ? themedContext
- : new ContextThemeWrapper(themedContext, customizedThemeId);
+ static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) {
+ // 1) Current theme is already applied to the context
+
+ // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme)
+ if (theme == 0) {
+ theme = getThemeResource(context, !alertDialog
+ ? android.support.v7.appcompat.R.attr.dialogTheme
+ : android.support.v7.appcompat.R.attr.alertDialogTheme);
+ }
+ // Apply it
+ context = new ContextThemeWrapper(context, theme);
+
+ // 3) If a custom Media Router theme is provided then apply the base theme
+ if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) {
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+ }
+
+ return context;
}
+ // This method should be used in conjunction with the previous method.
+ static int createThemedDialogStyle(Context context) {
+ // 4) Apply the custom Media Router theme
+ int theme = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (theme == 0) {
+ // 3) No custom MediaRouther theme was provided so apply the base theme instead
+ theme = getRouterThemeId(context);
+ }
- /**
- * Creates the theme resource ID intended to be used by dialogs.
- */
- static int createThemeForDialog(Context context, int style) {
- int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
- return customizedThemeId != 0 ? customizedThemeId : getStyledRouterThemeId(context, style);
+ return theme;
}
+ // END. Previous two methods should be used in conjunction.
- public static int getThemeResource(Context context, int attr) {
+ static int getThemeResource(Context context, int attr) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
}
- public static float getDisabledAlpha(Context context) {
+ static float getDisabledAlpha(Context context) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
? value.getFloat() : 0.5f;
}
- public static @ControllerColorType int getControllerColor(Context context, int style) {
+ static @ControllerColorType int getControllerColor(Context context, int style) {
int primaryColor = getThemeColor(context, style,
android.support.v7.appcompat.R.attr.colorPrimary);
if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
@@ -92,7 +121,7 @@ final class MediaRouterThemeHelper {
return COLOR_DARK_ON_LIGHT_BACKGROUND;
}
- public static int getButtonTextColor(Context context) {
+ static int getButtonTextColor(Context context) {
int primaryColor = getThemeColor(context, 0,
android.support.v7.appcompat.R.attr.colorPrimary);
int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
@@ -104,7 +133,7 @@ final class MediaRouterThemeHelper {
return primaryColor;
}
- public static void setMediaControlsBackgroundColor(
+ static void setMediaControlsBackgroundColor(
Context context, View mainControls, View groupControls, boolean hasGroup) {
int primaryColor = getThemeColor(context, 0,
android.support.v7.appcompat.R.attr.colorPrimary);
@@ -124,7 +153,7 @@ final class MediaRouterThemeHelper {
groupControls.setTag(primaryDarkColor);
}
- public static void setVolumeSliderColor(
+ static void setVolumeSliderColor(
Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
int controllerColor = getControllerColor(context, 0);
if (Color.alpha(controllerColor) != 0xFF) {
@@ -136,23 +165,10 @@ final class MediaRouterThemeHelper {
volumeSlider.setColor(controllerColor);
}
- // This is copied from {@link AlertDialog#resolveDialogTheme} to pre-evaluate theme in advance.
- public static int getAlertDialogResolvedTheme(Context context, int themeResId) {
- if (themeResId >= 0x01000000) { // start of real resource IDs.
- return themeResId;
- } else {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(
- android.support.v7.appcompat.R.attr.alertDialogTheme, outValue, true);
- return outValue.resourceId;
- }
- }
-
private static boolean isLightTheme(Context context) {
TypedValue value = new TypedValue();
- return context.getTheme().resolveAttribute(
- android.support.v7.appcompat.R.attr.isLightTheme, value, true)
- && value.data != 0;
+ return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme,
+ value, true) && value.data != 0;
}
private static int getThemeColor(Context context, int style, int attr) {
@@ -173,16 +189,16 @@ final class MediaRouterThemeHelper {
return value.data;
}
- private static int getStyledRouterThemeId(Context context, int style) {
+ private static int getRouterThemeId(Context context) {
int themeId;
if (isLightTheme(context)) {
- if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_Light;
} else {
themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
}
} else {
- if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_LightControlPanel;
} else {
themeId = R.style.Theme_MediaRouter;
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.java b/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.java
deleted file mode 100644
index aab74172..00000000
--- a/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.java
+++ /dev/null
@@ -1,164 +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.support.v7.recyclerview.extensions;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.arch.paging.TestExecutor;
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.v7.util.ListUpdateCallback;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public class ListAdapterHelperTest {
- private TestExecutor mMainThread = new TestExecutor();
- private TestExecutor mBackgroundThread = new TestExecutor();
-
-
- private static final DiffCallback<String> STRING_DIFF_CALLBACK = new DiffCallback<String>() {
- @Override
- public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
- return oldItem.equals(newItem);
- }
-
- @Override
- public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
- return oldItem.equals(newItem);
- }
- };
-
- private static final ListUpdateCallback IGNORE_CALLBACK = new ListUpdateCallback() {
- @Override
- public void onInserted(int position, int count) {
- }
-
- @Override
- public void onRemoved(int position, int count) {
- }
-
- @Override
- public void onMoved(int fromPosition, int toPosition) {
- }
-
- @Override
- public void onChanged(int position, int count, Object payload) {
- }
- };
-
-
- private <T> ListAdapterHelper<T> createHelper(
- ListUpdateCallback listUpdateCallback, DiffCallback<T> diffCallback) {
- return new ListAdapterHelper<T>(listUpdateCallback,
- new ListAdapterConfig.Builder<T>()
- .setDiffCallback(diffCallback)
- .setMainThreadExecutor(mMainThread)
- .setBackgroundThreadExecutor(mBackgroundThread)
- .build());
- }
-
- @Test
- public void initialState() {
- ListUpdateCallback callback = mock(ListUpdateCallback.class);
- ListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
- assertEquals(0, helper.getItemCount());
- verifyZeroInteractions(callback);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getEmpty() {
- ListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
- helper.getItem(0);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getNegative() {
- ListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
- helper.setList(Arrays.asList("a", "b"));
- helper.getItem(-1);
- }
-
- @Test(expected = IndexOutOfBoundsException.class)
- public void getPastEnd() {
- ListAdapterHelper<String> helper = createHelper(IGNORE_CALLBACK, STRING_DIFF_CALLBACK);
- helper.setList(Arrays.asList("a", "b"));
- helper.getItem(2);
- }
-
- @Test
- public void setListSimple() {
- ListUpdateCallback callback = mock(ListUpdateCallback.class);
- ListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
- helper.setList(Arrays.asList("a", "b"));
-
- assertEquals(2, helper.getItemCount());
- assertEquals("a", helper.getItem(0));
- assertEquals("b", helper.getItem(1));
-
- verify(callback).onInserted(0, 2);
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
- }
-
- @Test
- public void setListUpdate() {
- ListUpdateCallback callback = mock(ListUpdateCallback.class);
- ListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
-
- // initial list (immediate)
- helper.setList(Arrays.asList("a", "b"));
- verify(callback).onInserted(0, 2);
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
-
- // update (deferred)
- helper.setList(Arrays.asList("a", "b", "c"));
- verifyNoMoreInteractions(callback);
- drain();
- verify(callback).onInserted(2, 1);
- verifyNoMoreInteractions(callback);
-
- // clear (immediate)
- helper.setList(null);
- verify(callback).onRemoved(0, 3);
- verifyNoMoreInteractions(callback);
- drain();
- verifyNoMoreInteractions(callback);
-
- }
-
- private void drain() {
- boolean executed;
- do {
- executed = mBackgroundThread.executeAll();
- executed |= mMainThread.executeAll();
- } while (executed);
- }
-}
diff --git a/android/support/v7/util/DiffUtil.java b/android/support/v7/util/DiffUtil.java
index 6302666f..ebc33f31 100644
--- a/android/support/v7/util/DiffUtil.java
+++ b/android/support/v7/util/DiffUtil.java
@@ -16,6 +16,7 @@
package android.support.v7.util;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
@@ -348,6 +349,72 @@ public class DiffUtil {
}
/**
+ * Callback for calculating the diff between two non-null items in a list.
+ * <p>
+ * {@link Callback} serves two roles - list indexing, and item diffing. ItemCallback handles
+ * just the second of these, which allows separation of code that indexes into an array or List
+ * from the presentation-layer and content specific diffing code.
+ *
+ * @param <T> Type of items to compare.
+ */
+ public abstract static class ItemCallback<T> {
+ /**
+ * Called to check whether two objects represent the same item.
+ * <p>
+ * For example, if your items have unique ids, this method should check their id equality.
+ *
+ * @param oldItem The item in the old list.
+ * @param newItem The item in the new list.
+ * @return True if the two items represent the same object or false if they are different.
+ *
+ * @see Callback#areItemsTheSame(int, int)
+ */
+ public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+ /**
+ * Called to check whether two items have the same data.
+ * <p>
+ * This information is used to detect if the contents of an item have changed.
+ * <p>
+ * This method to check equality instead of {@link Object#equals(Object)} so that you can
+ * change its behavior depending on your UI.
+ * <p>
+ * For example, if you are using DiffUtil with a
+ * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
+ * return whether the items' visual representations are the same.
+ * <p>
+ * This method is called only if {@link #areItemsTheSame(T, T)} returns {@code true} for
+ * these items.
+ *
+ * @param oldItem The item in the old list.
+ * @param newItem The item in the new list.
+ * @return True if the contents of the items are the same or false if they are different.
+ *
+ * @see Callback#areContentsTheSame(int, int)
+ */
+ public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+ /**
+ * When {@link #areItemsTheSame(T, T)} returns {@code true} for two items and
+ * {@link #areContentsTheSame(T, T)} returns false for them, this method is called to
+ * get a payload about the change.
+ * <p>
+ * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
+ * particular field that changed in the item and your
+ * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+ * information to run the correct animation.
+ * <p>
+ * Default implementation returns {@code null}.
+ *
+ * @see Callback#getChangePayload(int, int)
+ */
+ @SuppressWarnings({"WeakerAccess", "unused"})
+ public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) {
+ return null;
+ }
+ }
+
+ /**
* Snakes represent a match between two lists. It is optionally prefixed or postfixed with an
* add or remove operation. See the Myers' paper for details.
*/
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index 51510aa2..fa6196f5 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -29,6 +29,7 @@ import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.appcompat.R;
import android.text.method.PasswordTransformationMethod;
@@ -36,6 +37,8 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
+import java.lang.ref.WeakReference;
+
@RequiresApi(9)
class AppCompatTextHelper {
@@ -63,6 +66,7 @@ class AppCompatTextHelper {
private int mStyle = Typeface.NORMAL;
private Typeface mFontTypeface;
+ private boolean mAsyncFontPending;
AppCompatTextHelper(TextView view) {
mView = view;
@@ -213,8 +217,23 @@ class AppCompatTextHelper {
? R.styleable.TextAppearance_android_fontFamily
: R.styleable.TextAppearance_fontFamily;
if (!context.isRestricted()) {
+ final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
+ ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
+ @Override
+ public void onFontRetrieved(@NonNull Typeface typeface) {
+ onAsyncTypefaceReceived(textViewWeak, typeface);
+ }
+
+ @Override
+ public void onFontRetrievalFailed(int reason) {
+ // Do nothing.
+ }
+ };
try {
- mFontTypeface = a.getFont(fontFamilyId, mStyle);
+ // Note the callback will be triggered on the UI thread.
+ mFontTypeface = a.getFont(fontFamilyId, mStyle, replyCallback);
+ // If this call gave us an immediate result, ignore any pending callbacks.
+ mAsyncFontPending = mFontTypeface == null;
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
// Expected if it is not a font resource.
}
@@ -222,12 +241,16 @@ class AppCompatTextHelper {
if (mFontTypeface == null) {
// Try with String. This is done by TextView JB+, but fails in ICS
String fontFamilyName = a.getString(fontFamilyId);
- mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+ if (fontFamilyName != null) {
+ mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+ }
}
return;
}
if (a.hasValue(R.styleable.TextAppearance_android_typeface)) {
+ // Ignore previous pending fonts
+ mAsyncFontPending = false;
int typefaceIndex = a.getInt(R.styleable.TextAppearance_android_typeface, SANS);
switch (typefaceIndex) {
case SANS:
@@ -245,6 +268,16 @@ class AppCompatTextHelper {
}
}
+ private void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, Typeface typeface) {
+ if (mAsyncFontPending) {
+ mFontTypeface = typeface;
+ final TextView textView = textViewWeak.get();
+ if (textView != null) {
+ textView.setTypeface(typeface, mStyle);
+ }
+ }
+ }
+
void onSetTextAppearance(Context context, int resId) {
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
resId, R.styleable.TextAppearance);
diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java
index 7009733c..4bc17a86 100644
--- a/android/support/v7/widget/RecyclerView.java
+++ b/android/support/v7/widget/RecyclerView.java
@@ -6408,16 +6408,16 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
void markKnownViewsInvalid() {
- if (mAdapter != null && mAdapter.hasStableIds()) {
- final int cachedCount = mCachedViews.size();
- for (int i = 0; i < cachedCount; i++) {
- final ViewHolder holder = mCachedViews.get(i);
- if (holder != null) {
- holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
- holder.addChangePayload(null);
- }
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+ holder.addChangePayload(null);
}
- } else {
+ }
+
+ if (mAdapter == null || !mAdapter.hasStableIds()) {
// we cannot re-use cached views in this case. Recycle them all
recycleAndClearCachedViews();
}
diff --git a/android/support/v7/widget/TintTypedArray.java b/android/support/v7/widget/TintTypedArray.java
index 22709551..384c4615 100644
--- a/android/support/v7/widget/TintTypedArray.java
+++ b/android/support/v7/widget/TintTypedArray.java
@@ -106,7 +106,8 @@ public class TintTypedArray {
* not a font resource.
*/
@Nullable
- public Typeface getFont(@StyleableRes int index, int style) {
+ public Typeface getFont(@StyleableRes int index, int style,
+ @Nullable ResourcesCompat.FontCallback fontCallback) {
final int resourceId = mWrapped.getResourceId(index, 0);
if (resourceId == 0) {
return null;
@@ -114,7 +115,7 @@ public class TintTypedArray {
if (mTypedValue == null) {
mTypedValue = new TypedValue();
}
- return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
+ return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, fontCallback);
}
public int length() {
diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java
index 7fbbbb3f..db53dfc1 100644
--- a/android/support/wear/ambient/AmbientMode.java
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -22,7 +22,6 @@ import android.content.Context;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.VisibleForTesting;
-import android.util.Log;
import com.google.android.wearable.compat.WearableActivityController;
@@ -263,20 +262,6 @@ public final class AmbientMode extends Fragment {
AmbientController() {}
/**
- * Sets whether this activity's task should be moved to the front when the system exits
- * ambient mode. If true, the activity's task may be moved to the front if it was the last
- * activity to be running when ambient started, depending on how much time the system spent
- * in ambient mode.
- */
- public void setAutoResumeEnabled(boolean enabled) {
- if (mDelegate != null) {
- mDelegate.setAutoResumeEnabled(enabled);
- } else {
- Log.w(TAG, "The fragment is not yet fully initialized, this call is a no-op");
- }
- }
-
- /**
* @return {@code true} if the activity is currently in ambient.
*/
public boolean isAmbient() {
diff --git a/android/system/ErrnoException.java b/android/system/ErrnoException.java
index 90155c89..e60ac8f3 100644
--- a/android/system/ErrnoException.java
+++ b/android/system/ErrnoException.java
@@ -26,57 +26,57 @@ import libcore.io.Libcore;
* callers need to adjust their behavior based on the exact failure.
*/
public final class ErrnoException extends Exception {
- private final String functionName;
+ private final String functionName;
- /**
- * The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
- */
- public final int errno;
+ /**
+ * The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
+ */
+ public final int errno;
- /**
- * Constructs an instance with the given function name and errno value.
- */
- public ErrnoException(String functionName, int errno) {
- this.functionName = functionName;
- this.errno = errno;
- }
+ /**
+ * Constructs an instance with the given function name and errno value.
+ */
+ public ErrnoException(String functionName, int errno) {
+ this.functionName = functionName;
+ this.errno = errno;
+ }
- /**
- * Constructs an instance with the given function name, errno value, and cause.
- */
- public ErrnoException(String functionName, int errno, Throwable cause) {
- super(cause);
- this.functionName = functionName;
- this.errno = errno;
- }
+ /**
+ * Constructs an instance with the given function name, errno value, and cause.
+ */
+ public ErrnoException(String functionName, int errno, Throwable cause) {
+ super(cause);
+ this.functionName = functionName;
+ this.errno = errno;
+ }
- /**
- * Converts the stashed function name and errno value to a human-readable string.
- * We do this here rather than in the constructor so that callers only pay for
- * this if they need it.
- */
- @Override public String getMessage() {
- String errnoName = OsConstants.errnoName(errno);
- if (errnoName == null) {
- errnoName = "errno " + errno;
+ /**
+ * Converts the stashed function name and errno value to a human-readable string.
+ * We do this here rather than in the constructor so that callers only pay for
+ * this if they need it.
+ */
+ @Override public String getMessage() {
+ String errnoName = OsConstants.errnoName(errno);
+ if (errnoName == null) {
+ errnoName = "errno " + errno;
+ }
+ String description = Libcore.os.strerror(errno);
+ return functionName + " failed: " + errnoName + " (" + description + ")";
}
- String description = Libcore.os.strerror(errno);
- return functionName + " failed: " + errnoName + " (" + description + ")";
- }
- /**
- * @hide - internal use only.
- */
- public IOException rethrowAsIOException() throws IOException {
- IOException newException = new IOException(getMessage());
- newException.initCause(this);
- throw newException;
- }
+ /**
+ * @hide - internal use only.
+ */
+ public IOException rethrowAsIOException() throws IOException {
+ IOException newException = new IOException(getMessage());
+ newException.initCause(this);
+ throw newException;
+ }
- /**
- * @hide - internal use only.
- */
- public SocketException rethrowAsSocketException() throws SocketException {
- throw new SocketException(getMessage(), this);
- }
+ /**
+ * @hide - internal use only.
+ */
+ public SocketException rethrowAsSocketException() throws SocketException {
+ throw new SocketException(getMessage(), this);
+ }
}
diff --git a/android/system/GaiException.java b/android/system/GaiException.java
index dc105668..182cc3eb 100644
--- a/android/system/GaiException.java
+++ b/android/system/GaiException.java
@@ -27,57 +27,57 @@ import libcore.io.Libcore;
* @hide
*/
public final class GaiException extends RuntimeException {
- private final String functionName;
+ private final String functionName;
- /**
- * The native error value, for comparison with the {@code GAI_} constants in {@link OsConstants}.
- */
- public final int error;
+ /**
+ * The native error value, for comparison with the {@code GAI_} constants in {@link OsConstants}.
+ */
+ public final int error;
- /**
- * Constructs an instance with the given function name and error value.
- */
- public GaiException(String functionName, int error) {
- this.functionName = functionName;
- this.error = error;
- }
+ /**
+ * Constructs an instance with the given function name and error value.
+ */
+ public GaiException(String functionName, int error) {
+ this.functionName = functionName;
+ this.error = error;
+ }
- /**
- * Constructs an instance with the given function name, error value, and cause.
- */
- public GaiException(String functionName, int error, Throwable cause) {
- super(cause);
- this.functionName = functionName;
- this.error = error;
- }
+ /**
+ * Constructs an instance with the given function name, error value, and cause.
+ */
+ public GaiException(String functionName, int error, Throwable cause) {
+ super(cause);
+ this.functionName = functionName;
+ this.error = error;
+ }
- /**
- * Converts the stashed function name and error value to a human-readable string.
- * We do this here rather than in the constructor so that callers only pay for
- * this if they need it.
- */
- @Override public String getMessage() {
- String gaiName = OsConstants.gaiName(error);
- if (gaiName == null) {
- gaiName = "GAI_ error " + error;
+ /**
+ * Converts the stashed function name and error value to a human-readable string.
+ * We do this here rather than in the constructor so that callers only pay for
+ * this if they need it.
+ */
+ @Override public String getMessage() {
+ String gaiName = OsConstants.gaiName(error);
+ if (gaiName == null) {
+ gaiName = "GAI_ error " + error;
+ }
+ String description = Libcore.os.gai_strerror(error);
+ return functionName + " failed: " + gaiName + " (" + description + ")";
}
- String description = Libcore.os.gai_strerror(error);
- return functionName + " failed: " + gaiName + " (" + description + ")";
- }
- /**
- * @hide - internal use only.
- */
- public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
- UnknownHostException newException = new UnknownHostException(detailMessage);
- newException.initCause(this);
- throw newException;
- }
+ /**
+ * @hide - internal use only.
+ */
+ public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
+ UnknownHostException newException = new UnknownHostException(detailMessage);
+ newException.initCause(this);
+ throw newException;
+ }
- /**
- * @hide - internal use only.
- */
- public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
- throw rethrowAsUnknownHostException(getMessage());
- }
+ /**
+ * @hide - internal use only.
+ */
+ public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
+ throw rethrowAsUnknownHostException(getMessage());
+ }
}
diff --git a/android/system/Os.java b/android/system/Os.java
index 8e312ddc..2dabae2f 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -35,592 +35,597 @@ import libcore.io.Libcore;
* <p>The corresponding constants can be found in {@link OsConstants}.
*/
public final class Os {
- private Os() {}
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/accept.2.html">accept(2)</a>.
- */
- public static FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
-
- /**
- * TODO Change the public API by removing the overload above and unhiding this version.
- * @hide
- */
- public static FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/access.2.html">access(2)</a>.
- */
- public static boolean access(String path, int mode) throws ErrnoException { return Libcore.os.access(path, mode); }
-
- /** @hide */ public static InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return Libcore.os.android_getaddrinfo(node, hints, netId); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/bind.2.html">bind(2)</a>.
- */
- public static void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.bind(fd, address, port); }
-
- /** @hide */ public static void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.bind(fd, address); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
- *
- * @hide
- */
- public static StructCapUserData[] capget(StructCapUserHeader hdr) throws ErrnoException {
- return Libcore.os.capget(hdr);
- }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/capset.2.html">capset(2)</a>.
- *
- * @hide
- */
- public static void capset(StructCapUserHeader hdr, StructCapUserData[] data)
- throws ErrnoException {
- Libcore.os.capset(hdr, data);
- }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/chmod.2.html">chmod(2)</a>.
- */
- public static void chmod(String path, int mode) throws ErrnoException { Libcore.os.chmod(path, mode); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/chown.2.html">chown(2)</a>.
- */
- public static void chown(String path, int uid, int gid) throws ErrnoException { Libcore.os.chown(path, uid, gid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/close.2.html">close(2)</a>.
- */
- public static void close(FileDescriptor fd) throws ErrnoException { Libcore.os.close(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/connect.2.html">connect(2)</a>.
- */
- public static void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.connect(fd, address, port); }
-
- /** @hide */ public static void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.connect(fd, address); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/dup.2.html">dup(2)</a>.
- */
- public static FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return Libcore.os.dup(oldFd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/dup2.2.html">dup2(2)</a>.
- */
- public static FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return Libcore.os.dup2(oldFd, newFd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/environ.3.html">environ(3)</a>.
- */
- public static String[] environ() { return Libcore.os.environ(); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/execv.2.html">execv(2)</a>.
- */
- public static void execv(String filename, String[] argv) throws ErrnoException { Libcore.os.execv(filename, argv); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/execve.2.html">execve(2)</a>.
- */
- public static void execve(String filename, String[] argv, String[] envp) throws ErrnoException { Libcore.os.execve(filename, argv, envp); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/fchmod.2.html">fchmod(2)</a>.
- */
- public static void fchmod(FileDescriptor fd, int mode) throws ErrnoException { Libcore.os.fchmod(fd, mode); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/fchown.2.html">fchown(2)</a>.
- */
- public static void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { Libcore.os.fchown(fd, uid, gid); }
-
- /** @hide */ public static int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException { return Libcore.os.fcntlFlock(fd, cmd, arg); }
- /** @hide */ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { return Libcore.os.fcntlInt(fd, cmd, arg); }
- /** @hide */ public static int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return Libcore.os.fcntlVoid(fd, cmd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/fdatasync.2.html">fdatasync(2)</a>.
- */
- public static void fdatasync(FileDescriptor fd) throws ErrnoException { Libcore.os.fdatasync(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/fstat.2.html">fstat(2)</a>.
- */
- public static StructStat fstat(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstat(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/fstatvfs.2.html">fstatvfs(2)</a>.
- */
- public static StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstatvfs(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/fsync.2.html">fsync(2)</a>.
- */
- public static void fsync(FileDescriptor fd) throws ErrnoException { Libcore.os.fsync(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/ftruncate.2.html">ftruncate(2)</a>.
- */
- public static void ftruncate(FileDescriptor fd, long length) throws ErrnoException { Libcore.os.ftruncate(fd, length); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/gai_strerror.3.html">gai_strerror(3)</a>.
- */
- public static String gai_strerror(int error) { return Libcore.os.gai_strerror(error); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getegid.2.html">getegid(2)</a>.
- */
- public static int getegid() { return Libcore.os.getegid(); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/geteuid.2.html">geteuid(2)</a>.
- */
- public static int geteuid() { return Libcore.os.geteuid(); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getgid.2.html">getgid(2)</a>.
- */
- public static int getgid() { return Libcore.os.getgid(); }
-
- /**
- * 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); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/getifaddrs.3.html">getifaddrs(3)</a>.
- */
- /** @hide */ public static StructIfaddrs[] getifaddrs() throws ErrnoException { return Libcore.os.getifaddrs(); }
-
- /** @hide */ public static String getnameinfo(InetAddress address, int flags) throws GaiException { return Libcore.os.getnameinfo(address, flags); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getpeername.2.html">getpeername(2)</a>.
- */
- public static SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return Libcore.os.getpeername(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getpgid.2.html">getpgid(2)</a>.
- */
- /** @hide */ public static int getpgid(int pid) throws ErrnoException { return Libcore.os.getpgid(pid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getpid.2.html">getpid(2)</a>.
- */
- public static int getpid() { return Libcore.os.getpid(); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getppid.2.html">getppid(2)</a>.
- */
- public static int getppid() { return Libcore.os.getppid(); }
-
- /** @hide */ public static StructPasswd getpwnam(String name) throws ErrnoException { return Libcore.os.getpwnam(name); }
-
- /** @hide */ public static StructPasswd getpwuid(int uid) throws ErrnoException { return Libcore.os.getpwuid(uid); }
-
- /** @hide */ public static StructRlimit getrlimit(int resource) throws ErrnoException { return Libcore.os.getrlimit(resource); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getsockname.2.html">getsockname(2)</a>.
- */
- public static SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return Libcore.os.getsockname(fd); }
-
- /** @hide */ public static int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptByte(fd, level, option); }
- /** @hide */ public static InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInAddr(fd, level, option); }
- /** @hide */ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInt(fd, level, option); }
- /** @hide */ public static StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptLinger(fd, level, option); }
- /** @hide */ public static StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptTimeval(fd, level, option); }
- /** @hide */ public static StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptUcred(fd, level, option); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/gettid.2.html">gettid(2)</a>.
- */
- public static int gettid() { return Libcore.os.gettid(); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getuid.2.html">getuid(2)</a>.
- */
- public static int getuid() { return Libcore.os.getuid(); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/getxattr.2.html">getxattr(2)</a>
- */
- public static byte[] getxattr(String path, String name) throws ErrnoException { return Libcore.os.getxattr(path, name); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/if_indextoname.3.html">if_indextoname(3)</a>.
- */
- public static String if_indextoname(int index) { return Libcore.os.if_indextoname(index); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/if_nametoindex.3.html">if_nametoindex(3)</a>.
- */
- public static int if_nametoindex(String name) { return Libcore.os.if_nametoindex(name); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/inet_pton.3.html">inet_pton(3)</a>.
- */
- 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); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
- */
- public static boolean isatty(FileDescriptor fd) { return Libcore.os.isatty(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/kill.2.html">kill(2)</a>.
- */
- public static void kill(int pid, int signal) throws ErrnoException { Libcore.os.kill(pid, signal); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/lchown.2.html">lchown(2)</a>.
- */
- public static void lchown(String path, int uid, int gid) throws ErrnoException { Libcore.os.lchown(path, uid, gid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/link.2.html">link(2)</a>.
- */
- public static void link(String oldPath, String newPath) throws ErrnoException { Libcore.os.link(oldPath, newPath); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/listen.2.html">listen(2)</a>.
- */
- public static void listen(FileDescriptor fd, int backlog) throws ErrnoException { Libcore.os.listen(fd, backlog); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/listxattr.2.html">listxattr(2)</a>
- */
- public static String[] listxattr(String path) throws ErrnoException { return Libcore.os.listxattr(path); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/lseek.2.html">lseek(2)</a>.
- */
- public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return Libcore.os.lseek(fd, offset, whence); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/lstat.2.html">lstat(2)</a>.
- */
- public static StructStat lstat(String path) throws ErrnoException { return Libcore.os.lstat(path); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/mincore.2.html">mincore(2)</a>.
- */
- public static void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { Libcore.os.mincore(address, byteCount, vector); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/mkdir.2.html">mkdir(2)</a>.
- */
- public static void mkdir(String path, int mode) throws ErrnoException { Libcore.os.mkdir(path, mode); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/mkfifo.3.html">mkfifo(3)</a>.
- */
- public static void mkfifo(String path, int mode) throws ErrnoException { Libcore.os.mkfifo(path, mode); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/mlock.2.html">mlock(2)</a>.
- */
- public static void mlock(long address, long byteCount) throws ErrnoException { Libcore.os.mlock(address, byteCount); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>.
- */
- public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/msync.2.html">msync(2)</a>.
- */
- public static void msync(long address, long byteCount, int flags) throws ErrnoException { Libcore.os.msync(address, byteCount, flags); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/munlock.2.html">munlock(2)</a>.
- */
- public static void munlock(long address, long byteCount) throws ErrnoException { Libcore.os.munlock(address, byteCount); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/munmap.2.html">munmap(2)</a>.
- */
- public static void munmap(long address, long byteCount) throws ErrnoException { Libcore.os.munmap(address, byteCount); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>.
- */
- public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return Libcore.os.open(path, flags, mode); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/pipe.2.html">pipe(2)</a>.
- */
- public static FileDescriptor[] pipe() throws ErrnoException { return Libcore.os.pipe2(0); }
-
- /** @hide */ public static FileDescriptor[] pipe2(int flags) throws ErrnoException { return Libcore.os.pipe2(flags); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/poll.2.html">poll(2)</a>.
- *
- * <p>Note that in Lollipop this could throw an {@code ErrnoException} with {@code EINTR}.
- * In later releases, the implementation will automatically just restart the system call with
- * an appropriately reduced timeout.
- */
- public static int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return Libcore.os.poll(fds, timeoutMs); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/posix_fallocate.3.html">posix_fallocate(3)</a>.
- */
- public static void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { Libcore.os.posix_fallocate(fd, offset, length); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>.
- */
- public static int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return Libcore.os.prctl(option, arg2, arg3, arg4, arg5); };
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
- */
- public static int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, buffer, offset); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
- */
- public static int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, bytes, byteOffset, byteCount, offset); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
- */
- public static int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, buffer, offset); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
- */
- public static int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
- */
- public static int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, buffer); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
- */
- public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, bytes, byteOffset, byteCount); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/readlink.2.html">readlink(2)</a>.
- */
- public static String readlink(String path) throws ErrnoException { return Libcore.os.readlink(path); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/readv.2.html">readv(2)</a>.
- */
- public static int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.readv(fd, buffers, offsets, byteCounts); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
- */
- public static int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, buffer, flags, srcAddress); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
- */
- public static int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/remove.3.html">remove(3)</a>.
- */
- public static void remove(String path) throws ErrnoException { Libcore.os.remove(path); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/removexattr.2.html">removexattr(2)</a>.
- */
- public static void removexattr(String path, String name) throws ErrnoException { Libcore.os.removexattr(path, name); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a>.
- */
- public static void rename(String oldPath, String newPath) throws ErrnoException { Libcore.os.rename(oldPath, newPath); }
-
- /**
- * 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); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
- */
- public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, buffer, flags, inetAddress, port); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
- */
- public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
- */
- /** @hide */ public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, address); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setegid.2.html">setegid(2)</a>.
- */
- public static void setegid(int egid) throws ErrnoException { Libcore.os.setegid(egid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/setenv.3.html">setenv(3)</a>.
- */
- public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { Libcore.os.setenv(name, value, overwrite); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/seteuid.2.html">seteuid(2)</a>.
- */
- public static void seteuid(int euid) throws ErrnoException { Libcore.os.seteuid(euid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setgid.2.html">setgid(2)</a>.
- */
- public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
-
- /**
- * 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); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setregid.2.html">setregid(2)</a>.
- */
- /** @hide */ public static void setregid(int rgid, int egid) throws ErrnoException { Libcore.os.setregid(rgid, egid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setreuid.2.html">setreuid(2)</a>.
- */
- /** @hide */ public static void setreuid(int ruid, int euid) throws ErrnoException { Libcore.os.setreuid(ruid, euid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setsid.2.html">setsid(2)</a>.
- */
- public static int setsid() throws ErrnoException { return Libcore.os.setsid(); }
-
- /** @hide */ public static void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptByte(fd, level, option, value); }
- /** @hide */ public static void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { Libcore.os.setsockoptIfreq(fd, level, option, value); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setsockopt.2.html">setsockopt(2)</a>.
- */
- public static void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptInt(fd, level, option, value); }
-
- /** @hide */ public static void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptIpMreqn(fd, level, option, value); }
- /** @hide */ public static void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { Libcore.os.setsockoptGroupReq(fd, level, option, value); }
- /** @hide */ public static void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { Libcore.os.setsockoptLinger(fd, level, option, value); }
- /** @hide */ public static void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { Libcore.os.setsockoptTimeval(fd, level, option, value); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setuid.2.html">setuid(2)</a>.
- */
- public static void setuid(int uid) throws ErrnoException { Libcore.os.setuid(uid); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/setxattr.2.html">setxattr(2)</a>
- */
- public static void setxattr(String path, String name, byte[] value, int flags) throws ErrnoException { Libcore.os.setxattr(path, name, value, flags); };
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/shutdown.2.html">shutdown(2)</a>.
- */
- public static void shutdown(FileDescriptor fd, int how) throws ErrnoException { Libcore.os.shutdown(fd, how); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/socket.2.html">socket(2)</a>.
- */
- public static FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return Libcore.os.socket(domain, type, protocol); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/socketpair.2.html">socketpair(2)</a>.
- */
- public static void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException { Libcore.os.socketpair(domain, type, protocol, fd1, fd2); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/stat.2.html">stat(2)</a>.
- */
- public static StructStat stat(String path) throws ErrnoException { return Libcore.os.stat(path); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/statvfs.2.html">statvfs(2)</a>.
- */
- public static StructStatVfs statvfs(String path) throws ErrnoException { return Libcore.os.statvfs(path); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>.
- */
- public static String strerror(int errno) { return Libcore.os.strerror(errno); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/strsignal.3.html">strsignal(3)</a>.
- */
- public static String strsignal(int signal) { return Libcore.os.strsignal(signal); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/symlink.2.html">symlink(2)</a>.
- */
- public static void symlink(String oldPath, String newPath) throws ErrnoException { Libcore.os.symlink(oldPath, newPath); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/sysconf.3.html">sysconf(3)</a>.
- */
- public static long sysconf(int name) { return Libcore.os.sysconf(name); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/tcdrain.3.html">tcdrain(3)</a>.
- */
- public static void tcdrain(FileDescriptor fd) throws ErrnoException { Libcore.os.tcdrain(fd); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/tcsendbreak.3.html">tcsendbreak(3)</a>.
- */
- public static void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException { Libcore.os.tcsendbreak(fd, duration); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/umask.2.html">umask(2)</a>.
- */
- public static int umask(int mask) { return Libcore.os.umask(mask); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/uname.2.html">uname(2)</a>.
- */
- public static StructUtsname uname() { return Libcore.os.uname(); }
-
- /**
- * @hide See <a href="http://man7.org/linux/man-pages/man2/unlink.2.html">unlink(2)</a>.
- */
- public static void unlink(String pathname) throws ErrnoException { Libcore.os.unlink(pathname); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man3/unsetenv.3.html">unsetenv(3)</a>.
- */
- public static void unsetenv(String name) throws ErrnoException { Libcore.os.unsetenv(name); }
-
- /**
- * 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); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
- */
- public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, buffer); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
- */
- public static int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, bytes, byteOffset, byteCount); }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/writev.2.html">writev(2)</a>.
- */
- public static int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.writev(fd, buffers, offsets, byteCounts); }
+ private Os() {}
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/accept.2.html">accept(2)</a>.
+ */
+ public static FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
+
+ /**
+ * TODO Change the public API by removing the overload above and unhiding this version.
+ * @hide
+ */
+ public static FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/access.2.html">access(2)</a>.
+ */
+ public static boolean access(String path, int mode) throws ErrnoException { return Libcore.os.access(path, mode); }
+
+ /** @hide */ public static InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return Libcore.os.android_getaddrinfo(node, hints, netId); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/bind.2.html">bind(2)</a>.
+ */
+ public static void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.bind(fd, address, port); }
+
+ /** @hide */ public static void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.bind(fd, address); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
+ *
+ * @hide
+ */
+ public static StructCapUserData[] capget(StructCapUserHeader hdr) throws ErrnoException {
+ return Libcore.os.capget(hdr);
+ }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/capset.2.html">capset(2)</a>.
+ *
+ * @hide
+ */
+ public static void capset(StructCapUserHeader hdr, StructCapUserData[] data)
+ throws ErrnoException {
+ Libcore.os.capset(hdr, data);
+ }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/chmod.2.html">chmod(2)</a>.
+ */
+ public static void chmod(String path, int mode) throws ErrnoException { Libcore.os.chmod(path, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/chown.2.html">chown(2)</a>.
+ */
+ public static void chown(String path, int uid, int gid) throws ErrnoException { Libcore.os.chown(path, uid, gid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/close.2.html">close(2)</a>.
+ */
+ public static void close(FileDescriptor fd) throws ErrnoException { Libcore.os.close(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/connect.2.html">connect(2)</a>.
+ */
+ public static void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.connect(fd, address, port); }
+
+ /** @hide */ public static void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.connect(fd, address); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/dup.2.html">dup(2)</a>.
+ */
+ public static FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return Libcore.os.dup(oldFd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/dup2.2.html">dup2(2)</a>.
+ */
+ public static FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return Libcore.os.dup2(oldFd, newFd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/environ.3.html">environ(3)</a>.
+ */
+ public static String[] environ() { return Libcore.os.environ(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/execv.2.html">execv(2)</a>.
+ */
+ public static void execv(String filename, String[] argv) throws ErrnoException { Libcore.os.execv(filename, argv); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/execve.2.html">execve(2)</a>.
+ */
+ public static void execve(String filename, String[] argv, String[] envp) throws ErrnoException { Libcore.os.execve(filename, argv, envp); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fchmod.2.html">fchmod(2)</a>.
+ */
+ public static void fchmod(FileDescriptor fd, int mode) throws ErrnoException { Libcore.os.fchmod(fd, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fchown.2.html">fchown(2)</a>.
+ */
+ public static void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { Libcore.os.fchown(fd, uid, gid); }
+
+ /** @hide */ public static int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException { return Libcore.os.fcntlFlock(fd, cmd, arg); }
+ /** @hide */ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { return Libcore.os.fcntlInt(fd, cmd, arg); }
+ /** @hide */ public static int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return Libcore.os.fcntlVoid(fd, cmd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fdatasync.2.html">fdatasync(2)</a>.
+ */
+ public static void fdatasync(FileDescriptor fd) throws ErrnoException { Libcore.os.fdatasync(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fstat.2.html">fstat(2)</a>.
+ */
+ public static StructStat fstat(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstat(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fstatvfs.2.html">fstatvfs(2)</a>.
+ */
+ public static StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstatvfs(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fsync.2.html">fsync(2)</a>.
+ */
+ public static void fsync(FileDescriptor fd) throws ErrnoException { Libcore.os.fsync(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/ftruncate.2.html">ftruncate(2)</a>.
+ */
+ public static void ftruncate(FileDescriptor fd, long length) throws ErrnoException { Libcore.os.ftruncate(fd, length); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/gai_strerror.3.html">gai_strerror(3)</a>.
+ */
+ public static String gai_strerror(int error) { return Libcore.os.gai_strerror(error); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getegid.2.html">getegid(2)</a>.
+ */
+ public static int getegid() { return Libcore.os.getegid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/geteuid.2.html">geteuid(2)</a>.
+ */
+ public static int geteuid() { return Libcore.os.geteuid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getgid.2.html">getgid(2)</a>.
+ */
+ public static int getgid() { return Libcore.os.getgid(); }
+
+ /**
+ * 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); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/getifaddrs.3.html">getifaddrs(3)</a>.
+ */
+ /** @hide */ public static StructIfaddrs[] getifaddrs() throws ErrnoException { return Libcore.os.getifaddrs(); }
+
+ /** @hide */ public static String getnameinfo(InetAddress address, int flags) throws GaiException { return Libcore.os.getnameinfo(address, flags); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getpeername.2.html">getpeername(2)</a>.
+ */
+ public static SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return Libcore.os.getpeername(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getpgid.2.html">getpgid(2)</a>.
+ */
+ /** @hide */ public static int getpgid(int pid) throws ErrnoException { return Libcore.os.getpgid(pid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getpid.2.html">getpid(2)</a>.
+ */
+ public static int getpid() { return Libcore.os.getpid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getppid.2.html">getppid(2)</a>.
+ */
+ public static int getppid() { return Libcore.os.getppid(); }
+
+ /** @hide */ public static StructPasswd getpwnam(String name) throws ErrnoException { return Libcore.os.getpwnam(name); }
+
+ /** @hide */ public static StructPasswd getpwuid(int uid) throws ErrnoException { return Libcore.os.getpwuid(uid); }
+
+ /** @hide */ public static StructRlimit getrlimit(int resource) throws ErrnoException { return Libcore.os.getrlimit(resource); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getsockname.2.html">getsockname(2)</a>.
+ */
+ public static SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return Libcore.os.getsockname(fd); }
+
+ /** @hide */ public static int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptByte(fd, level, option); }
+ /** @hide */ public static InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInAddr(fd, level, option); }
+ /** @hide */ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInt(fd, level, option); }
+ /** @hide */ public static StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptLinger(fd, level, option); }
+ /** @hide */ public static StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptTimeval(fd, level, option); }
+ /** @hide */ public static StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptUcred(fd, level, option); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/gettid.2.html">gettid(2)</a>.
+ */
+ public static int gettid() { return Libcore.os.gettid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getuid.2.html">getuid(2)</a>.
+ */
+ public static int getuid() { return Libcore.os.getuid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getxattr.2.html">getxattr(2)</a>
+ */
+ public static byte[] getxattr(String path, String name) throws ErrnoException { return Libcore.os.getxattr(path, name); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/if_indextoname.3.html">if_indextoname(3)</a>.
+ */
+ public static String if_indextoname(int index) { return Libcore.os.if_indextoname(index); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/if_nametoindex.3.html">if_nametoindex(3)</a>.
+ */
+ public static int if_nametoindex(String name) { return Libcore.os.if_nametoindex(name); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/inet_pton.3.html">inet_pton(3)</a>.
+ */
+ 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); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
+ */
+ public static boolean isatty(FileDescriptor fd) { return Libcore.os.isatty(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/kill.2.html">kill(2)</a>.
+ */
+ public static void kill(int pid, int signal) throws ErrnoException { Libcore.os.kill(pid, signal); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/lchown.2.html">lchown(2)</a>.
+ */
+ public static void lchown(String path, int uid, int gid) throws ErrnoException { Libcore.os.lchown(path, uid, gid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/link.2.html">link(2)</a>.
+ */
+ public static void link(String oldPath, String newPath) throws ErrnoException { Libcore.os.link(oldPath, newPath); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/listen.2.html">listen(2)</a>.
+ */
+ public static void listen(FileDescriptor fd, int backlog) throws ErrnoException { Libcore.os.listen(fd, backlog); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/listxattr.2.html">listxattr(2)</a>
+ */
+ public static String[] listxattr(String path) throws ErrnoException { return Libcore.os.listxattr(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/lseek.2.html">lseek(2)</a>.
+ */
+ public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return Libcore.os.lseek(fd, offset, whence); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/lstat.2.html">lstat(2)</a>.
+ */
+ public static StructStat lstat(String path) throws ErrnoException { return Libcore.os.lstat(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mincore.2.html">mincore(2)</a>.
+ */
+ public static void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { Libcore.os.mincore(address, byteCount, vector); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mkdir.2.html">mkdir(2)</a>.
+ */
+ public static void mkdir(String path, int mode) throws ErrnoException { Libcore.os.mkdir(path, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/mkfifo.3.html">mkfifo(3)</a>.
+ */
+ public static void mkfifo(String path, int mode) throws ErrnoException { Libcore.os.mkfifo(path, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mlock.2.html">mlock(2)</a>.
+ */
+ public static void mlock(long address, long byteCount) throws ErrnoException { Libcore.os.mlock(address, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>.
+ */
+ public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/msync.2.html">msync(2)</a>.
+ */
+ public static void msync(long address, long byteCount, int flags) throws ErrnoException { Libcore.os.msync(address, byteCount, flags); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/munlock.2.html">munlock(2)</a>.
+ */
+ public static void munlock(long address, long byteCount) throws ErrnoException { Libcore.os.munlock(address, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/munmap.2.html">munmap(2)</a>.
+ */
+ public static void munmap(long address, long byteCount) throws ErrnoException { Libcore.os.munmap(address, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>.
+ */
+ public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return Libcore.os.open(path, flags, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pipe.2.html">pipe(2)</a>.
+ */
+ public static FileDescriptor[] pipe() throws ErrnoException { return Libcore.os.pipe2(0); }
+
+ /** @hide */ public static FileDescriptor[] pipe2(int flags) throws ErrnoException { return Libcore.os.pipe2(flags); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/poll.2.html">poll(2)</a>.
+ *
+ * <p>Note that in Lollipop this could throw an {@code ErrnoException} with {@code EINTR}.
+ * In later releases, the implementation will automatically just restart the system call with
+ * an appropriately reduced timeout.
+ */
+ public static int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return Libcore.os.poll(fds, timeoutMs); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/posix_fallocate.3.html">posix_fallocate(3)</a>.
+ */
+ public static void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { Libcore.os.posix_fallocate(fd, offset, length); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>.
+ */
+ public static int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return Libcore.os.prctl(option, arg2, arg3, arg4, arg5); };
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
+ */
+ public static int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, buffer, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
+ */
+ public static int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, bytes, byteOffset, byteCount, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
+ */
+ public static int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, buffer, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
+ */
+ public static int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
+ */
+ public static int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, buffer); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
+ */
+ public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, bytes, byteOffset, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/readlink.2.html">readlink(2)</a>.
+ */
+ public static String readlink(String path) throws ErrnoException { return Libcore.os.readlink(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/realpath.3.html">realpath(3)</a>.
+ */
+ /** @hide */ public static String realpath(String path) throws ErrnoException { return Libcore.os.realpath(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/readv.2.html">readv(2)</a>.
+ */
+ public static int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.readv(fd, buffers, offsets, byteCounts); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
+ */
+ public static int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, buffer, flags, srcAddress); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
+ */
+ public static int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/remove.3.html">remove(3)</a>.
+ */
+ public static void remove(String path) throws ErrnoException { Libcore.os.remove(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/removexattr.2.html">removexattr(2)</a>.
+ */
+ public static void removexattr(String path, String name) throws ErrnoException { Libcore.os.removexattr(path, name); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a>.
+ */
+ public static void rename(String oldPath, String newPath) throws ErrnoException { Libcore.os.rename(oldPath, newPath); }
+
+ /**
+ * 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); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+ */
+ public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, buffer, flags, inetAddress, port); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+ */
+ public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+ */
+ /** @hide */ public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, address); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setegid.2.html">setegid(2)</a>.
+ */
+ public static void setegid(int egid) throws ErrnoException { Libcore.os.setegid(egid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/setenv.3.html">setenv(3)</a>.
+ */
+ public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { Libcore.os.setenv(name, value, overwrite); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/seteuid.2.html">seteuid(2)</a>.
+ */
+ public static void seteuid(int euid) throws ErrnoException { Libcore.os.seteuid(euid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setgid.2.html">setgid(2)</a>.
+ */
+ public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
+
+ /**
+ * 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); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setregid.2.html">setregid(2)</a>.
+ */
+ /** @hide */ public static void setregid(int rgid, int egid) throws ErrnoException { Libcore.os.setregid(rgid, egid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setreuid.2.html">setreuid(2)</a>.
+ */
+ /** @hide */ public static void setreuid(int ruid, int euid) throws ErrnoException { Libcore.os.setreuid(ruid, euid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setsid.2.html">setsid(2)</a>.
+ */
+ public static int setsid() throws ErrnoException { return Libcore.os.setsid(); }
+
+ /** @hide */ public static void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptByte(fd, level, option, value); }
+ /** @hide */ public static void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { Libcore.os.setsockoptIfreq(fd, level, option, value); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setsockopt.2.html">setsockopt(2)</a>.
+ */
+ public static void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptInt(fd, level, option, value); }
+
+ /** @hide */ public static void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptIpMreqn(fd, level, option, value); }
+ /** @hide */ public static void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { Libcore.os.setsockoptGroupReq(fd, level, option, value); }
+ /** @hide */ public static void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { Libcore.os.setsockoptLinger(fd, level, option, value); }
+ /** @hide */ public static void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { Libcore.os.setsockoptTimeval(fd, level, option, value); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setuid.2.html">setuid(2)</a>.
+ */
+ public static void setuid(int uid) throws ErrnoException { Libcore.os.setuid(uid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setxattr.2.html">setxattr(2)</a>
+ */
+ public static void setxattr(String path, String name, byte[] value, int flags) throws ErrnoException { Libcore.os.setxattr(path, name, value, flags); };
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/shutdown.2.html">shutdown(2)</a>.
+ */
+ public static void shutdown(FileDescriptor fd, int how) throws ErrnoException { Libcore.os.shutdown(fd, how); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/socket.2.html">socket(2)</a>.
+ */
+ public static FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return Libcore.os.socket(domain, type, protocol); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/socketpair.2.html">socketpair(2)</a>.
+ */
+ public static void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException { Libcore.os.socketpair(domain, type, protocol, fd1, fd2); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/stat.2.html">stat(2)</a>.
+ */
+ public static StructStat stat(String path) throws ErrnoException { return Libcore.os.stat(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/statvfs.2.html">statvfs(2)</a>.
+ */
+ public static StructStatVfs statvfs(String path) throws ErrnoException { return Libcore.os.statvfs(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>.
+ */
+ public static String strerror(int errno) { return Libcore.os.strerror(errno); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/strsignal.3.html">strsignal(3)</a>.
+ */
+ public static String strsignal(int signal) { return Libcore.os.strsignal(signal); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/symlink.2.html">symlink(2)</a>.
+ */
+ public static void symlink(String oldPath, String newPath) throws ErrnoException { Libcore.os.symlink(oldPath, newPath); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/sysconf.3.html">sysconf(3)</a>.
+ */
+ public static long sysconf(int name) { return Libcore.os.sysconf(name); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/tcdrain.3.html">tcdrain(3)</a>.
+ */
+ public static void tcdrain(FileDescriptor fd) throws ErrnoException { Libcore.os.tcdrain(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/tcsendbreak.3.html">tcsendbreak(3)</a>.
+ */
+ public static void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException { Libcore.os.tcsendbreak(fd, duration); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/umask.2.html">umask(2)</a>.
+ */
+ public static int umask(int mask) { return Libcore.os.umask(mask); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/uname.2.html">uname(2)</a>.
+ */
+ public static StructUtsname uname() { return Libcore.os.uname(); }
+
+ /**
+ * @hide See <a href="http://man7.org/linux/man-pages/man2/unlink.2.html">unlink(2)</a>.
+ */
+ public static void unlink(String pathname) throws ErrnoException { Libcore.os.unlink(pathname); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/unsetenv.3.html">unsetenv(3)</a>.
+ */
+ public static void unsetenv(String name) throws ErrnoException { Libcore.os.unsetenv(name); }
+
+ /**
+ * 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); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
+ */
+ public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, buffer); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
+ */
+ public static int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, bytes, byteOffset, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/writev.2.html">writev(2)</a>.
+ */
+ public static int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.writev(fd, buffers, offsets, byteCounts); }
}
diff --git a/android/system/StructAddrinfo.java b/android/system/StructAddrinfo.java
index 2425946f..dab7590e 100644
--- a/android/system/StructAddrinfo.java
+++ b/android/system/StructAddrinfo.java
@@ -28,31 +28,31 @@ import libcore.util.Objects;
* @hide
*/
public final class StructAddrinfo {
- /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
- public int ai_flags;
+ /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
+ public int ai_flags;
- /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
- public int ai_family;
+ /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
+ public int ai_family;
- /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
- public int ai_socktype;
+ /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
+ public int ai_socktype;
- /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
- public int ai_protocol;
+ /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
+ public int ai_protocol;
- /** Address length. (Not useful in Java.) */
- // public int ai_addrlen;
+ /** Address length. (Not useful in Java.) */
+ // public int ai_addrlen;
- /** Address. */
- public InetAddress ai_addr;
+ /** Address. */
+ public InetAddress ai_addr;
- /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
- // public String ai_canonname;
+ /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
+ // public String ai_canonname;
- /** Next element in linked list. */
- public StructAddrinfo ai_next;
+ /** Next element in linked list. */
+ public StructAddrinfo ai_next;
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructCapUserData.java b/android/system/StructCapUserData.java
index af63caf7..331e2ead 100644
--- a/android/system/StructCapUserData.java
+++ b/android/system/StructCapUserData.java
@@ -24,25 +24,25 @@ import libcore.util.Objects;
* @hide
*/
public final class StructCapUserData {
- /** Effective capability mask. */
- public final int effective; /* __u32 */
+ /** Effective capability mask. */
+ public final int effective; /* __u32 */
- /** Permitted capability mask. */
- public final int permitted; /* __u32 */
+ /** Permitted capability mask. */
+ public final int permitted; /* __u32 */
- /** Inheritable capability mask. */
- public final int inheritable; /* __u32 */
+ /** Inheritable capability mask. */
+ public final int inheritable; /* __u32 */
- /**
- * Constructs an instance with the given field values.
- */
- public StructCapUserData(int effective, int permitted, int inheritable) {
- this.effective = effective;
- this.permitted = permitted;
- this.inheritable = inheritable;
- }
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructCapUserData(int effective, int permitted, int inheritable) {
+ this.effective = effective;
+ this.permitted = permitted;
+ this.inheritable = inheritable;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructCapUserHeader.java b/android/system/StructCapUserHeader.java
index abbb3954..fb03aa89 100644
--- a/android/system/StructCapUserHeader.java
+++ b/android/system/StructCapUserHeader.java
@@ -24,25 +24,25 @@ import libcore.util.Objects;
* @hide
*/
public final class StructCapUserHeader {
- /**
- * Version of the header. Note this is not final as capget() may mutate the field when an
- * invalid version is provided. See
- * <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
- */
- public int version; /* __u32 */
+ /**
+ * Version of the header. Note this is not final as capget() may mutate the field when an
+ * invalid version is provided. See
+ * <a href="http://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
+ */
+ public int version; /* __u32 */
- /** Pid of the header. The pid a call applies to. */
- public final int pid;
+ /** Pid of the header. The pid a call applies to. */
+ public final int pid;
- /**
- * Constructs an instance with the given field values.
- */
- public StructCapUserHeader(int version, int pid) {
- this.version = version;
- this.pid = pid;
- }
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructCapUserHeader(int version, int pid) {
+ this.version = version;
+ this.pid = pid;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructFlock.java b/android/system/StructFlock.java
index 92cd95ab..0d934250 100644
--- a/android/system/StructFlock.java
+++ b/android/system/StructFlock.java
@@ -26,22 +26,22 @@ import libcore.util.Objects;
* @hide
*/
public final class StructFlock {
- /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */
- public short l_type;
+ /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */
+ public short l_type;
- /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */
- public short l_whence;
+ /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */
+ public short l_whence;
- /** Start offset. */
- public long l_start; /*off_t*/
+ /** Start offset. */
+ public long l_start; /*off_t*/
- /** Byte count to operate on. */
- public long l_len; /*off_t*/
+ /** Byte count to operate on. */
+ public long l_len; /*off_t*/
- /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */
- public int l_pid; /*pid_t*/
+ /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */
+ public int l_pid; /*pid_t*/
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructGroupReq.java b/android/system/StructGroupReq.java
index 8ed5950b..78db5f53 100644
--- a/android/system/StructGroupReq.java
+++ b/android/system/StructGroupReq.java
@@ -25,15 +25,15 @@ import libcore.util.Objects;
* @hide
*/
public final class StructGroupReq {
- public final int gr_interface;
- public final InetAddress gr_group;
+ public final int gr_interface;
+ public final InetAddress gr_group;
- public StructGroupReq(int gr_interface, InetAddress gr_group) {
- this.gr_interface = gr_interface;
- this.gr_group = gr_group;
- }
+ public StructGroupReq(int gr_interface, InetAddress gr_group) {
+ this.gr_interface = gr_interface;
+ this.gr_group = gr_group;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructLinger.java b/android/system/StructLinger.java
index 55ffc5c9..46d41325 100644
--- a/android/system/StructLinger.java
+++ b/android/system/StructLinger.java
@@ -25,22 +25,22 @@ import libcore.util.Objects;
* @hide
*/
public final class StructLinger {
- /** Whether or not linger is enabled. Non-zero is on. */
- public final int l_onoff;
+ /** Whether or not linger is enabled. Non-zero is on. */
+ public final int l_onoff;
- /** Linger time in seconds. */
- public final int l_linger;
+ /** Linger time in seconds. */
+ public final int l_linger;
- public StructLinger(int l_onoff, int l_linger) {
- this.l_onoff = l_onoff;
- this.l_linger = l_linger;
- }
+ public StructLinger(int l_onoff, int l_linger) {
+ this.l_onoff = l_onoff;
+ this.l_linger = l_linger;
+ }
- public boolean isOn() {
- return l_onoff != 0;
- }
+ public boolean isOn() {
+ return l_onoff != 0;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructPasswd.java b/android/system/StructPasswd.java
index 93655c19..633a607d 100644
--- a/android/system/StructPasswd.java
+++ b/android/system/StructPasswd.java
@@ -25,24 +25,24 @@ import libcore.util.Objects;
* @hide
*/
public final class StructPasswd {
- public final String pw_name;
- public final int pw_uid;
- public final int pw_gid;
- public final String pw_dir;
- public final String pw_shell;
+ public final String pw_name;
+ public final int pw_uid;
+ public final int pw_gid;
+ public final String pw_dir;
+ public final String pw_shell;
- /**
- * Constructs an instance with the given field values.
- */
- public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) {
- this.pw_name = pw_name;
- this.pw_uid = pw_uid;
- this.pw_gid = pw_gid;
- this.pw_dir = pw_dir;
- this.pw_shell = pw_shell;
- }
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) {
+ this.pw_name = pw_name;
+ this.pw_uid = pw_uid;
+ this.pw_gid = pw_gid;
+ this.pw_dir = pw_dir;
+ this.pw_shell = pw_shell;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructPollfd.java b/android/system/StructPollfd.java
index 2ca117e9..2f40b8bc 100644
--- a/android/system/StructPollfd.java
+++ b/android/system/StructPollfd.java
@@ -24,26 +24,26 @@ import libcore.util.Objects;
* Corresponds to C's {@code struct pollfd} from {@code <poll.h>}.
*/
public final class StructPollfd {
- /** The file descriptor to poll. */
- public FileDescriptor fd;
+ /** The file descriptor to poll. */
+ public FileDescriptor fd;
- /**
- * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
- * POLLOUT to the write fd set.
- */
- public short events;
+ /**
+ * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
+ * POLLOUT to the write fd set.
+ */
+ public short events;
- /** The events that actually happened. */
- public short revents;
+ /** The events that actually happened. */
+ public short revents;
- /**
- * A non-standard extension that lets callers conveniently map back to the object
- * their fd belongs to. This is used by Selector, for example, to associate each
- * FileDescriptor with the corresponding SelectionKey.
- */
- public Object userData;
+ /**
+ * A non-standard extension that lets callers conveniently map back to the object
+ * their fd belongs to. This is used by Selector, for example, to associate each
+ * FileDescriptor with the corresponding SelectionKey.
+ */
+ public Object userData;
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructRlimit.java b/android/system/StructRlimit.java
index 6bb05a9c..007757f4 100644
--- a/android/system/StructRlimit.java
+++ b/android/system/StructRlimit.java
@@ -25,15 +25,15 @@ import libcore.util.Objects;
* @hide
*/
public final class StructRlimit {
- public final long rlim_cur;
- public final long rlim_max;
+ public final long rlim_cur;
+ public final long rlim_max;
- public StructRlimit(long rlim_cur, long rlim_max) {
- this.rlim_cur = rlim_cur;
- this.rlim_max = rlim_max;
- }
+ public StructRlimit(long rlim_cur, long rlim_max) {
+ this.rlim_cur = rlim_cur;
+ this.rlim_max = rlim_max;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructStat.java b/android/system/StructStat.java
index a1e87296..a8b1fca4 100644
--- a/android/system/StructStat.java
+++ b/android/system/StructStat.java
@@ -23,99 +23,99 @@ import libcore.util.Objects;
* Corresponds to C's {@code struct stat} from {@code <stat.h>}.
*/
public final class StructStat {
- /** Device ID of device containing file. */
- public final long st_dev; /*dev_t*/
-
- /** File serial number (inode). */
- public final long st_ino; /*ino_t*/
-
- /** Mode (permissions) of file. */
- public final int st_mode; /*mode_t*/
-
- /** Number of hard links to the file. */
- public final long st_nlink; /*nlink_t*/
-
- /** User ID of file. */
- public final int st_uid; /*uid_t*/
-
- /** Group ID of file. */
- public final int st_gid; /*gid_t*/
-
- /** Device ID (if file is character or block special). */
- public final long st_rdev; /*dev_t*/
-
- /**
- * For regular files, the file size in bytes.
- * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
- * For a shared memory object, the length in bytes.
- * For a typed memory object, the length in bytes.
- * For other file types, the use of this field is unspecified.
- */
- public final long st_size; /*off_t*/
-
- /** Seconds part of time of last access. */
- public final long st_atime; /*time_t*/
-
- /** StructTimespec with time of last access. */
- public final StructTimespec st_atim;
-
- /** Seconds part of time of last data modification. */
- public final long st_mtime; /*time_t*/
-
- /** StructTimespec with time of last modification. */
- public final StructTimespec st_mtim;
-
- /** Seconds part of time of last status change */
- public final long st_ctime; /*time_t*/
-
- /** StructTimespec with time of last status change. */
- public final StructTimespec st_ctim;
-
- /**
- * A file system-specific preferred I/O block size for this object.
- * For some file system types, this may vary from file to file.
- */
- public final long st_blksize; /*blksize_t*/
-
- /** Number of blocks allocated for this object. */
- public final long st_blocks; /*blkcnt_t*/
-
- /**
- * Constructs an instance with the given field values.
- */
- public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
- long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
- long st_blksize, long st_blocks) {
- this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid,
- st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L),
- new StructTimespec(st_ctime, 0L), st_blksize, st_blocks);
- }
-
- /**
- * Constructs an instance with the given field values.
- */
- public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
- long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim,
- StructTimespec st_ctim, long st_blksize, long st_blocks) {
- this.st_dev = st_dev;
- this.st_ino = st_ino;
- this.st_mode = st_mode;
- this.st_nlink = st_nlink;
- this.st_uid = st_uid;
- this.st_gid = st_gid;
- this.st_rdev = st_rdev;
- this.st_size = st_size;
- this.st_atime = st_atim.tv_sec;
- this.st_mtime = st_mtim.tv_sec;
- this.st_ctime = st_ctim.tv_sec;
- this.st_atim = st_atim;
- this.st_mtim = st_mtim;
- this.st_ctim = st_ctim;
- this.st_blksize = st_blksize;
- this.st_blocks = st_blocks;
- }
-
- @Override public String toString() {
- return Objects.toString(this);
- }
+ /** Device ID of device containing file. */
+ public final long st_dev; /*dev_t*/
+
+ /** File serial number (inode). */
+ public final long st_ino; /*ino_t*/
+
+ /** Mode (permissions) of file. */
+ public final int st_mode; /*mode_t*/
+
+ /** Number of hard links to the file. */
+ public final long st_nlink; /*nlink_t*/
+
+ /** User ID of file. */
+ public final int st_uid; /*uid_t*/
+
+ /** Group ID of file. */
+ public final int st_gid; /*gid_t*/
+
+ /** Device ID (if file is character or block special). */
+ public final long st_rdev; /*dev_t*/
+
+ /**
+ * For regular files, the file size in bytes.
+ * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
+ * For a shared memory object, the length in bytes.
+ * For a typed memory object, the length in bytes.
+ * For other file types, the use of this field is unspecified.
+ */
+ public final long st_size; /*off_t*/
+
+ /** Seconds part of time of last access. */
+ public final long st_atime; /*time_t*/
+
+ /** StructTimespec with time of last access. */
+ public final StructTimespec st_atim;
+
+ /** Seconds part of time of last data modification. */
+ public final long st_mtime; /*time_t*/
+
+ /** StructTimespec with time of last modification. */
+ public final StructTimespec st_mtim;
+
+ /** Seconds part of time of last status change */
+ public final long st_ctime; /*time_t*/
+
+ /** StructTimespec with time of last status change. */
+ public final StructTimespec st_ctim;
+
+ /**
+ * A file system-specific preferred I/O block size for this object.
+ * For some file system types, this may vary from file to file.
+ */
+ public final long st_blksize; /*blksize_t*/
+
+ /** Number of blocks allocated for this object. */
+ public final long st_blocks; /*blkcnt_t*/
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+ long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
+ long st_blksize, long st_blocks) {
+ this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid,
+ st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L),
+ new StructTimespec(st_ctime, 0L), st_blksize, st_blocks);
+ }
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+ long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim,
+ StructTimespec st_ctim, long st_blksize, long st_blocks) {
+ this.st_dev = st_dev;
+ this.st_ino = st_ino;
+ this.st_mode = st_mode;
+ this.st_nlink = st_nlink;
+ this.st_uid = st_uid;
+ this.st_gid = st_gid;
+ this.st_rdev = st_rdev;
+ this.st_size = st_size;
+ this.st_atime = st_atim.tv_sec;
+ this.st_mtime = st_mtim.tv_sec;
+ this.st_ctime = st_ctim.tv_sec;
+ this.st_atim = st_atim;
+ this.st_mtim = st_mtim;
+ this.st_ctim = st_ctim;
+ this.st_blksize = st_blksize;
+ this.st_blocks = st_blocks;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructStatVfs.java b/android/system/StructStatVfs.java
index 942a39a8..3b192e7c 100644
--- a/android/system/StructStatVfs.java
+++ b/android/system/StructStatVfs.java
@@ -22,59 +22,59 @@ import libcore.util.Objects;
* File information returned by {@link Os#fstatvfs} and {@link Os#statvfs}.
*/
public final class StructStatVfs {
- /** File system block size (used for block counts). */
- public final long f_bsize; /*unsigned long*/
+ /** File system block size (used for block counts). */
+ public final long f_bsize; /*unsigned long*/
- /** Fundamental file system block size. */
- public final long f_frsize; /*unsigned long*/
+ /** Fundamental file system block size. */
+ public final long f_frsize; /*unsigned long*/
- /** Total block count. */
- public final long f_blocks; /*fsblkcnt_t*/
+ /** Total block count. */
+ public final long f_blocks; /*fsblkcnt_t*/
- /** Free block count. */
- public final long f_bfree; /*fsblkcnt_t*/
+ /** Free block count. */
+ public final long f_bfree; /*fsblkcnt_t*/
- /** Free block count available to non-root. */
- public final long f_bavail; /*fsblkcnt_t*/
+ /** Free block count available to non-root. */
+ public final long f_bavail; /*fsblkcnt_t*/
- /** Total file (inode) count. */
- public final long f_files; /*fsfilcnt_t*/
+ /** Total file (inode) count. */
+ public final long f_files; /*fsfilcnt_t*/
- /** Free file (inode) count. */
- public final long f_ffree; /*fsfilcnt_t*/
+ /** Free file (inode) count. */
+ public final long f_ffree; /*fsfilcnt_t*/
- /** Free file (inode) count available to non-root. */
- public final long f_favail; /*fsfilcnt_t*/
+ /** Free file (inode) count available to non-root. */
+ public final long f_favail; /*fsfilcnt_t*/
- /** File system id. */
- public final long f_fsid; /*unsigned long*/
+ /** File system id. */
+ public final long f_fsid; /*unsigned long*/
- /** Bit mask of ST_* flags. */
- public final long f_flag; /*unsigned long*/
+ /** Bit mask of ST_* flags. */
+ public final long f_flag; /*unsigned long*/
- /** Maximum filename length. */
- public final long f_namemax; /*unsigned long*/
+ /** Maximum filename length. */
+ public final long f_namemax; /*unsigned long*/
- /**
- * Constructs an instance with the given field values.
- */
- public StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail,
- long f_files, long f_ffree, long f_favail,
- long f_fsid, long f_flag, long f_namemax) {
- this.f_bsize = f_bsize;
- this.f_frsize = f_frsize;
- this.f_blocks = f_blocks;
- this.f_bfree = f_bfree;
- this.f_bavail = f_bavail;
- this.f_files = f_files;
- this.f_ffree = f_ffree;
- this.f_favail = f_favail;
- this.f_fsid = f_fsid;
- this.f_flag = f_flag;
- this.f_namemax = f_namemax;
- }
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail,
+ long f_files, long f_ffree, long f_favail,
+ long f_fsid, long f_flag, long f_namemax) {
+ this.f_bsize = f_bsize;
+ this.f_frsize = f_frsize;
+ this.f_blocks = f_blocks;
+ this.f_bfree = f_bfree;
+ this.f_bavail = f_bavail;
+ this.f_files = f_files;
+ this.f_ffree = f_ffree;
+ this.f_favail = f_favail;
+ this.f_fsid = f_fsid;
+ this.f_flag = f_flag;
+ this.f_namemax = f_namemax;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructTimespec.java b/android/system/StructTimespec.java
index b5e192e5..c1067805 100644
--- a/android/system/StructTimespec.java
+++ b/android/system/StructTimespec.java
@@ -22,58 +22,58 @@ import libcore.util.Objects;;
* Corresponds to C's {@code struct timespec} from {@code <time.h>}.
*/
public final class StructTimespec implements Comparable<StructTimespec> {
- /** Seconds part of time of last data modification. */
- public final long tv_sec; /*time_t*/
+ /** Seconds part of time of last data modification. */
+ public final long tv_sec; /*time_t*/
- /** Nanoseconds (values are [0, 999999999]). */
- public final long tv_nsec;
+ /** Nanoseconds (values are [0, 999999999]). */
+ public final long tv_nsec;
- public StructTimespec(long tv_sec, long tv_nsec) {
- this.tv_sec = tv_sec;
- this.tv_nsec = tv_nsec;
- if (tv_nsec < 0 || tv_nsec > 999_999_999) {
- throw new IllegalArgumentException(
- "tv_nsec value " + tv_nsec + " is not in [0, 999999999]");
- }
- }
+ public StructTimespec(long tv_sec, long tv_nsec) {
+ this.tv_sec = tv_sec;
+ this.tv_nsec = tv_nsec;
+ if (tv_nsec < 0 || tv_nsec > 999_999_999) {
+ throw new IllegalArgumentException(
+ "tv_nsec value " + tv_nsec + " is not in [0, 999999999]");
+ }
+ }
- @Override
- public int compareTo(StructTimespec other) {
- if (tv_sec > other.tv_sec) {
- return 1;
- }
- if (tv_sec < other.tv_sec) {
- return -1;
- }
- if (tv_nsec > other.tv_nsec) {
- return 1;
- }
- if (tv_nsec < other.tv_nsec) {
- return -1;
- }
- return 0;
- }
+ @Override
+ public int compareTo(StructTimespec other) {
+ if (tv_sec > other.tv_sec) {
+ return 1;
+ }
+ if (tv_sec < other.tv_sec) {
+ return -1;
+ }
+ if (tv_nsec > other.tv_nsec) {
+ return 1;
+ }
+ if (tv_nsec < other.tv_nsec) {
+ return -1;
+ }
+ return 0;
+ }
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
- StructTimespec that = (StructTimespec) o;
+ StructTimespec that = (StructTimespec) o;
- if (tv_sec != that.tv_sec) return false;
- return tv_nsec == that.tv_nsec;
- }
+ if (tv_sec != that.tv_sec) return false;
+ return tv_nsec == that.tv_nsec;
+ }
- @Override
- public int hashCode() {
- int result = (int) (tv_sec ^ (tv_sec >>> 32));
- result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32));
- return result;
- }
+ @Override
+ public int hashCode() {
+ int result = (int) (tv_sec ^ (tv_sec >>> 32));
+ result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32));
+ return result;
+ }
- @Override
- public String toString() {
- return Objects.toString(this);
- }
+ @Override
+ public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructTimeval.java b/android/system/StructTimeval.java
index 8a155b4f..91a6f2ae 100644
--- a/android/system/StructTimeval.java
+++ b/android/system/StructTimeval.java
@@ -25,28 +25,28 @@ import libcore.util.Objects;
* @hide
*/
public final class StructTimeval {
- /** Seconds. */
- public final long tv_sec;
-
- /** Microseconds. */
- public final long tv_usec;
-
- private StructTimeval(long tv_sec, long tv_usec) {
- this.tv_sec = tv_sec;
- this.tv_usec = tv_usec;
- }
-
- public static StructTimeval fromMillis(long millis) {
- long tv_sec = millis / 1000;
- long tv_usec = (millis - (tv_sec * 1000)) * 1000;
- return new StructTimeval(tv_sec, tv_usec);
- }
-
- public long toMillis() {
- return (tv_sec * 1000) + (tv_usec / 1000);
- }
-
- @Override public String toString() {
- return Objects.toString(this);
- }
+ /** Seconds. */
+ public final long tv_sec;
+
+ /** Microseconds. */
+ public final long tv_usec;
+
+ private StructTimeval(long tv_sec, long tv_usec) {
+ this.tv_sec = tv_sec;
+ this.tv_usec = tv_usec;
+ }
+
+ public static StructTimeval fromMillis(long millis) {
+ long tv_sec = millis / 1000;
+ long tv_usec = (millis - (tv_sec * 1000)) * 1000;
+ return new StructTimeval(tv_sec, tv_usec);
+ }
+
+ public long toMillis() {
+ return (tv_sec * 1000) + (tv_usec / 1000);
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructUcred.java b/android/system/StructUcred.java
index a1e3cd6e..e6e1479d 100644
--- a/android/system/StructUcred.java
+++ b/android/system/StructUcred.java
@@ -24,22 +24,22 @@ import libcore.util.Objects;
* @hide
*/
public final class StructUcred {
- /** The peer's process id. */
- public final int pid;
+ /** The peer's process id. */
+ public final int pid;
- /** The peer process' uid. */
- public final int uid;
+ /** The peer process' uid. */
+ public final int uid;
- /** The peer process' gid. */
- public final int gid;
+ /** The peer process' gid. */
+ public final int gid;
- public StructUcred(int pid, int uid, int gid) {
- this.pid = pid;
- this.uid = uid;
- this.gid = gid;
- }
+ public StructUcred(int pid, int uid, int gid) {
+ this.pid = pid;
+ this.uid = uid;
+ this.gid = gid;
+ }
- @Override public String toString() {
- return Objects.toString(this);
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/system/StructUtsname.java b/android/system/StructUtsname.java
index 37778380..9cfe21b0 100644
--- a/android/system/StructUtsname.java
+++ b/android/system/StructUtsname.java
@@ -23,33 +23,33 @@ import libcore.util.Objects;
* Corresponds to C's {@code struct utsname} from {@code <sys/utsname.h>}.
*/
public final class StructUtsname {
- /** The OS name, such as "Linux". */
- public final String sysname;
-
- /** The machine's unqualified name on some implementation-defined network. */
- public final String nodename;
-
- /** The OS release, such as "2.6.35-27-generic". */
- public final String release;
-
- /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */
- public final String version;
-
- /** The machine architecture, such as "armv7l" or "x86_64". */
- public final String machine;
-
- /**
- * Constructs an instance with the given field values.
- */
- public StructUtsname(String sysname, String nodename, String release, String version, String machine) {
- this.sysname = sysname;
- this.nodename = nodename;
- this.release = release;
- this.version = version;
- this.machine = machine;
- }
-
- @Override public String toString() {
- return Objects.toString(this);
- }
+ /** The OS name, such as "Linux". */
+ public final String sysname;
+
+ /** The machine's unqualified name on some implementation-defined network. */
+ public final String nodename;
+
+ /** The OS release, such as "2.6.35-27-generic". */
+ public final String release;
+
+ /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */
+ public final String version;
+
+ /** The machine architecture, such as "armv7l" or "x86_64". */
+ public final String machine;
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructUtsname(String sysname, String nodename, String release, String version, String machine) {
+ this.sysname = sysname;
+ this.nodename = nodename;
+ this.release = release;
+ this.version = version;
+ this.machine = machine;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 689ce954..99f8cfbf 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -763,6 +763,18 @@ public class CarrierConfigManager {
public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
/**
+ * Some carriers will send call forwarding responses for voicemail in a format that is not 3gpp
+ * compliant, which causes issues during parsing. This causes the
+ * {@link com.android.internal.telephony.CallForwardInfo#number} to contain non-numerical
+ * characters instead of a number.
+ *
+ * If true, we will detect the non-numerical characters and replace them with "Voicemail".
+ * @hide
+ */
+ public static final String KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL =
+ "call_forwarding_map_non_number_to_voicemail_bool";
+
+ /**
* Determines whether conference calls are supported by a carrier. When {@code true},
* conference calling is supported, {@code false otherwise}.
*/
@@ -1407,6 +1419,17 @@ public class CarrierConfigManager {
"support_3gpp_call_forwarding_while_roaming_bool";
/**
+ * Boolean indicating whether to display voicemail number as default call forwarding number in
+ * call forwarding settings.
+ * If true, display vm number when cf number is null.
+ * If false, display the cf number from network.
+ * By default this value is false.
+ * @hide
+ */
+ public static final String KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL =
+ "display_voicemail_number_as_default_call_forwarding_number";
+
+ /**
* When {@code true}, the user will be notified when they attempt to place an international call
* when the call is placed using wifi calling.
* @hide
@@ -1573,6 +1596,25 @@ public class CarrierConfigManager {
public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL =
"show_ims_registration_status_bool";
+ /**
+ * The flag to disable the popup dialog which warns the user of data charges.
+ * @hide
+ */
+ public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL =
+ "disable_charge_indication_bool";
+
+ /**
+ * Boolean indicating whether to skip the call forwarding (CF) fail-to-disable dialog.
+ * The logic used to determine whether we succeeded in disabling is carrier specific,
+ * so the dialog may not always be accurate.
+ * {@code false} - show CF fail-to-disable dialog.
+ * {@code true} - skip showing CF fail-to-disable dialog.
+ *
+ * @hide
+ */
+ public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
+ "skip_cf_fail_to_disable_dialog_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1703,6 +1745,7 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
+ sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
@@ -1726,6 +1769,7 @@ public class CarrierConfigManager {
sDefaults.putString(KEY_CARRIER_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL, false);
+ sDefaults.putBoolean(KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL, false);
// MMS defaults
sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false);
@@ -1827,6 +1871,8 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1);
sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
+ sDefaults.putBoolean(KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL,
+ false);
sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
null);
@@ -1840,6 +1886,7 @@ public class CarrierConfigManager {
sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
+ sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
}
/**
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
index 764b7b22..9a9877a8 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -77,8 +77,9 @@ public class MbmsDownloadSession implements AutoCloseable {
* Integer extra that Android will attach to the intent supplied via
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
* Indicates the result code of the download. One of
- * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
- * {@link #RESULT_IO_ERROR}.
+ * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED},
+ * {@link #RESULT_IO_ERROR}, {@link #RESULT_DOWNLOAD_FAILURE}, {@link #RESULT_OUT_OF_STORAGE},
+ * {@link #RESULT_SERVICE_ID_NOT_DEFINED}, or {@link #RESULT_FILE_ROOT_UNREACHABLE}.
*
* This extra may also be used by the middleware when it is sending intents to the app.
*/
@@ -142,11 +143,41 @@ public class MbmsDownloadSession implements AutoCloseable {
/**
* Indicates that the download will not be completed due to an I/O error incurred while
- * writing to temp files. This commonly indicates that the device is out of storage space,
- * but may indicate other conditions as well (such as an SD card being removed).
+ * writing to temp files.
+ *
+ * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+ * the download again.
*/
public static final int RESULT_IO_ERROR = 4;
- // TODO - more results!
+
+ /**
+ * Indicates that the Service ID specified in the {@link DownloadRequest} is incorrect due to
+ * the Id being incorrect, stale, expired, or similar.
+ */
+ public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5;
+
+ /**
+ * Indicates that there was an error while processing downloaded files, such as a file repair or
+ * file decoding error and is not due to a file I/O error.
+ *
+ * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+ * the download again.
+ */
+ public static final int RESULT_DOWNLOAD_FAILURE = 6;
+
+ /**
+ * Indicates that the file system is full and the {@link DownloadRequest} can not complete.
+ * Either space must be made on the current file system or the temp file root location must be
+ * changed to a location that is not full to download the temp files.
+ */
+ public static final int RESULT_OUT_OF_STORAGE = 7;
+
+ /**
+ * Indicates that the file root that was set is currently unreachable. This can happen if the
+ * temp files are set to be stored on external storage and the SD card was removed, for example.
+ * The temp file root should be changed before sending another DownloadRequest.
+ */
+ public static final int RESULT_FILE_ROOT_UNREACHABLE = 8;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/android/telephony/NetworkScanRequest.java b/android/telephony/NetworkScanRequest.java
index d2aef200..9674c930 100644
--- a/android/telephony/NetworkScanRequest.java
+++ b/android/telephony/NetworkScanRequest.java
@@ -19,6 +19,7 @@ package android.telephony;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -38,6 +39,20 @@ public final class NetworkScanRequest implements Parcelable {
public static final int MAX_BANDS = 8;
/** @hide */
public static final int MAX_CHANNELS = 32;
+ /** @hide */
+ public static final int MAX_MCC_MNC_LIST_SIZE = 20;
+ /** @hide */
+ public static final int MIN_SEARCH_PERIODICITY_SEC = 5;
+ /** @hide */
+ public static final int MAX_SEARCH_PERIODICITY_SEC = 300;
+ /** @hide */
+ public static final int MIN_SEARCH_MAX_SEC = 60;
+ /** @hide */
+ public static final int MAX_SEARCH_MAX_SEC = 3600;
+ /** @hide */
+ public static final int MIN_INCREMENTAL_PERIODICITY_SEC = 1;
+ /** @hide */
+ public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10;
/** Performs the scan only once */
public static final int SCAN_TYPE_ONE_SHOT = 0;
@@ -46,24 +61,84 @@ public final class NetworkScanRequest implements Parcelable {
*
* The modem will start new scans periodically, and the interval between two scans is usually
* multiple minutes.
- * */
+ */
public static final int SCAN_TYPE_PERIODIC = 1;
/** Defines the type of the scan. */
public int scanType;
+ /**
+ * Search periodicity (in seconds).
+ * Expected range for the input is [5s - 300s]
+ * This value must be less than or equal to maxSearchTime
+ */
+ public int searchPeriodicity;
+
+ /**
+ * Maximum duration of the periodic search (in seconds).
+ * Expected range for the input is [60s - 3600s]
+ * If the search lasts this long, it will be terminated.
+ */
+ public int maxSearchTime;
+
+ /**
+ * Indicates whether the modem should report incremental
+ * results of the network scan to the client.
+ * FALSE – Incremental results are not reported.
+ * TRUE (default) – Incremental results are reported
+ */
+ public boolean incrementalResults;
+
+ /**
+ * Indicates the periodicity with which the modem should
+ * report incremental results to the client (in seconds).
+ * Expected range for the input is [1s - 10s]
+ * This value must be less than or equal to maxSearchTime
+ */
+ public int incrementalResultsPeriodicity;
+
/** Describes the radio access technologies with bands or channels that need to be scanned. */
public RadioAccessSpecifier[] specifiers;
/**
+ * Describes the List of PLMN ids (MCC-MNC)
+ * If any PLMN of this list is found, search should end at that point and
+ * results with all PLMN found till that point should be sent as response.
+ * If list not sent, search to be completed till end and all PLMNs found to be reported.
+ * Max size of array is MAX_MCC_MNC_LIST_SIZE
+ */
+ public ArrayList<String> mccMncs;
+
+ /**
* Creates a new NetworkScanRequest with scanType and network specifiers
*
* @param scanType The type of the scan
* @param specifiers the radio network with bands / channels to be scanned
+ * @param searchPeriodicity Search periodicity (in seconds)
+ * @param maxSearchTime Maximum duration of the periodic search (in seconds)
+ * @param incrementalResults Indicates whether the modem should report incremental
+ * results of the network scan to the client
+ * @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should
+ * report incremental results to the client (in seconds)
+ * @param mccMncs Describes the List of PLMN ids (MCC-MNC)
*/
- public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) {
+ public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers,
+ int searchPeriodicity,
+ int maxSearchTime,
+ boolean incrementalResults,
+ int incrementalResultsPeriodicity,
+ ArrayList<String> mccMncs) {
this.scanType = scanType;
this.specifiers = specifiers;
+ this.searchPeriodicity = searchPeriodicity;
+ this.maxSearchTime = maxSearchTime;
+ this.incrementalResults = incrementalResults;
+ this.incrementalResultsPeriodicity = incrementalResultsPeriodicity;
+ if (mccMncs != null) {
+ this.mccMncs = mccMncs;
+ } else {
+ this.mccMncs = new ArrayList<>();
+ }
}
@Override
@@ -75,6 +150,11 @@ public final class NetworkScanRequest implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(scanType);
dest.writeParcelableArray(specifiers, flags);
+ dest.writeInt(searchPeriodicity);
+ dest.writeInt(maxSearchTime);
+ dest.writeBoolean(incrementalResults);
+ dest.writeInt(incrementalResultsPeriodicity);
+ dest.writeStringList(mccMncs);
}
private NetworkScanRequest(Parcel in) {
@@ -82,6 +162,12 @@ public final class NetworkScanRequest implements Parcelable {
specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
Object.class.getClassLoader(),
RadioAccessSpecifier.class);
+ searchPeriodicity = in.readInt();
+ maxSearchTime = in.readInt();
+ incrementalResults = in.readBoolean();
+ incrementalResultsPeriodicity = in.readInt();
+ mccMncs = new ArrayList<>();
+ in.readStringList(mccMncs);
}
@Override
@@ -99,13 +185,24 @@ public final class NetworkScanRequest implements Parcelable {
}
return (scanType == nsr.scanType
- && Arrays.equals(specifiers, nsr.specifiers));
+ && Arrays.equals(specifiers, nsr.specifiers)
+ && searchPeriodicity == nsr.searchPeriodicity
+ && maxSearchTime == nsr.maxSearchTime
+ && incrementalResults == nsr.incrementalResults
+ && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
+ && (((mccMncs != null)
+ && mccMncs.equals(nsr.mccMncs))));
}
@Override
public int hashCode () {
return ((scanType * 31)
- + (Arrays.hashCode(specifiers)) * 37);
+ + (Arrays.hashCode(specifiers)) * 37
+ + (searchPeriodicity * 41)
+ + (maxSearchTime * 43)
+ + ((incrementalResults == true? 1 : 0) * 47)
+ + (incrementalResultsPeriodicity * 53)
+ + (mccMncs.hashCode() * 59));
}
public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/android/telephony/ServiceState.java b/android/telephony/ServiceState.java
index e448fb2a..116e711e 100644
--- a/android/telephony/ServiceState.java
+++ b/android/telephony/ServiceState.java
@@ -1197,15 +1197,6 @@ public class ServiceState implements Parcelable {
}
}
- /**
- * @Deprecated to be removed Q3 2013 use {@link #getVoiceNetworkType}
- * @hide
- */
- public int getNetworkType() {
- Rlog.e(LOG_TAG, "ServiceState.getNetworkType() DEPRECATED will be removed *******");
- return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology);
- }
-
/** @hide */
public int getDataNetworkType() {
return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology);
diff --git a/android/telephony/SignalStrength.java b/android/telephony/SignalStrength.java
index 9e023993..c8b47765 100644
--- a/android/telephony/SignalStrength.java
+++ b/android/telephony/SignalStrength.java
@@ -19,7 +19,6 @@ package android.telephony;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
import android.util.Log;
import android.content.res.Resources;
@@ -429,6 +428,15 @@ public class SignalStrength implements Parcelable {
}
/**
+ * Fix {@link #isGsm} based on the signal strength data.
+ *
+ * @hide
+ */
+ public void fixType() {
+ isGsm = getCdmaRelatedSignalStrength() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ }
+
+ /**
* @param true - Gsm, Lte phones
* false - Cdma phones
*
@@ -541,30 +549,7 @@ public class SignalStrength implements Parcelable {
* while 4 represents a very strong signal strength.
*/
public int getLevel() {
- int level = 0;
-
- if (isGsm) {
- level = getLteLevel();
- if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- level = getTdScdmaLevel();
- if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- level = getGsmLevel();
- }
- }
- } else {
- int cdmaLevel = getCdmaLevel();
- int evdoLevel = getEvdoLevel();
- if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- /* We don't know evdo, use cdma */
- level = cdmaLevel;
- } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- /* We don't know cdma, use evdo */
- level = evdoLevel;
- } else {
- /* We know both, use the lowest level */
- level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
- }
- }
+ int level = isGsm ? getGsmRelatedSignalStrength() : getCdmaRelatedSignalStrength();
if (DBG) log("getLevel=" + level);
return level;
}
@@ -1049,6 +1034,36 @@ public class SignalStrength implements Parcelable {
+ " " + (isGsm ? "gsm|lte" : "cdma"));
}
+ /** Returns the signal strength related to GSM. */
+ private int getGsmRelatedSignalStrength() {
+ int level = getLteLevel();
+ if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ level = getTdScdmaLevel();
+ if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ level = getGsmLevel();
+ }
+ }
+ return level;
+ }
+
+ /** Returns the signal strength related to CDMA. */
+ private int getCdmaRelatedSignalStrength() {
+ int level;
+ int cdmaLevel = getCdmaLevel();
+ int evdoLevel = getEvdoLevel();
+ if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ /* We don't know evdo, use cdma */
+ level = cdmaLevel;
+ } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ /* We don't know cdma, use evdo */
+ level = evdoLevel;
+ } else {
+ /* We know both, use the lowest level */
+ level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
+ }
+ return level;
+ }
+
/**
* Set SignalStrength based on intent notifier map
*
diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java
index 5a57f322..f0d60b68 100644
--- a/android/telephony/mbms/DownloadRequest.java
+++ b/android/telephony/mbms/DownloadRequest.java
@@ -16,6 +16,7 @@
package android.telephony.mbms;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Intent;
import android.net.Uri;
@@ -26,7 +27,6 @@ import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@@ -71,6 +71,19 @@ public final class DownloadRequest implements Parcelable {
private String appIntent;
private int version = CURRENT_VERSION;
+
+ /**
+ * Builds a new DownloadRequest.
+ * @param sourceUri the source URI for the DownloadRequest to be built. This URI should
+ * never be null.
+ */
+ public Builder(@NonNull Uri sourceUri) {
+ if (sourceUri == null) {
+ throw new IllegalArgumentException("Source URI must be non-null.");
+ }
+ source = sourceUri;
+ }
+
/**
* Sets the service from which the download request to be built will download from.
* @param serviceInfo
@@ -92,15 +105,6 @@ public final class DownloadRequest implements Parcelable {
}
/**
- * Sets the source URI for the download request to be built.
- * @param source
- */
- public Builder setSource(Uri source) {
- this.source = source;
- return this;
- }
-
- /**
* Set the subscription ID on which the file(s) should be downloaded.
* @param subscriptionId
*/
@@ -316,9 +320,11 @@ public final class DownloadRequest implements Parcelable {
throw new RuntimeException("Could not get sha256 hash object");
}
if (version >= 1) {
- // Hash the source URI, destination URI, and the app intent
+ // Hash the source URI and the app intent
digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
- digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+ if (serializedResultIntentForApp != null) {
+ digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+ }
}
// Add updates for future versions here
return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index fe275372..9af1eb9e 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -287,7 +287,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return;
}
- List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST);
+ List<Uri> tempFiles = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST);
if (tempFiles == null) {
return;
}
@@ -309,7 +309,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return;
}
int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
- List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
+ List<Uri> pausedList = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST);
if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -492,9 +492,14 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
}
+ if (appInfo.metaData == null) {
+ throw new RuntimeException("App must declare the file provider authority as metadata " +
+ "in the manifest.");
+ }
String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
if (authority == null) {
- throw new RuntimeException("Must declare the file provider authority as meta data");
+ throw new RuntimeException("App must declare the file provider authority as metadata " +
+ "in the manifest.");
}
return authority;
}
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 2f85a1df..9ccdd56f 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -113,15 +113,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
@Override
public final int initialize(final int subscriptionId,
final IMbmsDownloadSessionCallback callback) throws RemoteException {
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
final int uid = Binder.getCallingUid();
- callback.asBinder().linkToDeath(new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, subscriptionId);
- }
- }, 0);
- return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
+ int result = initialize(subscriptionId, new MbmsDownloadSessionCallback() {
@Override
public void onError(int errorCode, String message) {
try {
@@ -149,6 +147,17 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
}
}
});
+
+ if (result == MbmsErrors.SUCCESS) {
+ callback.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, subscriptionId);
+ }
+ }, 0);
+ }
+
+ return result;
}
/**
@@ -240,16 +249,12 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
public final int registerStateCallback(final DownloadRequest downloadRequest,
final IDownloadStateCallback callback, int flags) throws RemoteException {
final int uid = Binder.getCallingUid();
- DeathRecipient deathRecipient = new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
- mDownloadCallbackBinderMap.remove(callback.asBinder());
- mDownloadCallbackDeathRecipients.remove(callback.asBinder());
- }
- };
- mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
- callback.asBinder().linkToDeath(deathRecipient, 0);
+ if (downloadRequest == null) {
+ throw new NullPointerException("Download request must not be null");
+ }
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
@Override
@@ -258,9 +263,23 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
}
};
- mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+ int result = registerStateCallback(downloadRequest, exposedCallback);
- return registerStateCallback(downloadRequest, exposedCallback);
+ if (result == MbmsErrors.SUCCESS) {
+ DeathRecipient deathRecipient = new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+ mDownloadCallbackBinderMap.remove(callback.asBinder());
+ mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+ }
+ };
+ mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+ callback.asBinder().linkToDeath(deathRecipient, 0);
+ mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+ }
+
+ return result;
}
/**
@@ -292,6 +311,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
public final int unregisterStateCallback(
final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
throws RemoteException {
+ if (downloadRequest == null) {
+ throw new NullPointerException("Download request must not be null");
+ }
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
DeathRecipient deathRecipient =
mDownloadCallbackDeathRecipients.remove(callback.asBinder());
if (deathRecipient == null) {
diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index f8f370a5..a2381536 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -65,15 +65,13 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public final int initialize(final IMbmsStreamingSessionCallback callback,
final int subscriptionId) throws RemoteException {
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
final int uid = Binder.getCallingUid();
- callback.asBinder().linkToDeath(new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, subscriptionId);
- }
- }, 0);
- return initialize(new MbmsStreamingSessionCallback() {
+ int result = initialize(new MbmsStreamingSessionCallback() {
@Override
public void onError(final int errorCode, final String message) {
try {
@@ -101,6 +99,17 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
}
}
}, subscriptionId);
+
+ if (result == MbmsErrors.SUCCESS) {
+ callback.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, subscriptionId);
+ }
+ }, 0);
+ }
+
+ return result;
}
@@ -152,15 +161,13 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public int startStreaming(final int subscriptionId, String serviceId,
final IStreamingServiceCallback callback) throws RemoteException {
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
final int uid = Binder.getCallingUid();
- callback.asBinder().linkToDeath(new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, subscriptionId);
- }
- }, 0);
- return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+ int result = startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
@Override
public void onError(final int errorCode, final String message) {
try {
@@ -207,6 +214,17 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
}
}
});
+
+ if (result == MbmsErrors.SUCCESS) {
+ callback.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, subscriptionId);
+ }
+ }, 0);
+ }
+
+ return result;
}
/**
diff --git a/android/text/AndroidBidi.java b/android/text/AndroidBidi.java
index bbe15232..179d545f 100644
--- a/android/text/AndroidBidi.java
+++ b/android/text/AndroidBidi.java
@@ -16,6 +16,11 @@
package android.text;
+import android.icu.lang.UCharacter;
+import android.icu.lang.UCharacterDirection;
+import android.icu.lang.UProperty;
+import android.icu.text.Bidi;
+import android.icu.text.BidiClassifier;
import android.text.Layout.Directions;
import com.android.internal.annotations.VisibleForTesting;
@@ -27,26 +32,57 @@ import com.android.internal.annotations.VisibleForTesting;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class AndroidBidi {
- public static int bidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
+ private static class EmojiBidiOverride extends BidiClassifier {
+ EmojiBidiOverride() {
+ super(null /* No persisting object needed */);
+ }
+
+ // Tells ICU to use the standard Unicode value.
+ private static final int NO_OVERRIDE =
+ UCharacter.getIntPropertyMaxValue(UProperty.BIDI_CLASS) + 1;
+
+ @Override
+ public int classify(int c) {
+ if (Emoji.isNewEmoji(c)) {
+ // All new emoji characters in Unicode 10.0 are of the bidi class ON.
+ return UCharacterDirection.OTHER_NEUTRAL;
+ } else {
+ return NO_OVERRIDE;
+ }
+ }
+ }
+
+ private static final EmojiBidiOverride sEmojiBidiOverride = new EmojiBidiOverride();
+
+ /**
+ * Runs the bidi algorithm on input text.
+ */
+ public static int bidi(int dir, char[] chs, byte[] chInfo) {
if (chs == null || chInfo == null) {
throw new NullPointerException();
}
- if (n < 0 || chs.length < n || chInfo.length < n) {
+ final int length = chs.length;
+ if (chInfo.length < length) {
throw new IndexOutOfBoundsException();
}
- switch(dir) {
- case Layout.DIR_REQUEST_LTR: dir = 0; break;
- case Layout.DIR_REQUEST_RTL: dir = 1; break;
- case Layout.DIR_REQUEST_DEFAULT_LTR: dir = -2; break;
- case Layout.DIR_REQUEST_DEFAULT_RTL: dir = -1; break;
- default: dir = 0; break;
+ final byte paraLevel;
+ switch (dir) {
+ case Layout.DIR_REQUEST_LTR: paraLevel = Bidi.LTR; break;
+ case Layout.DIR_REQUEST_RTL: paraLevel = Bidi.RTL; break;
+ case Layout.DIR_REQUEST_DEFAULT_LTR: paraLevel = Bidi.LEVEL_DEFAULT_LTR; break;
+ case Layout.DIR_REQUEST_DEFAULT_RTL: paraLevel = Bidi.LEVEL_DEFAULT_RTL; break;
+ default: paraLevel = Bidi.LTR; break;
}
-
- int result = runBidi(dir, chs, chInfo, n, haveInfo);
- result = (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
- return result;
+ final Bidi icuBidi = new Bidi(length /* maxLength */, 0 /* maxRunCount */);
+ icuBidi.setCustomClassifier(sEmojiBidiOverride);
+ icuBidi.setPara(chs, paraLevel, null /* embeddingLevels */);
+ for (int i = 0; i < length; i++) {
+ chInfo[i] = icuBidi.getLevelAt(i);
+ }
+ final byte result = icuBidi.getParaLevel();
+ return (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
}
/**
@@ -178,6 +214,4 @@ public class AndroidBidi {
}
return new Directions(ld);
}
-
- private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
} \ No newline at end of file
diff --git a/android/text/AndroidBidi_Delegate.java b/android/text/AndroidBidi_Delegate.java
deleted file mode 100644
index 38171dc0..00000000
--- a/android/text/AndroidBidi_Delegate.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.icu.text.Bidi;
-
-/**
- * Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
- *
- * Through the layoutlib_create tool, the original methods of AndroidBidi have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class AndroidBidi_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count,
- boolean haveInfo) {
-
- switch (dir) {
- case 0: // Layout.DIR_REQUEST_LTR
- dir = Bidi.LTR;
- break;
- case 1: // Layout.DIR_REQUEST_RTL
- dir = Bidi.RTL;
- break;
- case -1: // Layout.DIR_REQUEST_DEFAULT_RTL
- dir = Bidi.LEVEL_DEFAULT_RTL;
- break;
- case -2: // Layout.DIR_REQUEST_DEFAULT_LTR
- dir = Bidi.LEVEL_DEFAULT_LTR;
- break;
- default:
- // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue.
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null);
- dir = Bidi.LEVEL_DEFAULT_LTR;
- }
- Bidi bidi = new Bidi(chars, 0, null, 0, count, dir);
- if (charInfo != null) {
- for (int i = 0; i < count; ++i)
- charInfo[i] = bidi.getLevelAt(i);
- }
- return bidi.getParaLevel();
- }
-}
diff --git a/android/text/BoringLayoutCreateDrawPerfTest.java b/android/text/BoringLayoutCreateDrawPerfTest.java
index 47dd257b..586c3852 100644
--- a/android/text/BoringLayoutCreateDrawPerfTest.java
+++ b/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -46,7 +46,7 @@ public class BoringLayoutCreateDrawPerfTest {
private static final float SPACING_ADD = 10f;
private static final float SPACING_MULT = 1.5f;
- @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/BoringLayoutIsBoringPerfTest.java b/android/text/BoringLayoutIsBoringPerfTest.java
index 34de65de..9d11f295 100644
--- a/android/text/BoringLayoutIsBoringPerfTest.java
+++ b/android/text/BoringLayoutIsBoringPerfTest.java
@@ -40,7 +40,7 @@ public class BoringLayoutIsBoringPerfTest {
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={4},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={4},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/DynamicLayout.java b/android/text/DynamicLayout.java
index 24260c4f..fba358cf 100644
--- a/android/text/DynamicLayout.java
+++ b/android/text/DynamicLayout.java
@@ -299,7 +299,7 @@ public class DynamicLayout extends Layout
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
- private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
/**
@@ -440,7 +440,7 @@ public class DynamicLayout extends Layout
mEllipsizeAt = null;
}
- mObjects = new PackedObjectVector<Directions>(1);
+ mObjects = new PackedObjectVector<>(1);
// Initial state is a single line with 0 characters (0 to 0), with top at 0 and bottom at
// whatever is natural, and undefined ellipsis.
@@ -1050,7 +1050,7 @@ public class DynamicLayout extends Layout
private static class ChangeWatcher implements TextWatcher, SpanWatcher {
public ChangeWatcher(DynamicLayout layout) {
- mLayout = new WeakReference<DynamicLayout>(layout);
+ mLayout = new WeakReference<>(layout);
}
private void reflow(CharSequence s, int where, int before, int after) {
diff --git a/android/text/Hyphenator.java b/android/text/Hyphenator.java
index ad26f23a..4f1488e1 100644
--- a/android/text/Hyphenator.java
+++ b/android/text/Hyphenator.java
@@ -16,258 +16,15 @@
package android.text;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Locale;
-
/**
- * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
+ * Hyphenator just initializes the native implementation of automatic hyphenation,
* in essence finding valid hyphenation opportunities in a word.
*
* @hide
*/
public class Hyphenator {
- private static String TAG = "Hyphenator";
-
- private final static Object sLock = new Object();
-
- @GuardedBy("sLock")
- final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
-
- private final long mNativePtr;
- private final HyphenationData mData;
-
- private Hyphenator(long nativePtr, HyphenationData data) {
- mNativePtr = nativePtr;
- mData = data;
- }
-
- public long getNativePtr() {
- return mNativePtr;
- }
-
- public static Hyphenator get(@Nullable Locale locale) {
- synchronized (sLock) {
- Hyphenator result = sMap.get(locale);
- if (result != null) {
- return result;
- }
-
- // If there's a variant, fall back to language+variant only, if available
- final String variant = locale.getVariant();
- if (!variant.isEmpty()) {
- final Locale languageAndVariantOnlyLocale =
- new Locale(locale.getLanguage(), "", variant);
- result = sMap.get(languageAndVariantOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
- }
-
- // Fall back to language-only, if available
- final Locale languageOnlyLocale = new Locale(locale.getLanguage());
- result = sMap.get(languageOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
-
- // Fall back to script-only, if available
- final String script = locale.getScript();
- if (!script.equals("")) {
- final Locale scriptOnlyLocale = new Locale.Builder()
- .setLanguage("und")
- .setScript(script)
- .build();
- result = sMap.get(scriptOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
- }
-
- return putEmptyAlias(locale);
- }
- }
-
- private static class HyphenationData {
- private static final String SYSTEM_HYPHENATOR_LOCATION = "/system/usr/hyphen-data";
-
- public final int mMinPrefix, mMinSuffix;
- public final long mDataAddress;
-
- // Reasonable enough values for cases where we have no hyphenation patterns but may be able
- // to do some automatic hyphenation based on characters. These values would be used very
- // rarely.
- private static final int DEFAULT_MIN_PREFIX = 2;
- private static final int DEFAULT_MIN_SUFFIX = 2;
-
- public static final HyphenationData sEmptyData =
- new HyphenationData(DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX);
-
- // Create empty HyphenationData.
- private HyphenationData(int minPrefix, int minSuffix) {
- mMinPrefix = minPrefix;
- mMinSuffix = minSuffix;
- mDataAddress = 0;
- }
-
- HyphenationData(String languageTag, int minPrefix, int minSuffix) {
- mMinPrefix = minPrefix;
- mMinSuffix = minSuffix;
-
- final String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb";
- final File patternFile = new File(SYSTEM_HYPHENATOR_LOCATION, patternFilename);
- if (!patternFile.canRead()) {
- Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable");
- mDataAddress = 0;
- } else {
- long address;
- try (RandomAccessFile f = new RandomAccessFile(patternFile, "r")) {
- address = Os.mmap(0, f.length(), OsConstants.PROT_READ,
- OsConstants.MAP_SHARED, f.getFD(), 0 /* offset */);
- } catch (IOException | ErrnoException e) {
- Log.e(TAG, "error loading hyphenation " + patternFile, e);
- address = 0;
- }
- mDataAddress = address;
- }
- }
- }
-
- // Do not call this method outside of init method.
- private static Hyphenator putNewHyphenator(Locale loc, HyphenationData data) {
- final Hyphenator hyphenator = new Hyphenator(nBuildHyphenator(
- data.mDataAddress, loc.getLanguage(), data.mMinPrefix, data.mMinSuffix), data);
- sMap.put(loc, hyphenator);
- return hyphenator;
- }
-
- // Do not call this method outside of init method.
- private static void loadData(String langTag, int minPrefix, int maxPrefix) {
- final HyphenationData data = new HyphenationData(langTag, minPrefix, maxPrefix);
- putNewHyphenator(Locale.forLanguageTag(langTag), data);
- }
-
- // Caller must acquire sLock before calling this method.
- // The Hyphenator for the baseLangTag must exists.
- private static Hyphenator addAliasByTag(String langTag, String baseLangTag) {
- return putAlias(Locale.forLanguageTag(langTag),
- sMap.get(Locale.forLanguageTag(baseLangTag)));
- }
-
- // Caller must acquire sLock before calling this method.
- private static Hyphenator putAlias(Locale locale, Hyphenator base) {
- return putNewHyphenator(locale, base.mData);
- }
-
- // Caller must acquire sLock before calling this method.
- private static Hyphenator putEmptyAlias(Locale locale) {
- return putNewHyphenator(locale, HyphenationData.sEmptyData);
- }
-
- // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but
- // that appears too small.
- private static final int INDIC_MIN_PREFIX = 2;
- private static final int INDIC_MIN_SUFFIX = 2;
-
- /**
- * Load hyphenation patterns at initialization time. We want to have patterns
- * for all locales loaded and ready to use so we don't have to do any file IO
- * on the UI thread when drawing text in different locales.
- *
- * @hide
- */
public static void init() {
- synchronized (sLock) {
- sMap.put(null, null);
-
- loadData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
- loadData("bg", 2, 2); // Bulgarian
- loadData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
- loadData("cu", 1, 2); // Church Slavonic
- loadData("cy", 2, 3); // Welsh
- loadData("da", 2, 2); // Danish
- loadData("de-1901", 2, 2); // German 1901 orthography
- loadData("de-1996", 2, 2); // German 1996 orthography
- loadData("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
- loadData("en-GB", 2, 3); // British English
- loadData("en-US", 2, 3); // American English
- loadData("es", 2, 2); // Spanish
- loadData("et", 2, 3); // Estonian
- loadData("eu", 2, 2); // Basque
- loadData("fr", 2, 3); // French
- loadData("ga", 2, 3); // Irish
- loadData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
- loadData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
- loadData("hr", 2, 2); // Croatian
- loadData("hu", 2, 2); // Hungarian
- // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
- // Going with a more conservative value of (2, 2) for now.
- loadData("hy", 2, 2); // Armenian
- loadData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
- loadData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
- loadData("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
- loadData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
- loadData("nb", 2, 2); // Norwegian Bokmål
- loadData("nn", 2, 2); // Norwegian Nynorsk
- loadData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
- loadData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
- loadData("pt", 2, 3); // Portuguese
- loadData("sl", 2, 2); // Slovenian
- loadData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
- loadData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
- loadData("tk", 2, 2); // Turkmen
- loadData("und-Ethi", 1, 1); // Any language in Ethiopic script
-
- // English locales that fall back to en-US. The data is
- // from CLDR. It's all English locales, minus the locales whose
- // parent is en-001 (from supplementalData.xml, under <parentLocales>).
- // TODO: Figure out how to get this from ICU.
- addAliasByTag("en-AS", "en-US"); // English (American Samoa)
- addAliasByTag("en-GU", "en-US"); // English (Guam)
- addAliasByTag("en-MH", "en-US"); // English (Marshall Islands)
- addAliasByTag("en-MP", "en-US"); // English (Northern Mariana Islands)
- addAliasByTag("en-PR", "en-US"); // English (Puerto Rico)
- addAliasByTag("en-UM", "en-US"); // English (United States Minor Outlying Islands)
- addAliasByTag("en-VI", "en-US"); // English (Virgin Islands)
-
- // All English locales other than those falling back to en-US are mapped to en-GB.
- addAliasByTag("en", "en-GB");
-
- // For German, we're assuming the 1996 (and later) orthography by default.
- addAliasByTag("de", "de-1996");
- // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
- addAliasByTag("de-LI-1901", "de-CH-1901");
-
- // Norwegian is very probably Norwegian Bokmål.
- addAliasByTag("no", "nb");
-
- // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
- addAliasByTag("mn", "mn-Cyrl"); // Mongolian
-
- // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
- // Data is from CLDR's likelySubtags.xml.
- // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
- addAliasByTag("am", "und-Ethi"); // Amharic
- addAliasByTag("byn", "und-Ethi"); // Blin
- addAliasByTag("gez", "und-Ethi"); // Geʻez
- addAliasByTag("ti", "und-Ethi"); // Tigrinya
- addAliasByTag("wal", "und-Ethi"); // Wolaytta
- }
- };
-
- private static native long nBuildHyphenator(/* non-zero */ long dataAddress,
- @NonNull String langTag, @IntRange(from = 1) int minPrefix,
- @IntRange(from = 1) int minSuffix);
+ nInit();
+ }
+ private static native void nInit();
}
diff --git a/android/text/Layout.java b/android/text/Layout.java
index 60fff738..ac5c2e92 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -319,8 +319,6 @@ public abstract class Layout {
private float getJustifyWidth(int lineNum) {
Alignment paraAlign = mAlignment;
- TabStops tabStops = null;
- boolean tabStopsIsInitialized = false;
int left = 0;
int right = mWidth;
@@ -371,10 +369,6 @@ public abstract class Layout {
}
}
- if (getLineContainsTab(lineNum)) {
- tabStops = new TabStops(TAB_INCREMENT, spans);
- }
-
final Alignment align;
if (paraAlign == Alignment.ALIGN_LEFT) {
align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
@@ -1423,7 +1417,6 @@ public abstract class Layout {
float dist = Math.abs(getHorizontal(max, primary) - horiz);
if (dist <= bestdist) {
- bestdist = dist;
best = max;
}
@@ -1570,7 +1563,7 @@ public abstract class Layout {
// XXX: we don't care about tabs
tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
- tl = TextLine.recycle(tl);
+ TextLine.recycle(tl);
return caret;
}
@@ -1894,10 +1887,7 @@ public abstract class Layout {
int margin = 0;
- boolean isFirstParaLine = lineStart == 0 ||
- spanned.charAt(lineStart - 1) == '\n';
-
- boolean useFirstLineMargin = isFirstParaLine;
+ boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n';
for (int i = 0; i < spans.length; i++) {
if (spans[i] instanceof LeadingMarginSpan2) {
int spStart = spanned.getSpanStart(spans[i]);
diff --git a/android/text/MeasuredText.java b/android/text/MeasuredText.java
index b09ccc29..ffc44a72 100644
--- a/android/text/MeasuredText.java
+++ b/android/text/MeasuredText.java
@@ -106,8 +106,8 @@ class MeasuredText {
if (mWidths == null || mWidths.length < len) {
mWidths = ArrayUtils.newUnpaddedFloatArray(len);
}
- if (mChars == null || mChars.length < len) {
- mChars = ArrayUtils.newUnpaddedCharArray(len);
+ if (mChars == null || mChars.length != len) {
+ mChars = new char[len];
}
TextUtils.getChars(text, start, end, mChars, 0);
@@ -151,7 +151,7 @@ class MeasuredText {
boolean isRtl = textDir.isRtl(mChars, 0, len);
bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
}
- mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+ mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels);
mEasy = false;
}
}
diff --git a/android/text/PaintMeasureDrawPerfTest.java b/android/text/PaintMeasureDrawPerfTest.java
index 00b60add..67687985 100644
--- a/android/text/PaintMeasureDrawPerfTest.java
+++ b/android/text/PaintMeasureDrawPerfTest.java
@@ -42,7 +42,7 @@ public class PaintMeasureDrawPerfTest {
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={1},{0} chars")
+ @Parameterized.Parameters(name = "cached={1},{0}chars")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java
index 961cd8ee..5c60188d 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -21,21 +21,18 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
-import android.os.LocaleList;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.TabStopSpan;
import android.util.Log;
-import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import java.util.Arrays;
-import java.util.Locale;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -101,7 +98,6 @@ public class StaticLayout extends Layout {
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
- b.mLocales = null;
b.mMeasuredText = MeasuredText.obtain();
return b;
@@ -118,7 +114,6 @@ public class StaticLayout extends Layout {
b.mMeasuredText = null;
b.mLeftIndents = null;
b.mRightIndents = null;
- b.mLocales = null;
b.mLeftPaddings = null;
b.mRightPaddings = null;
nFinishBuilder(b.mNativePtr);
@@ -409,17 +404,6 @@ public class StaticLayout extends Layout {
return this;
}
- @NonNull
- private long[] getHyphenators(@NonNull LocaleList locales) {
- final int length = locales.size();
- final long[] result = new long[length];
- for (int i = 0; i < length; i++) {
- final Locale locale = locales.get(i);
- result[i] = Hyphenator.get(locale).getNativePtr();
- }
- return result;
- }
-
/**
* Measurement and break iteration is done in native code. The protocol for using
* the native code is as follows.
@@ -433,35 +417,17 @@ public class StaticLayout extends Layout {
* + addStyleRun (a text run, to be measured in native code)
* + addReplacementRun (a replacement run, width is given)
*
- * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
* Run nComputeLineBreaks() to obtain line breaks for the paragraph.
*
* After all paragraphs, call finish() to release expensive buffers.
*/
- private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
- final LocaleList locales = paint.getTextLocales();
- final String languageTags;
- long[] hyphenators;
- if (!locales.equals(mLocales)) {
- mLocales = locales;
- return new Pair(locales.toLanguageTags(), getHyphenators(locales));
- } else {
- // passing null means keep current locale.
- // TODO: move locale change detection to native.
- return new Pair(null, null);
- }
- }
-
/* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
- Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
- locHyph.second);
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
}
/* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
- Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
- nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second);
+ nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
}
/**
@@ -519,9 +485,7 @@ public class StaticLayout extends Layout {
// This will go away and be subsumed by native builder code
private MeasuredText mMeasuredText;
- private LocaleList mLocales;
-
- private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
public StaticLayout(CharSequence source, TextPaint paint,
@@ -810,9 +774,6 @@ public class StaticLayout extends Layout {
}
}
- // TODO: Move locale tracking code to native.
- b.mLocales = null; // Reset the locale tracking.
-
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
firstWidth, firstWidthLineCount, restWidth,
variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
@@ -866,10 +827,9 @@ public class StaticLayout extends Layout {
spanEndCacheCount++;
}
- nGetWidths(b.mNativePtr, widths);
int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
- lineBreaks.breaks.length);
+ lineBreaks.breaks.length, widths);
final int[] breaks = lineBreaks.breaks;
final float[] lineWidths = lineBreaks.widths;
@@ -947,10 +907,10 @@ public class StaticLayout extends Layout {
boolean moreChars = (endPos < bufEnd);
final int ascent = fallbackLineSpacing
- ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex]))
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
: fmAscent;
final int descent = fallbackLineSpacing
- ? Math.max(fmDescent, (int) Math.round(descents[breakIndex]))
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
: fmDescent;
v = out(source, here, endPos,
ascent, descent, fmTop, fmBottom,
@@ -1177,7 +1137,7 @@ public class StaticLayout extends Layout {
mWorkPaint.set(paint);
do {
final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
- widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir);
+ widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
if (ellipsizedWidth <= avail) {
lineFits = true;
} else {
@@ -1207,7 +1167,7 @@ public class StaticLayout extends Layout {
// This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
// should not be accessed while the method is running.
private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
- int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
+ int widthStart, float avail, TextUtils.TruncateAt where, int line,
TextPaint paint, boolean forceEllipsis, int dir) {
final int savedHyphenEdit = paint.getHyphenEdit();
paint.setHyphenEdit(0);
@@ -1541,26 +1501,28 @@ public class StaticLayout extends Layout {
@Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
@IntRange(from = 0) int indentsOffset);
+ // TODO: Make this method CriticalNative once native code defers doing layouts.
private static native void nAddStyleRun(
/* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
- @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
- @Nullable String languageTags, @Nullable long[] hyphenators);
+ @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
- private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
+ // TODO: Make this method CriticalNative once native code defers doing layouts.
+ private static native void nAddReplacementRun(
+ /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@IntRange(from = 0) int start, @IntRange(from = 0) int end,
- @FloatRange(from = 0.0f) float width, @Nullable String languageTags,
- @Nullable long[] hyphenators);
-
- private static native void nGetWidths(long nativePtr, float[] widths);
+ @FloatRange(from = 0.0f) float width);
// populates LineBreaks and returns the number of breaks found
//
// the arrays inside the LineBreaks objects are passed in as well
// to reduce the number of JNI calls in the common case where the
// arrays do not have to be resized
+ // The individual character widths will be returned in charWidths. The length of charWidths must
+ // be at least the length of the text.
private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength);
+ float[] recycleDescents, int[] recycleFlags, int recycleLength,
+ float[] charWidths);
private int mLineCount;
private int mTopPadding, mBottomPadding;
diff --git a/android/text/StaticLayoutCreateDrawPerfTest.java b/android/text/StaticLayoutCreateDrawPerfTest.java
index 356e2e0d..bfdb7589 100644
--- a/android/text/StaticLayoutCreateDrawPerfTest.java
+++ b/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -50,7 +50,7 @@ public class StaticLayoutCreateDrawPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index 63337f08..def3c91c 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -13,7 +13,6 @@ import android.icu.util.ULocale;
import android.text.Primitive.PrimitiveType;
import android.text.StaticLayout.LineBreaks;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -72,13 +71,11 @@ public class StaticLayout_Delegate {
@LayoutlibDelegate
/*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start,
- int end, boolean isRtl, String languageTags, long[] hyphenators) {
+ int end, boolean isRtl) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
return;
}
- builder.mLocales = languageTags;
- builder.mNativeHyphenators = hyphenators;
int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
@@ -86,30 +83,20 @@ public class StaticLayout_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width,
- String languageTags, long[] hyphenators) {
+ /*package*/ static void nAddReplacementRun(long nativeBuilder, long nativePaint, int start,
+ int end, float width) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
return;
}
- builder.mLocales = languageTags;
- builder.mNativeHyphenators = hyphenators;
builder.mWidths[start] = width;
Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
}
@LayoutlibDelegate
- /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
- }
- }
-
- @LayoutlibDelegate
/*package*/ static int nComputeLineBreaks(long nativeBuilder, LineBreaks recycle,
int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength) {
+ float[] recycleDescents, int[] recycleFlags, int recycleLength, float[] charWidths) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
@@ -118,7 +105,7 @@ public class StaticLayout_Delegate {
// compute all possible breakpoints.
int length = builder.mWidths.length;
- BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales));
+ BreakIterator it = BreakIterator.getLineInstance();
it.setText(new Segment(builder.mText, 0, length));
// average word length in english is 5. So, initialize the possible breaks with a guess.
@@ -149,6 +136,7 @@ public class StaticLayout_Delegate {
builder.mTabStopCalculator);
}
builder.mLineBreaker.computeBreaks(recycle);
+ System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
return recycle.breaks.length;
}
@@ -206,11 +194,9 @@ public class StaticLayout_Delegate {
* Java representation of the native Builder class.
*/
private static class Builder {
- String mLocales;
char[] mText;
float[] mWidths;
LineBreaker mLineBreaker;
- long[] mNativeHyphenators;
int mBreakStrategy;
LineWidth mLineWidth;
TabStops mTabStopCalculator;
diff --git a/android/text/TextLine.java b/android/text/TextLine.java
index 2dbff100..20c0ed87 100644
--- a/android/text/TextLine.java
+++ b/android/text/TextLine.java
@@ -73,7 +73,7 @@ class TextLine {
new SpanSet<ReplacementSpan>(ReplacementSpan.class);
private final DecorationInfo mDecorationInfo = new DecorationInfo();
- private final ArrayList<DecorationInfo> mDecorations = new ArrayList();
+ private final ArrayList<DecorationInfo> mDecorations = new ArrayList<>();
private static final TextLine[] sCached = new TextLine[3];
@@ -340,14 +340,14 @@ class TextLine {
boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
if (inSegment && advance) {
- return h += measureRun(segstart, offset, j, runIsRtl, fmi);
+ return h + measureRun(segstart, offset, j, runIsRtl, fmi);
}
float w = measureRun(segstart, j, j, runIsRtl, fmi);
h += advance ? w : -w;
if (inSegment) {
- return h += measureRun(segstart, offset, j, runIsRtl, null);
+ return h + measureRun(segstart, offset, j, runIsRtl, null);
}
if (codept == '\t') {
@@ -828,14 +828,14 @@ class TextLine {
}
if (info.isUnderlineText) {
final float thickness =
- Math.max(((Paint) wp).getUnderlineThickness(), 1.0f);
+ Math.max(wp.getUnderlineThickness(), 1.0f);
drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness,
decorationXLeft, decorationXRight, y);
}
if (info.isStrikeThruText) {
final float thickness =
- Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f);
+ Math.max(wp.getStrikeThruThickness(), 1.0f);
drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness,
decorationXLeft, decorationXRight, y);
}
diff --git a/android/text/TextViewSetTextMeasurePerfTest.java b/android/text/TextViewSetTextMeasurePerfTest.java
index a2bf33e1..ff2d57ed 100644
--- a/android/text/TextViewSetTextMeasurePerfTest.java
+++ b/android/text/TextViewSetTextMeasurePerfTest.java
@@ -40,7 +40,7 @@ import java.util.Locale;
import java.util.Random;
/**
- * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ * Performance test for {@link TextView} measure/draw.
*/
@LargeTest
@RunWith(Parameterized.class)
@@ -51,7 +51,7 @@ public class TextViewSetTextMeasurePerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/util/Log.java b/android/util/Log.java
index 02998653..b94e48b3 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,45 +16,12 @@
package android.util;
-import android.os.DeadSystemException;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.LineBreakBufferedWriter;
-
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.io.Writer;
import java.net.UnknownHostException;
/**
- * API for sending log output.
- *
- * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
- * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
- * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
- *
- * <p>The order in terms of verbosity, from least to most is
- * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled
- * into an application except during development. Debug logs are compiled
- * in but stripped at runtime. Error, warning and info logs are always kept.
- *
- * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
- * in your class:
- *
- * <pre>private static final String TAG = "MyActivity";</pre>
- *
- * and use that in subsequent calls to the log methods.
- * </p>
- *
- * <p><b>Tip:</b> Don't forget that when you make a call like
- * <pre>Log.v(TAG, "index=" + i);</pre>
- * that when you're building the string to pass into Log.d, the compiler uses a
- * StringBuilder and at least three allocations occur: the StringBuilder
- * itself, the buffer, and the String object. Realistically, there is also
- * another buffer allocation and copy, and even more pressure on the gc.
- * That means that if your log message is filtered out, you might be doing
- * significant work and incurring significant overhead.
+ * Mock Log implementation for testing on non android host.
*/
public final class Log {
@@ -88,29 +55,6 @@ public final class Log {
*/
public static final int ASSERT = 7;
- /**
- * Exception class used to capture a stack trace in {@link #wtf}.
- * @hide
- */
- public static class TerribleFailure extends Exception {
- TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
- }
-
- /**
- * Interface to handle terrible failures from {@link #wtf}.
- *
- * @hide
- */
- public interface TerribleFailureHandler {
- void onTerribleFailure(String tag, TerribleFailure what, boolean system);
- }
-
- private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
- public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
- RuntimeInit.wtf(tag, what, system);
- }
- };
-
private Log() {
}
@@ -121,7 +65,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -132,7 +76,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -142,7 +86,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -153,7 +97,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -163,7 +107,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println_native(LOG_ID_MAIN, INFO, tag, msg);
+ return println(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -174,7 +118,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
+ return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -184,7 +128,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println_native(LOG_ID_MAIN, WARN, tag, msg);
+ return println(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -195,31 +139,9 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
+ return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
- /**
- * Checks to see whether or not a log for the specified tag is loggable at the specified level.
- *
- * The default level of any tag is set to INFO. This means that any level above and including
- * INFO will be logged. Before you make any calls to a logging method you should check to see
- * if your tag should be logged. You can change the default level by setting a system property:
- * 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
- * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
- * turn off all logging for your tag. You can also create a local.prop file that with the
- * following in it:
- * 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
- * and place that in /data/local.prop.
- *
- * @param tag The tag to check.
- * @param level The level to check.
- * @return Whether or not that this is allowed to be logged.
- * @throws IllegalArgumentException is thrown if the tag.length() > 23
- * for Nougat (7.0) releases (API <= 23) and prior, there is no
- * tag limit of concern after this API level.
- */
- public static native boolean isLoggable(String tag, int level);
-
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -227,7 +149,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
+ return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
/**
@@ -237,7 +159,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println_native(LOG_ID_MAIN, ERROR, tag, msg);
+ return println(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -248,82 +170,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- */
- public static int wtf(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
- }
-
- /**
- * Like {@link #wtf(String, String)}, but also writes to the log the full
- * call stack.
- * @hide
- */
- public static int wtfStack(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, String)}, with an exception to log.
- * @param tag Used to identify the source of a log message.
- * @param tr An exception to log.
- */
- public static int wtf(String tag, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, Throwable)}, with a message as well.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- * @param tr An exception to log. May be null.
- */
- public static int wtf(String tag, String msg, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
- }
-
- static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
- boolean system) {
- TerribleFailure what = new TerribleFailure(msg, tr);
- // Only mark this as ERROR, do not use ASSERT since that should be
- // reserved for cases where the system is guaranteed to abort.
- // The onTerribleFailure call does not always cause a crash.
- int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
- sWtfHandler.onTerribleFailure(tag, what, system);
- return bytes;
- }
-
- static void wtfQuiet(int logId, String tag, String msg, boolean system) {
- TerribleFailure what = new TerribleFailure(msg, null);
- sWtfHandler.onTerribleFailure(tag, what, system);
- }
-
- /**
- * Sets the terrible failure handler, for testing.
- *
- * @return the old handler
- *
- * @hide
- */
- public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
- if (handler == null) {
- throw new NullPointerException("handler == null");
- }
- TerribleFailureHandler oldHandler = sWtfHandler;
- sWtfHandler = handler;
- return oldHandler;
+ return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -346,7 +193,7 @@ public final class Log {
}
StringWriter sw = new StringWriter();
- PrintWriter pw = new FastPrintWriter(sw, false, 256);
+ PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
@@ -361,7 +208,7 @@ public final class Log {
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
- return println_native(LOG_ID_MAIN, priority, tag, msg);
+ return println(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -370,115 +217,9 @@ public final class Log {
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
- /** @hide */ public static native int println_native(int bufID,
- int priority, String tag, String msg);
-
- /**
- * Return the maximum payload the log daemon accepts without truncation.
- * @return LOGGER_ENTRY_MAX_PAYLOAD.
- */
- private static native int logger_entry_max_payload_native();
-
- /**
- * Helper function for long messages. Uses the LineBreakBufferedWriter to break
- * up long messages and stacktraces along newlines, but tries to write in large
- * chunks. This is to avoid truncation.
- * @hide
- */
- public static int printlns(int bufID, int priority, String tag, String msg,
- Throwable tr) {
- ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
- // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
- // and the length of the tag.
- // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
- // is too expensive to compute that ahead of time.
- int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
- - 2 // Two terminators.
- - (tag != null ? tag.length() : 0) // Tag length.
- - 32; // Some slack.
- // At least assume you can print *some* characters (tag is not too large).
- bufferSize = Math.max(bufferSize, 100);
-
- LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
-
- lbbw.println(msg);
-
- if (tr != null) {
- // This is to reduce the amount of log spew that apps do in the non-error
- // condition of the network being unavailable.
- Throwable t = tr;
- while (t != null) {
- if (t instanceof UnknownHostException) {
- break;
- }
- if (t instanceof DeadSystemException) {
- lbbw.println("DeadSystemException: The system died; "
- + "earlier logs will point to the root cause");
- break;
- }
- t = t.getCause();
- }
- if (t == null) {
- tr.printStackTrace(lbbw);
- }
- }
-
- lbbw.flush();
-
- return logWriter.getWritten();
- }
-
- /**
- * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
- * a JNI call during logging.
- */
- static class PreloadHolder {
- public final static int LOGGER_ENTRY_MAX_PAYLOAD =
- logger_entry_max_payload_native();
- }
-
- /**
- * Helper class to write to the logcat. Different from LogWriter, this writes
- * the whole given buffer and does not break along newlines.
- */
- private static class ImmediateLogWriter extends Writer {
-
- private int bufID;
- private int priority;
- private String tag;
-
- private int written = 0;
-
- /**
- * Create a writer that immediately writes to the log, using the given
- * parameters.
- */
- public ImmediateLogWriter(int bufID, int priority, String tag) {
- this.bufID = bufID;
- this.priority = priority;
- this.tag = tag;
- }
-
- public int getWritten() {
- return written;
- }
-
- @Override
- public void write(char[] cbuf, int off, int len) {
- // Note: using String here has a bit of overhead as a Java object is created,
- // but using the char[] directly is not easier, as it needs to be translated
- // to a C char[] for logging.
- written += println_native(bufID, priority, tag, new String(cbuf, off, len));
- }
-
- @Override
- public void flush() {
- // Ignored.
- }
-
- @Override
- public void close() {
- // Ignored.
- }
+ /** @hide */ @SuppressWarnings("unused")
+ public static int println(int bufID,
+ int priority, String tag, String msg) {
+ return 0;
}
}
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 40154880..52086065 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,6 +20,10 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
+ * BEGIN LAYOUTLIB CHANGE
+ * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
+ * END LAYOUTLIB CHANGE
+ *
* A cache that holds strong references to a limited number of values. Each time
* a value is accessed, it is moved to the head of a queue. When a value is
* added to a full cache, the value at the end of that queue is evicted and may
@@ -87,8 +91,9 @@ public class LruCache<K, V> {
/**
* Sets the size of the cache.
- *
* @param maxSize The new maximum size.
+ *
+ * @hide
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
@@ -185,13 +190,10 @@ public class LruCache<K, V> {
}
/**
- * Remove the eldest entries until the total of remaining entries is at or
- * below the requested size.
- *
* @param maxSize the maximum size of the cache before returning. May be -1
- * to evict even 0-sized elements.
+ * to evict even 0-sized elements.
*/
- public void trimToSize(int maxSize) {
+ private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
@@ -205,7 +207,16 @@ public class LruCache<K, V> {
break;
}
- Map.Entry<K, V> toEvict = map.eldest();
+ // BEGIN LAYOUTLIB CHANGE
+ // get the last item in the linked list.
+ // This is not efficient, the goal here is to minimize the changes
+ // compared to the platform version.
+ Map.Entry<K, V> toEvict = null;
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ toEvict = entry;
+ }
+ // END LAYOUTLIB CHANGE
+
if (toEvict == null) {
break;
}
diff --git a/android/util/MutableBoolean.java b/android/util/MutableBoolean.java
index 5a8a200d..ed837ab6 100644
--- a/android/util/MutableBoolean.java
+++ b/android/util/MutableBoolean.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableBoolean {
- public boolean value;
+ public boolean value;
- public MutableBoolean(boolean value) {
- this.value = value;
- }
+ public MutableBoolean(boolean value) {
+ this.value = value;
+ }
}
diff --git a/android/util/MutableByte.java b/android/util/MutableByte.java
index 7397ba47..cc6b00a8 100644
--- a/android/util/MutableByte.java
+++ b/android/util/MutableByte.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableByte {
- public byte value;
+ public byte value;
- public MutableByte(byte value) {
- this.value = value;
- }
+ public MutableByte(byte value) {
+ this.value = value;
+ }
}
diff --git a/android/util/MutableChar.java b/android/util/MutableChar.java
index f435331b..9a2e2bce 100644
--- a/android/util/MutableChar.java
+++ b/android/util/MutableChar.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableChar {
- public char value;
+ public char value;
- public MutableChar(char value) {
- this.value = value;
- }
+ public MutableChar(char value) {
+ this.value = value;
+ }
}
diff --git a/android/util/MutableDouble.java b/android/util/MutableDouble.java
index f62f47e2..bd7329a3 100644
--- a/android/util/MutableDouble.java
+++ b/android/util/MutableDouble.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableDouble {
- public double value;
+ public double value;
- public MutableDouble(double value) {
- this.value = value;
- }
+ public MutableDouble(double value) {
+ this.value = value;
+ }
}
diff --git a/android/util/MutableFloat.java b/android/util/MutableFloat.java
index 6b5441c5..e6f2d7dc 100644
--- a/android/util/MutableFloat.java
+++ b/android/util/MutableFloat.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableFloat {
- public float value;
+ public float value;
- public MutableFloat(float value) {
- this.value = value;
- }
+ public MutableFloat(float value) {
+ this.value = value;
+ }
}
diff --git a/android/util/MutableInt.java b/android/util/MutableInt.java
index 2f930302..a3d8606d 100644
--- a/android/util/MutableInt.java
+++ b/android/util/MutableInt.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableInt {
- public int value;
+ public int value;
- public MutableInt(int value) {
- this.value = value;
- }
+ public MutableInt(int value) {
+ this.value = value;
+ }
}
diff --git a/android/util/MutableLong.java b/android/util/MutableLong.java
index 94beab57..575068ea 100644
--- a/android/util/MutableLong.java
+++ b/android/util/MutableLong.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableLong {
- public long value;
+ public long value;
- public MutableLong(long value) {
- this.value = value;
- }
+ public MutableLong(long value) {
+ this.value = value;
+ }
}
diff --git a/android/util/MutableShort.java b/android/util/MutableShort.java
index cdd99230..48fb232b 100644
--- a/android/util/MutableShort.java
+++ b/android/util/MutableShort.java
@@ -19,9 +19,9 @@ package android.util;
/**
*/
public final class MutableShort {
- public short value;
+ public short value;
- public MutableShort(short value) {
- this.value = value;
- }
+ public MutableShort(short value) {
+ this.value = value;
+ }
}
diff --git a/android/util/StatsLog.java b/android/util/StatsLog.java
deleted file mode 100644
index 0be1a8cf..00000000
--- a/android/util/StatsLog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- * Logging access for platform metrics.
- *
- * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
- * These diagnostic stats are for system integrators, not application authors.
- *
- * <p>Stats use integer tag codes.
- * They carry a payload of one or more int, long, or String values.
- * @hide
- */
-public class StatsLog {
- /** @hide */ public StatsLog() {}
-
- private static final String TAG = "StatsLog";
-
- // We assume that the native methods deal with any concurrency issues.
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param value A value to log
- * @return The number of bytes written
- */
- public static native int writeInt(int tag, int value);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param value A value to log
- * @return The number of bytes written
- */
- public static native int writeLong(int tag, long value);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param value A value to log
- * @return The number of bytes written
- */
- public static native int writeFloat(int tag, float value);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param str A value to log
- * @return The number of bytes written
- */
- public static native int writeString(int tag, String str);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param list A list of values to log. All values should
- * be of type int, long, float or String.
- * @return The number of bytes written
- */
- public static native int writeArray(int tag, Object... list);
-}
diff --git a/android/util/StatsLogKey.java b/android/util/StatsLogKey.java
deleted file mode 100644
index 9ad0a23d..00000000
--- a/android/util/StatsLogKey.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// THIS FILE IS AUTO-GENERATED.
-// DO NOT MODIFY.
-
-package android.util;
-
-/** @hide */
-public class StatsLogKey {
- private StatsLogKey() {}
-
- /** Constants for android.os.statsd.ScreenStateChange. */
-
- /** display_state */
- public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE = 1;
-
- /** Constants for android.os.statsd.ProcessStateChange. */
-
- /** state */
- public static final int PROCESS_STATE_CHANGE__STATE = 1;
-
- /** uid */
- public static final int PROCESS_STATE_CHANGE__UID = 2;
-
- /** package_name */
- public static final int PROCESS_STATE_CHANGE__PACKAGE_NAME = 1002;
-
- /** package_version */
- public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION = 3;
-
- /** package_version_string */
- public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION_STRING = 4;
-
-}
diff --git a/android/util/StatsLogTag.java b/android/util/StatsLogTag.java
deleted file mode 100644
index 5e5a8287..00000000
--- a/android/util/StatsLogTag.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// THIS FILE IS AUTO-GENERATED.
-// DO NOT MODIFY.
-
-package android.util;
-
-/** @hide */
-public class StatsLogTag {
- private StatsLogTag() {}
-
- /** android.os.statsd.ScreenStateChange. */
- public static final int SCREEN_STATE_CHANGE = 2;
-
- /** android.os.statsd.ProcessStateChange. */
- public static final int PROCESS_STATE_CHANGE = 1112;
-
-}
diff --git a/android/util/StatsLogValue.java b/android/util/StatsLogValue.java
deleted file mode 100644
index 05b9d933..00000000
--- a/android/util/StatsLogValue.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// THIS FILE IS AUTO-GENERATED.
-// DO NOT MODIFY.
-
-package android.util;
-
-/** @hide */
-public class StatsLogValue {
- private StatsLogValue() {}
-
- /** Constants for android.os.statsd.ScreenStateChange. */
-
- /** display_state: STATE_UNKNOWN */
- public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_UNKNOWN = 0;
-
- /** display_state: STATE_OFF */
- public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF = 1;
-
- /** display_state: STATE_ON */
- public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON = 2;
-
- /** display_state: STATE_DOZE */
- public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE = 3;
-
- /** display_state: STATE_DOZE_SUSPEND */
- public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE_SUSPEND = 4;
-
- /** display_state: STATE_VR */
- public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_VR = 5;
-
- /** Constants for android.os.statsd.ProcessStateChange. */
-
- /** state: START */
- public static final int PROCESS_STATE_CHANGE__STATE__START = 1;
-
- /** state: CRASH */
- public static final int PROCESS_STATE_CHANGE__STATE__CRASH = 2;
-
-}
diff --git a/android/util/apk/ApkSignatureSchemeV2Verifier.java b/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 0216a075..a9ccae11 100644
--- a/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -17,6 +17,7 @@
package android.util.apk;
import android.system.ErrnoException;
+import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.Pair;
@@ -59,9 +60,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import libcore.io.Libcore;
-import libcore.io.Os;
-
/**
* APK Signature Scheme v2 verifier.
*
@@ -994,8 +992,7 @@ public class ApkSignatureSchemeV2Verifier {
* {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
*/
private static final class MemoryMappedFileDataSource implements DataSource {
- private static final Os OS = Libcore.os;
- private static final long MEMORY_PAGE_SIZE_BYTES = OS.sysconf(OsConstants._SC_PAGESIZE);
+ private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
private final FileDescriptor mFd;
private final long mFilePosition;
@@ -1041,7 +1038,7 @@ public class ApkSignatureSchemeV2Verifier {
long mmapRegionSize = size + dataStartOffsetInMmapRegion;
long mmapPtr = 0;
try {
- mmapPtr = OS.mmap(
+ mmapPtr = Os.mmap(
0, // let the OS choose the start address of the region in memory
mmapRegionSize,
OsConstants.PROT_READ,
@@ -1066,7 +1063,7 @@ public class ApkSignatureSchemeV2Verifier {
} finally {
if (mmapPtr != 0) {
try {
- OS.munmap(mmapPtr, mmapRegionSize);
+ Os.munmap(mmapPtr, mmapRegionSize);
} catch (ErrnoException ignored) {}
}
}
diff --git a/android/view/MenuItem.java b/android/view/MenuItem.java
index 88b9c0d3..ad160cbf 100644
--- a/android/view/MenuItem.java
+++ b/android/view/MenuItem.java
@@ -72,11 +72,6 @@ public interface MenuItem {
public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
/**
- * @hide
- */
- int SHOW_AS_OVERFLOW_ALWAYS = 1 << 31;
-
- /**
* Interface definition for a callback to be invoked when a menu item is
* clicked.
*
@@ -806,12 +801,22 @@ public interface MenuItem {
}
/**
- * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_OVERFLOW_ALWAYS}.
- * Default value if {@code false}.
+ * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_ALWAYS}.
+ * Default value is {@code false}.
*
* @hide
*/
- default boolean requiresOverflow() {
+ default boolean requiresActionButton() {
return false;
}
+
+ /**
+ * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_NEVER}.
+ * Default value is {@code true}.
+ *
+ * @hide
+ */
+ default boolean requiresOverflow() {
+ return true;
+ }
}
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 31daefff..ff027a94 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -21,15 +21,22 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import android.annotation.Size;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
+import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
+import android.os.Debug;
import android.os.IBinder;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
import dalvik.system.CloseGuard;
+import java.io.Closeable;
+
+import libcore.util.NativeAllocationRegistry;
+
/**
* SurfaceControl
* @hide
@@ -54,25 +61,34 @@ public class SurfaceControl {
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
- private static native void nativeOpenTransaction();
- private static native void nativeCloseTransaction(boolean sync);
- private static native void nativeSetAnimationTransaction();
-
- private static native void nativeSetLayer(long nativeObject, int zorder);
- private static native void nativeSetRelativeLayer(long nativeObject, IBinder relativeTo,
- int zorder);
- private static native void nativeSetPosition(long nativeObject, float x, float y);
- private static native void nativeSetGeometryAppliesWithResize(long nativeObject);
- private static native void nativeSetSize(long nativeObject, int w, int h);
- private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
- private static native void nativeSetAlpha(long nativeObject, float alpha);
- private static native void nativeSetColor(long nativeObject, float[] color);
- private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx,
+ private static native long nativeCreateTransaction();
+ private static native long nativeGetNativeTransactionFinalizer();
+ private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+ private static native void nativeSetAnimationTransaction(long transactionObj);
+
+ private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
+ private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
+ IBinder relativeTo, int zorder);
+ private static native void nativeSetPosition(long transactionObj, long nativeObject,
+ float x, float y);
+ private static native void nativeSetGeometryAppliesWithResize(long transactionObj,
+ long nativeObject);
+ private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
+ private static native void nativeSetTransparentRegionHint(long transactionObj,
+ long nativeObject, Region region);
+ private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha);
+ private static native void nativeSetMatrix(long transactionObj, long nativeObject,
+ float dsdx, float dtdx,
float dtdy, float dsdy);
- private static native void nativeSetFlags(long nativeObject, int flags, int mask);
- private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
- private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b);
- private static native void nativeSetLayerStack(long nativeObject, int layerStack);
+ private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
+ private static native void nativeSetFlags(long transactionObj, long nativeObject,
+ int flags, int mask);
+ private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
+ int l, int t, int r, int b);
+ private static native void nativeSetFinalCrop(long transactionObj, long nativeObject,
+ int l, int t, int r, int b);
+ private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
+ int layerStack);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -82,15 +98,16 @@ public class SurfaceControl {
private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
- private static native void nativeSetDisplaySurface(
+ private static native void nativeSetDisplaySurface(long transactionObj,
IBinder displayToken, long nativeSurfaceObject);
- private static native void nativeSetDisplayLayerStack(
+ private static native void nativeSetDisplayLayerStack(long transactionObj,
IBinder displayToken, int layerStack);
- private static native void nativeSetDisplayProjection(
+ private static native void nativeSetDisplayProjection(long transactionObj,
IBinder displayToken, int orientation,
int l, int t, int r, int b,
int L, int T, int R, int B);
- private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height);
+ private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
+ int width, int height);
private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
IBinder displayToken);
private static native int nativeGetActiveConfig(IBinder displayToken);
@@ -101,16 +118,17 @@ public class SurfaceControl {
int colorMode);
private static native void nativeSetDisplayPowerMode(
IBinder displayToken, int mode);
- private static native void nativeDeferTransactionUntil(long nativeObject,
+ private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
IBinder handle, long frame);
- private static native void nativeDeferTransactionUntilSurface(long nativeObject,
+ private static native void nativeDeferTransactionUntilSurface(long transactionObj,
+ long nativeObject,
long surfaceObject, long frame);
- private static native void nativeReparentChildren(long nativeObject,
+ private static native void nativeReparentChildren(long transactionObj, long nativeObject,
IBinder handle);
- private static native void nativeReparent(long nativeObject,
+ private static native void nativeReparent(long transactionObj, long nativeObject,
IBinder parentHandle);
- private static native void nativeSeverChildren(long nativeObject);
- private static native void nativeSetOverrideScalingMode(long nativeObject,
+ private static native void nativeSeverChildren(long transactionObj, long nativeObject);
+ private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
int scalingMode);
private static native IBinder nativeGetHandle(long nativeObject);
private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
@@ -122,6 +140,9 @@ public class SurfaceControl {
private final String mName;
long mNativeObject; // package visibility only for Surface.java access
+ static Transaction sGlobalTransaction;
+ static long sTransactionNestCount = 0;
+
/* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
/**
@@ -377,11 +398,6 @@ public class SurfaceControl {
}
}
- @Override
- public String toString() {
- return "Surface(name=" + mName + ")";
- }
-
/**
* Release the local reference to the server-side surface.
* Always call release() when you're done with a Surface.
@@ -429,102 +445,141 @@ public class SurfaceControl {
/** start a transaction */
public static void openTransaction() {
- nativeOpenTransaction();
+ synchronized (SurfaceControl.class) {
+ if (sGlobalTransaction == null) {
+ sGlobalTransaction = new Transaction();
+ }
+ synchronized(SurfaceControl.class) {
+ sTransactionNestCount++;
+ }
+ }
+ }
+
+ private static void closeTransaction(boolean sync) {
+ synchronized(SurfaceControl.class) {
+ if (sTransactionNestCount == 0) {
+ Log.e(TAG, "Call to SurfaceControl.closeTransaction without matching openTransaction");
+ } else if (--sTransactionNestCount > 0) {
+ return;
+ }
+ sGlobalTransaction.apply(sync);
+ }
}
/** end a transaction */
public static void closeTransaction() {
- nativeCloseTransaction(false);
+ closeTransaction(false);
}
public static void closeTransactionSync() {
- nativeCloseTransaction(true);
+ closeTransaction(true);
}
public void deferTransactionUntil(IBinder handle, long frame) {
if (frame > 0) {
- nativeDeferTransactionUntil(mNativeObject, handle, frame);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.deferTransactionUntil(this, handle, frame);
+ }
}
}
public void deferTransactionUntil(Surface barrier, long frame) {
if (frame > 0) {
- nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame);
+ }
}
}
public void reparentChildren(IBinder newParentHandle) {
- nativeReparentChildren(mNativeObject, newParentHandle);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.reparentChildren(this, newParentHandle);
+ }
}
- /** Re-parents this layer to a new parent. */
public void reparent(IBinder newParentHandle) {
- nativeReparent(mNativeObject, newParentHandle);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.reparent(this, newParentHandle);
+ }
}
public void detachChildren() {
- nativeSeverChildren(mNativeObject);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.detachChildren(this);
+ }
}
public void setOverrideScalingMode(int scalingMode) {
checkNotReleased();
- nativeSetOverrideScalingMode(mNativeObject, scalingMode);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setOverrideScalingMode(this, scalingMode);
+ }
}
public IBinder getHandle() {
return nativeGetHandle(mNativeObject);
}
- /** flag the transaction as an animation */
public static void setAnimationTransaction() {
- nativeSetAnimationTransaction();
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setAnimationTransaction();
+ }
}
public void setLayer(int zorder) {
checkNotReleased();
- nativeSetLayer(mNativeObject, zorder);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setLayer(this, zorder);
+ }
}
public void setRelativeLayer(IBinder relativeTo, int zorder) {
checkNotReleased();
- nativeSetRelativeLayer(mNativeObject, relativeTo, zorder);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
+ }
}
public void setPosition(float x, float y) {
checkNotReleased();
- nativeSetPosition(mNativeObject, x, y);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setPosition(this, x, y);
+ }
}
- /**
- * If the buffer size changes in this transaction, position and crop updates specified
- * in this transaction will not complete until a buffer of the new size
- * arrives. As transform matrix and size are already frozen in this fashion,
- * this enables totally freezing the surface until the resize has completed
- * (at which point the geometry influencing aspects of this transaction will then occur)
- */
public void setGeometryAppliesWithResize() {
checkNotReleased();
- nativeSetGeometryAppliesWithResize(mNativeObject);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setGeometryAppliesWithResize(this);
+ }
}
public void setSize(int w, int h) {
checkNotReleased();
- nativeSetSize(mNativeObject, w, h);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setSize(this, w, h);
+ }
}
public void hide() {
checkNotReleased();
- nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.hide(this);
+ }
}
public void show() {
checkNotReleased();
- nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.show(this);
+ }
}
public void setTransparentRegionHint(Region region) {
checkNotReleased();
- nativeSetTransparentRegionHint(mNativeObject, region);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setTransparentRegionHint(this, region);
+ }
}
public boolean clearContentFrameStats() {
@@ -545,80 +600,70 @@ public class SurfaceControl {
return nativeGetAnimationFrameStats(outStats);
}
- /**
- * Sets an alpha value for the entire Surface. This value is combined with the
- * per-pixel alpha. It may be used with opaque Surfaces.
- */
public void setAlpha(float alpha) {
checkNotReleased();
- nativeSetAlpha(mNativeObject, alpha);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setAlpha(this, alpha);
+ }
}
- /**
- * Sets a color for the Surface.
- * @param color A float array with three values to represent r, g, b in range [0..1]
- */
public void setColor(@Size(3) float[] color) {
checkNotReleased();
- nativeSetColor(mNativeObject, color);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setColor(this, color);
+ }
}
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
checkNotReleased();
- nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setMatrix(this, dsdx, dtdx, dtdy, dsdy);
+ }
}
public void setWindowCrop(Rect crop) {
checkNotReleased();
- if (crop != null) {
- nativeSetWindowCrop(mNativeObject,
- crop.left, crop.top, crop.right, crop.bottom);
- } else {
- nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setWindowCrop(this, crop);
}
}
public void setFinalCrop(Rect crop) {
checkNotReleased();
- if (crop != null) {
- nativeSetFinalCrop(mNativeObject,
- crop.left, crop.top, crop.right, crop.bottom);
- } else {
- nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setFinalCrop(this, crop);
}
}
public void setLayerStack(int layerStack) {
checkNotReleased();
- nativeSetLayerStack(mNativeObject, layerStack);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setLayerStack(this, layerStack);
+ }
}
- /**
- * Sets the opacity of the surface. Setting the flag is equivalent to creating the
- * Surface with the {@link #OPAQUE} flag.
- */
public void setOpaque(boolean isOpaque) {
checkNotReleased();
- if (isOpaque) {
- nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
- } else {
- nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE);
+
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setOpaque(this, isOpaque);
}
}
- /**
- * Sets the security of the surface. Setting the flag is equivalent to creating the
- * Surface with the {@link #SECURE} flag.
- */
public void setSecure(boolean isSecure) {
checkNotReleased();
- if (isSecure) {
- nativeSetFlags(mNativeObject, SECURE, SECURE);
- } else {
- nativeSetFlags(mNativeObject, 0, SECURE);
+
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setSecure(this, isSecure);
}
}
+ @Override
+ public String toString() {
+ return "Surface(name=" + mName + ")/@0x" +
+ Integer.toHexString(System.identityHashCode(this));
+ }
+
/*
* set display parameters.
* needs to be inside open/closeTransaction block
@@ -741,50 +786,28 @@ public class SurfaceControl {
public static void setDisplayProjection(IBinder displayToken,
int orientation, Rect layerStackRect, Rect displayRect) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (layerStackRect == null) {
- throw new IllegalArgumentException("layerStackRect must not be null");
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplayProjection(displayToken, orientation,
+ layerStackRect, displayRect);
}
- if (displayRect == null) {
- throw new IllegalArgumentException("displayRect must not be null");
- }
- nativeSetDisplayProjection(displayToken, orientation,
- layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
- displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
}
public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
}
- nativeSetDisplayLayerStack(displayToken, layerStack);
}
public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
-
- if (surface != null) {
- synchronized (surface.mLock) {
- nativeSetDisplaySurface(displayToken, surface.mNativeObject);
- }
- } else {
- nativeSetDisplaySurface(displayToken, 0);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplaySurface(displayToken, surface);
}
}
public static void setDisplaySize(IBinder displayToken, int width, int height) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("width and height must be positive");
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplaySize(displayToken, width, height);
}
-
- nativeSetDisplaySize(displayToken, width, height);
}
public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
@@ -946,4 +969,261 @@ public class SurfaceControl {
nativeScreenshot(display, consumer, sourceCrop, width, height,
minLayer, maxLayer, allLayers, useIdentityTransform);
}
+
+ public static class Transaction implements Closeable {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Transaction.class.getClassLoader(),
+ nativeGetNativeTransactionFinalizer(), 512);
+ private long mNativeObject;
+
+ Runnable mFreeNativeResources;
+
+ public Transaction() {
+ mNativeObject = nativeCreateTransaction();
+ mFreeNativeResources
+ = sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ /**
+ * Apply the transaction, clearing it's state, and making it usable
+ * as a new transaction.
+ */
+ public void apply() {
+ apply(false);
+ }
+
+ /**
+ * Close the transaction, if the transaction was not already applied this will cancel the
+ * transaction.
+ */
+ @Override
+ public void close() {
+ mFreeNativeResources.run();
+ mNativeObject = 0;
+ }
+
+ /**
+ * Jankier version of apply. Avoid use (b/28068298).
+ */
+ public void apply(boolean sync) {
+ nativeApplyTransaction(mNativeObject, sync);
+ }
+
+ public Transaction show(SurfaceControl sc) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
+ return this;
+ }
+
+ public Transaction hide(SurfaceControl sc) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+ return this;
+ }
+
+ public Transaction setPosition(SurfaceControl sc, float x, float y) {
+ nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
+ return this;
+ }
+
+ public Transaction setSize(SurfaceControl sc, int w, int h) {
+ nativeSetSize(mNativeObject, sc.mNativeObject,
+ w, h);
+ return this;
+ }
+
+ public Transaction setLayer(SurfaceControl sc, int z) {
+ nativeSetLayer(mNativeObject, sc.mNativeObject, z);
+ return this;
+ }
+
+ public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
+ nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
+ relativeTo, z);
+ return this;
+ }
+
+ public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+ nativeSetTransparentRegionHint(mNativeObject,
+ sc.mNativeObject, transparentRegion);
+ return this;
+ }
+
+ public Transaction setAlpha(SurfaceControl sc, float alpha) {
+ nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
+ return this;
+ }
+
+ public Transaction setMatrix(SurfaceControl sc,
+ float dsdx, float dtdx, float dtdy, float dsdy) {
+ nativeSetMatrix(mNativeObject, sc.mNativeObject,
+ dsdx, dtdx, dtdy, dsdy);
+ return this;
+ }
+
+ public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+ if (crop != null) {
+ nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+ }
+
+ return this;
+ }
+
+ public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
+ if (crop != null) {
+ nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetFinalCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+ }
+
+ return this;
+ }
+
+ public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+ nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
+ return this;
+ }
+
+ public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
+ nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
+ return this;
+ }
+
+ public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
+ long frameNumber) {
+ nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
+ barrierSurface.mNativeObject, frameNumber);
+ return this;
+ }
+
+ public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
+ nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
+ return this;
+ }
+
+ /** Re-parents a specific child layer to a new parent */
+ public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+ nativeReparent(mNativeObject, sc.mNativeObject,
+ newParentHandle);
+ return this;
+ }
+
+ public Transaction detachChildren(SurfaceControl sc) {
+ nativeSeverChildren(mNativeObject, sc.mNativeObject);
+ return this;
+ }
+
+ public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
+ nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
+ overrideScalingMode);
+ return this;
+ }
+
+ /**
+ * Sets a color for the Surface.
+ * @param color A float array with three values to represent r, g, b in range [0..1]
+ */
+ public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+ nativeSetColor(mNativeObject, sc.mNativeObject, color);
+ return this;
+ }
+
+ /**
+ * If the buffer size changes in this transaction, position and crop updates specified
+ * in this transaction will not complete until a buffer of the new size
+ * arrives. As transform matrix and size are already frozen in this fashion,
+ * this enables totally freezing the surface until the resize has completed
+ * (at which point the geometry influencing aspects of this transaction will then occur)
+ */
+ public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
+ nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
+ return this;
+ }
+
+ /**
+ * Sets the security of the surface. Setting the flag is equivalent to creating the
+ * Surface with the {@link #SECURE} flag.
+ */
+ Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+ if (isSecure) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
+ } else {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the opacity of the surface. Setting the flag is equivalent to creating the
+ * Surface with the {@link #OPAQUE} flag.
+ */
+ public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+ if (isOpaque) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
+ } else {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_OPAQUE);
+ }
+ return this;
+ }
+
+ public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ if (surface != null) {
+ synchronized (surface.mLock) {
+ nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject);
+ }
+ } else {
+ nativeSetDisplaySurface(mNativeObject, displayToken, 0);
+ }
+ return this;
+ }
+
+ public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack);
+ return this;
+ }
+
+ public Transaction setDisplayProjection(IBinder displayToken,
+ int orientation, Rect layerStackRect, Rect displayRect) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (layerStackRect == null) {
+ throw new IllegalArgumentException("layerStackRect must not be null");
+ }
+ if (displayRect == null) {
+ throw new IllegalArgumentException("displayRect must not be null");
+ }
+ nativeSetDisplayProjection(mNativeObject, displayToken, orientation,
+ layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+ displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+ return this;
+ }
+
+ public Transaction setDisplaySize(IBinder displayToken, int width, int height) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width and height must be positive");
+ }
+
+ nativeSetDisplaySize(mNativeObject, displayToken, width, height);
+ return this;
+ }
+
+ /** flag the transaction as an animation */
+ public Transaction setAnimationTransaction() {
+ nativeSetAnimationTransaction(mNativeObject);
+ return this;
+ }
+ }
}
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index 462dad3f..ebb2af45 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,1208 +16,115 @@
package android.view;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
+import com.android.layoutlib.bridge.MockView;
import android.content.Context;
-import android.content.res.CompatibilityInfo.Translator;
-import android.content.res.Configuration;
import android.graphics.Canvas;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.Log;
-
-import com.android.internal.view.SurfaceCallbackHelper;
-
-import java.util.ArrayList;
-import java.util.concurrent.locks.ReentrantLock;
/**
- * Provides a dedicated drawing surface embedded inside of a view hierarchy.
- * You can control the format of this surface and, if you like, its size; the
- * SurfaceView takes care of placing the surface at the correct location on the
- * screen
- *
- * <p>The surface is Z ordered so that it is behind the window holding its
- * SurfaceView; the SurfaceView punches a hole in its window to allow its
- * surface to be displayed. The view hierarchy will take care of correctly
- * compositing with the Surface any siblings of the SurfaceView that would
- * normally appear on top of it. This can be used to place overlays such as
- * buttons on top of the Surface, though note however that it can have an
- * impact on performance since a full alpha-blended composite will be performed
- * each time the Surface changes.
- *
- * <p> The transparent region that makes the surface visible is based on the
- * layout positions in the view hierarchy. If the post-layout transform
- * properties are used to draw a sibling view on top of the SurfaceView, the
- * view may not be properly composited with the surface.
+ * Mock version of the SurfaceView.
+ * Only non override public methods from the real SurfaceView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
*
- * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
- * which can be retrieved by calling {@link #getHolder}.
+ * TODO: generate automatically.
*
- * <p>The Surface will be created for you while the SurfaceView's window is
- * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
- * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
- * Surface is created and destroyed as the window is shown and hidden.
- *
- * <p>One of the purposes of this class is to provide a surface in which a
- * secondary thread can render into the screen. If you are going to use it
- * this way, you need to be aware of some threading semantics:
- *
- * <ul>
- * <li> All SurfaceView and
- * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
- * from the thread running the SurfaceView's window (typically the main thread
- * of the application). They thus need to correctly synchronize with any
- * state that is also touched by the drawing thread.
- * <li> You must ensure that the drawing thread only touches the underlying
- * Surface while it is valid -- between
- * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
- * and
- * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
- * </ul>
- *
- * <p class="note"><strong>Note:</strong> Starting in platform version
- * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
- * updated synchronously with other View rendering. This means that translating
- * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
- * artifacts may occur on previous versions of the platform when its window is
- * positioned asynchronously.</p>
*/
-public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
- private static final String TAG = "SurfaceView";
- private static final boolean DEBUG = false;
-
- final ArrayList<SurfaceHolder.Callback> mCallbacks
- = new ArrayList<SurfaceHolder.Callback>();
-
- final int[] mLocation = new int[2];
-
- final ReentrantLock mSurfaceLock = new ReentrantLock();
- final Surface mSurface = new Surface(); // Current surface in use
- boolean mDrawingStopped = true;
- // We use this to track if the application has produced a frame
- // in to the Surface. Up until that point, we should be careful not to punch
- // holes.
- boolean mDrawFinished = false;
-
- final Rect mScreenRect = new Rect();
- SurfaceSession mSurfaceSession;
-
- SurfaceControl mSurfaceControl;
- // In the case of format changes we switch out the surface in-place
- // we need to preserve the old one until the new one has drawn.
- SurfaceControl mDeferredDestroySurfaceControl;
- final Rect mTmpRect = new Rect();
- final Configuration mConfiguration = new Configuration();
-
- int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
-
- boolean mIsCreating = false;
- private volatile boolean mRtHandlingPositionUpdates = false;
-
- private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
- = new ViewTreeObserver.OnScrollChangedListener() {
- @Override
- public void onScrollChanged() {
- updateSurface();
- }
- };
-
- private final ViewTreeObserver.OnPreDrawListener mDrawListener =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- // reposition ourselves where the surface is
- mHaveFrame = getWidth() > 0 && getHeight() > 0;
- updateSurface();
- return true;
- }
- };
-
- boolean mRequestedVisible = false;
- boolean mWindowVisibility = false;
- boolean mLastWindowVisibility = false;
- boolean mViewVisibility = false;
- boolean mWindowStopped = false;
-
- int mRequestedWidth = -1;
- int mRequestedHeight = -1;
- /* Set SurfaceView's format to 565 by default to maintain backward
- * compatibility with applications assuming this format.
- */
- int mRequestedFormat = PixelFormat.RGB_565;
-
- boolean mHaveFrame = false;
- boolean mSurfaceCreated = false;
- long mLastLockTime = 0;
-
- boolean mVisible = false;
- int mWindowSpaceLeft = -1;
- int mWindowSpaceTop = -1;
- int mSurfaceWidth = -1;
- int mSurfaceHeight = -1;
- int mFormat = -1;
- final Rect mSurfaceFrame = new Rect();
- int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
- private Translator mTranslator;
-
- private boolean mGlobalListenersAdded;
- private boolean mAttachedToWindow;
-
- private int mSurfaceFlags = SurfaceControl.HIDDEN;
-
- private int mPendingReportDraws;
+public class SurfaceView extends MockView {
public SurfaceView(Context context) {
this(context, null);
}
public SurfaceView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs , 0);
}
- public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
}
public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mRenderNode.requestPositionUpdates(this);
-
- setWillNotDraw(true);
- }
-
- /**
- * Return the SurfaceHolder providing access and control over this
- * SurfaceView's underlying surface.
- *
- * @return SurfaceHolder The holder of the surface.
- */
- public SurfaceHolder getHolder() {
- return mSurfaceHolder;
- }
-
- private void updateRequestedVisibility() {
- mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
- }
-
- /** @hide */
- @Override
- public void windowStopped(boolean stopped) {
- mWindowStopped = stopped;
- updateRequestedVisibility();
- updateSurface();
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- getViewRootImpl().addWindowStoppedCallback(this);
- mWindowStopped = false;
-
- mViewVisibility = getVisibility() == VISIBLE;
- updateRequestedVisibility();
-
- mAttachedToWindow = true;
- mParent.requestTransparentRegion(SurfaceView.this);
- if (!mGlobalListenersAdded) {
- ViewTreeObserver observer = getViewTreeObserver();
- observer.addOnScrollChangedListener(mScrollChangedListener);
- observer.addOnPreDrawListener(mDrawListener);
- mGlobalListenersAdded = true;
- }
- }
-
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- mWindowVisibility = visibility == VISIBLE;
- updateRequestedVisibility();
- updateSurface();
- }
-
- @Override
- public void setVisibility(int visibility) {
- super.setVisibility(visibility);
- mViewVisibility = visibility == VISIBLE;
- boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
- if (newRequestedVisible != mRequestedVisible) {
- // our base class (View) invalidates the layout only when
- // we go from/to the GONE state. However, SurfaceView needs
- // to request a re-layout when the visibility changes at all.
- // This is needed because the transparent region is computed
- // as part of the layout phase, and it changes (obviously) when
- // the visibility changes.
- requestLayout();
- }
- mRequestedVisible = newRequestedVisible;
- updateSurface();
- }
-
- private void performDrawFinished() {
- if (mPendingReportDraws > 0) {
- mDrawFinished = true;
- if (mAttachedToWindow) {
- notifyDrawFinished();
- invalidate();
- }
- } else {
- Log.e(TAG, System.identityHashCode(this) + "finished drawing"
- + " but no pending report draw (extra call"
- + " to draw completion runnable?)");
- }
- }
-
- void notifyDrawFinished() {
- ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot != null) {
- viewRoot.pendingDrawFinished();
- }
- mPendingReportDraws--;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- ViewRootImpl viewRoot = getViewRootImpl();
- // It's possible to create a SurfaceView using the default constructor and never
- // attach it to a view hierarchy, this is a common use case when dealing with
- // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
- // the lifecycle. Instead of attaching it to a view, he/she can just pass
- // the SurfaceHolder forward, most live wallpapers do it.
- if (viewRoot != null) {
- viewRoot.removeWindowStoppedCallback(this);
- }
-
- mAttachedToWindow = false;
- if (mGlobalListenersAdded) {
- ViewTreeObserver observer = getViewTreeObserver();
- observer.removeOnScrollChangedListener(mScrollChangedListener);
- observer.removeOnPreDrawListener(mDrawListener);
- mGlobalListenersAdded = false;
- }
-
- while (mPendingReportDraws > 0) {
- notifyDrawFinished();
- }
-
- mRequestedVisible = false;
-
- updateSurface();
- if (mSurfaceControl != null) {
- mSurfaceControl.destroy();
- }
- mSurfaceControl = null;
-
- mHaveFrame = false;
-
- super.onDetachedFromWindow();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = mRequestedWidth >= 0
- ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
- : getDefaultSize(0, widthMeasureSpec);
- int height = mRequestedHeight >= 0
- ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
- : getDefaultSize(0, heightMeasureSpec);
- setMeasuredDimension(width, height);
- }
-
- /** @hide */
- @Override
- protected boolean setFrame(int left, int top, int right, int bottom) {
- boolean result = super.setFrame(left, top, right, bottom);
- updateSurface();
- return result;
- }
-
- @Override
public boolean gatherTransparentRegion(Region region) {
- if (isAboveParent() || !mDrawFinished) {
- return super.gatherTransparentRegion(region);
- }
-
- boolean opaque = true;
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
- // this view draws, remove it from the transparent region
- opaque = super.gatherTransparentRegion(region);
- } else if (region != null) {
- int w = getWidth();
- int h = getHeight();
- if (w>0 && h>0) {
- getLocationInWindow(mLocation);
- // otherwise, punch a hole in the whole hierarchy
- int l = mLocation[0];
- int t = mLocation[1];
- region.op(l, t, l+w, t+h, Region.Op.UNION);
- }
- }
- if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
- opaque = false;
- }
- return opaque;
+ return false;
}
- @Override
- public void draw(Canvas canvas) {
- if (mDrawFinished && !isAboveParent()) {
- // draw() is not called when SKIP_DRAW is set
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
- // punch a whole in the view-hierarchy below us
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- }
- }
- super.draw(canvas);
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (mDrawFinished && !isAboveParent()) {
- // draw() is not called when SKIP_DRAW is set
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- // punch a whole in the view-hierarchy below us
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- }
- }
- super.dispatchDraw(canvas);
- }
-
- /**
- * Control whether the surface view's surface is placed on top of another
- * regular surface view in the window (but still behind the window itself).
- * This is typically used to place overlays on top of an underlying media
- * surface view.
- *
- * <p>Note that this must be set before the surface view's containing
- * window is attached to the window manager.
- *
- * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
- */
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
- mSubLayer = isMediaOverlay
- ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
}
- /**
- * Control whether the surface view's surface is placed on top of its
- * window. Normally it is placed behind the window, to allow it to
- * (for the most part) appear to composite with the views in the
- * hierarchy. By setting this, you cause it to be placed above the
- * window. This means that none of the contents of the window this
- * SurfaceView is in will be visible on top of its surface.
- *
- * <p>Note that this must be set before the surface view's containing
- * window is attached to the window manager.
- *
- * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
- */
public void setZOrderOnTop(boolean onTop) {
- if (onTop) {
- mSubLayer = APPLICATION_PANEL_SUBLAYER;
- } else {
- mSubLayer = APPLICATION_MEDIA_SUBLAYER;
- }
}
- /**
- * Control whether the surface view's content should be treated as secure,
- * preventing it from appearing in screenshots or from being viewed on
- * non-secure displays.
- *
- * <p>Note that this must be set before the surface view's containing
- * window is attached to the window manager.
- *
- * <p>See {@link android.view.Display#FLAG_SECURE} for details.
- *
- * @param isSecure True if the surface view is secure.
- */
public void setSecure(boolean isSecure) {
- if (isSecure) {
- mSurfaceFlags |= SurfaceControl.SECURE;
- } else {
- mSurfaceFlags &= ~SurfaceControl.SECURE;
- }
- }
-
- private void updateOpaqueFlag() {
- if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
- mSurfaceFlags |= SurfaceControl.OPAQUE;
- } else {
- mSurfaceFlags &= ~SurfaceControl.OPAQUE;
- }
}
- private Rect getParentSurfaceInsets() {
- final ViewRootImpl root = getViewRootImpl();
- if (root == null) {
- return null;
- } else {
- return root.mWindowAttributes.surfaceInsets;
- }
- }
-
- /** @hide */
- protected void updateSurface() {
- if (!mHaveFrame) {
- return;
- }
- ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
- return;
- }
-
- mTranslator = viewRoot.mTranslator;
- if (mTranslator != null) {
- mSurface.setCompatibilityTranslator(mTranslator);
- }
-
- int myWidth = mRequestedWidth;
- if (myWidth <= 0) myWidth = getWidth();
- int myHeight = mRequestedHeight;
- if (myHeight <= 0) myHeight = getHeight();
-
- final boolean formatChanged = mFormat != mRequestedFormat;
- final boolean visibleChanged = mVisible != mRequestedVisible;
- final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
- && mRequestedVisible;
- final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
- final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
- boolean redrawNeeded = false;
-
- if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
- getLocationInWindow(mLocation);
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Changes: creating=" + creating
- + " format=" + formatChanged + " size=" + sizeChanged
- + " visible=" + visibleChanged
- + " left=" + (mWindowSpaceLeft != mLocation[0])
- + " top=" + (mWindowSpaceTop != mLocation[1]));
-
- try {
- final boolean visible = mVisible = mRequestedVisible;
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- mSurfaceWidth = myWidth;
- mSurfaceHeight = myHeight;
- mFormat = mRequestedFormat;
- mLastWindowVisibility = mWindowVisibility;
-
- mScreenRect.left = mWindowSpaceLeft;
- mScreenRect.top = mWindowSpaceTop;
- mScreenRect.right = mWindowSpaceLeft + getWidth();
- mScreenRect.bottom = mWindowSpaceTop + getHeight();
- if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(mScreenRect);
- }
-
- final Rect surfaceInsets = getParentSurfaceInsets();
- mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
- if (creating) {
- mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
- mDeferredDestroySurfaceControl = mSurfaceControl;
-
- updateOpaqueFlag();
- mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
- "SurfaceView - " + viewRoot.getTitle().toString(),
- mSurfaceWidth, mSurfaceHeight, mFormat,
- mSurfaceFlags);
- } else if (mSurfaceControl == null) {
- return;
- }
-
- boolean realSizeChanged = false;
-
- mSurfaceLock.lock();
- try {
- mDrawingStopped = !visible;
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Cur surface: " + mSurface);
-
- SurfaceControl.openTransaction();
- try {
- mSurfaceControl.setLayer(mSubLayer);
- if (mViewVisibility) {
- mSurfaceControl.show();
- } else {
- mSurfaceControl.hide();
- }
-
- // While creating the surface, we will set it's initial
- // geometry. Outside of that though, we should generally
- // leave it to the RenderThread.
- //
- // There is one more case when the buffer size changes we aren't yet
- // prepared to sync (as even following the transaction applying
- // we still need to latch a buffer).
- // b/28866173
- if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
- mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
- mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
- 0.0f, 0.0f,
- mScreenRect.height() / (float) mSurfaceHeight);
- }
- if (sizeChanged) {
- mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
- }
- } finally {
- SurfaceControl.closeTransaction();
- }
-
- if (sizeChanged || creating) {
- redrawNeeded = true;
- }
-
- mSurfaceFrame.left = 0;
- mSurfaceFrame.top = 0;
- if (mTranslator == null) {
- mSurfaceFrame.right = mSurfaceWidth;
- mSurfaceFrame.bottom = mSurfaceHeight;
- } else {
- float appInvertedScale = mTranslator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
- }
-
- final int surfaceWidth = mSurfaceFrame.right;
- final int surfaceHeight = mSurfaceFrame.bottom;
- realSizeChanged = mLastSurfaceWidth != surfaceWidth
- || mLastSurfaceHeight != surfaceHeight;
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- } finally {
- mSurfaceLock.unlock();
- }
-
- try {
- redrawNeeded |= visible && !mDrawFinished;
-
- SurfaceHolder.Callback callbacks[] = null;
-
- final boolean surfaceChanged = creating;
- if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
- mSurfaceCreated = false;
- if (mSurface.isValid()) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "visibleChanged -- surfaceDestroyed");
- callbacks = getSurfaceCallbacks();
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceDestroyed(mSurfaceHolder);
- }
- // Since Android N the same surface may be reused and given to us
- // again by the system server at a later point. However
- // as we didn't do this in previous releases, clients weren't
- // necessarily required to clean up properly in
- // surfaceDestroyed. This leads to problems for example when
- // clients don't destroy their EGL context, and try
- // and create a new one on the same surface following reuse.
- // Since there is no valid use of the surface in-between
- // surfaceDestroyed and surfaceCreated, we force a disconnect,
- // so the next connect will always work if we end up reusing
- // the surface.
- if (mSurface.isValid()) {
- mSurface.forceScopedDisconnect();
- }
- }
- }
-
- if (creating) {
- mSurface.copyFrom(mSurfaceControl);
- }
-
- if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
- < Build.VERSION_CODES.O) {
- // Some legacy applications use the underlying native {@link Surface} object
- // as a key to whether anything has changed. In these cases, updates to the
- // existing {@link Surface} will be ignored when the size changes.
- // Therefore, we must explicitly recreate the {@link Surface} in these
- // cases.
- mSurface.createFrom(mSurfaceControl);
- }
-
- if (visible && mSurface.isValid()) {
- if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
- mSurfaceCreated = true;
- mIsCreating = true;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "visibleChanged -- surfaceCreated");
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceCreated(mSurfaceHolder);
- }
- }
- if (creating || formatChanged || sizeChanged
- || visibleChanged || realSizeChanged) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "surfaceChanged -- format=" + mFormat
- + " w=" + myWidth + " h=" + myHeight);
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
- }
- }
- if (redrawNeeded) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "surfaceRedrawNeeded");
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
-
- mPendingReportDraws++;
- viewRoot.drawPending();
- SurfaceCallbackHelper sch =
- new SurfaceCallbackHelper(this::onDrawFinished);
- sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
- }
- }
- } finally {
- mIsCreating = false;
- if (mSurfaceControl != null && !mSurfaceCreated) {
- mSurface.release();
- // If we are not in the stopped state, then the destruction of the Surface
- // represents a visual change we need to display, and we should go ahead
- // and destroy the SurfaceControl. However if we are in the stopped state,
- // we can just leave the Surface around so it can be a part of animations,
- // and we let the life-time be tied to the parent surface.
- if (!mWindowStopped) {
- mSurfaceControl.destroy();
- mSurfaceControl = null;
- }
- }
- }
- } catch (Exception ex) {
- Log.e(TAG, "Exception configuring surface", ex);
- }
- if (DEBUG) Log.v(
- TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
- + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
- + ", frame=" + mSurfaceFrame);
- } else {
- // Calculate the window position in case RT loses the window
- // and we need to fallback to a UI-thread driven position update
- getLocationInSurface(mLocation);
- final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
- || mWindowSpaceTop != mLocation[1];
- final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
- || getHeight() != mScreenRect.height();
- if (positionChanged || layoutSizeChanged) { // Only the position has changed
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
- // in view local space.
- mLocation[0] = getWidth();
- mLocation[1] = getHeight();
-
- mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
- mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
-
- if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(mScreenRect);
- }
-
- if (mSurfaceControl == null) {
- return;
- }
-
- if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
- try {
- if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
- "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mScreenRect.left, mScreenRect.top,
- mScreenRect.right, mScreenRect.bottom));
- setParentSpaceRectangle(mScreenRect, -1);
- } catch (Exception ex) {
- Log.e(TAG, "Exception configuring surface", ex);
- }
- }
- }
- }
- }
-
- private void onDrawFinished() {
- if (DEBUG) {
- Log.i(TAG, System.identityHashCode(this) + " "
- + "finishedDrawing");
- }
-
- if (mDeferredDestroySurfaceControl != null) {
- mDeferredDestroySurfaceControl.destroy();
- mDeferredDestroySurfaceControl = null;
- }
-
- runOnUiThread(() -> {
- performDrawFinished();
- });
- }
-
- private void setParentSpaceRectangle(Rect position, long frameNumber) {
- ViewRootImpl viewRoot = getViewRootImpl();
-
- SurfaceControl.openTransaction();
- try {
- if (frameNumber > 0) {
- mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
- }
- mSurfaceControl.setPosition(position.left, position.top);
- mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
- 0.0f, 0.0f,
- position.height() / (float) mSurfaceHeight);
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
- private Rect mRTLastReportedPosition = new Rect();
-
- /**
- * Called by native by a Rendering Worker thread to update the window position
- * @hide
- */
- public final void updateSurfacePosition_renderWorker(long frameNumber,
- int left, int top, int right, int bottom) {
- if (mSurfaceControl == null) {
- return;
- }
-
- // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
- // its 2nd frame if RenderThread is running slowly could potentially see
- // this as false, enter the branch, get pre-empted, then this comes along
- // and reports a new position, then the UI thread resumes and reports
- // its position. This could therefore be de-sync'd in that interval, but
- // the synchronization would violate the rule that RT must never block
- // on the UI thread which would open up potential deadlocks. The risk of
- // a single-frame desync is therefore preferable for now.
- mRtHandlingPositionUpdates = true;
- if (mRTLastReportedPosition.left == left
- && mRTLastReportedPosition.top == top
- && mRTLastReportedPosition.right == right
- && mRTLastReportedPosition.bottom == bottom) {
- return;
- }
- try {
- if (DEBUG) {
- Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
- "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- frameNumber, left, top, right, bottom));
- }
- mRTLastReportedPosition.set(left, top, right, bottom);
- setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
- // Now overwrite mRTLastReportedPosition with our values
- } catch (Exception ex) {
- Log.e(TAG, "Exception from repositionChild", ex);
- }
- }
-
- /**
- * Called by native on RenderThread to notify that the view is no longer in the
- * draw tree. UI thread is blocked at this point.
- * @hide
- */
- public final void surfacePositionLost_uiRtSync(long frameNumber) {
- if (DEBUG) {
- Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
- System.identityHashCode(this), frameNumber));
- }
- mRTLastReportedPosition.setEmpty();
-
- if (mSurfaceControl == null) {
- return;
- }
- if (mRtHandlingPositionUpdates) {
- mRtHandlingPositionUpdates = false;
- // This callback will happen while the UI thread is blocked, so we can
- // safely access other member variables at this time.
- // So do what the UI thread would have done if RT wasn't handling position
- // updates.
- if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
- try {
- if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
- "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mScreenRect.left, mScreenRect.top,
- mScreenRect.right, mScreenRect.bottom));
- setParentSpaceRectangle(mScreenRect, frameNumber);
- } catch (Exception ex) {
- Log.e(TAG, "Exception configuring surface", ex);
- }
- }
- }
- }
-
- private SurfaceHolder.Callback[] getSurfaceCallbacks() {
- SurfaceHolder.Callback callbacks[];
- synchronized (mCallbacks) {
- callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
- mCallbacks.toArray(callbacks);
- }
- return callbacks;
- }
-
- /**
- * This method still exists only for compatibility reasons because some applications have relied
- * on this method via reflection. See Issue 36345857 for details.
- *
- * @deprecated No platform code is using this method anymore.
- * @hide
- */
- @Deprecated
- public void setWindowType(int type) {
- if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
- throw new UnsupportedOperationException(
- "SurfaceView#setWindowType() has never been a public API.");
- }
-
- if (type == TYPE_APPLICATION_PANEL) {
- Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
- + "just to make the SurfaceView to be placed on top of its window, you must "
- + "call setZOrderOnTop(true) instead.", new Throwable());
- setZOrderOnTop(true);
- return;
- }
- Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
- + "type=" + type, new Throwable());
- }
-
- private void runOnUiThread(Runnable runnable) {
- Handler handler = getHandler();
- if (handler != null && handler.getLooper() != Looper.myLooper()) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
-
- /**
- * Check to see if the surface has fixed size dimensions or if the surface's
- * dimensions are dimensions are dependent on its current layout.
- *
- * @return true if the surface has dimensions that are fixed in size
- * @hide
- */
- public boolean isFixedSize() {
- return (mRequestedWidth != -1 || mRequestedHeight != -1);
- }
-
- private boolean isAboveParent() {
- return mSubLayer >= 0;
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
}
- private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
- private static final String LOG_TAG = "SurfaceHolder";
+ private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
@Override
public boolean isCreating() {
- return mIsCreating;
+ return false;
}
@Override
public void addCallback(Callback callback) {
- synchronized (mCallbacks) {
- // This is a linear search, but in practice we'll
- // have only a couple callbacks, so it doesn't matter.
- if (mCallbacks.contains(callback) == false) {
- mCallbacks.add(callback);
- }
- }
}
@Override
public void removeCallback(Callback callback) {
- synchronized (mCallbacks) {
- mCallbacks.remove(callback);
- }
}
@Override
public void setFixedSize(int width, int height) {
- if (mRequestedWidth != width || mRequestedHeight != height) {
- mRequestedWidth = width;
- mRequestedHeight = height;
- requestLayout();
- }
}
@Override
public void setSizeFromLayout() {
- if (mRequestedWidth != -1 || mRequestedHeight != -1) {
- mRequestedWidth = mRequestedHeight = -1;
- requestLayout();
- }
}
@Override
public void setFormat(int format) {
- // for backward compatibility reason, OPAQUE always
- // means 565 for SurfaceView
- if (format == PixelFormat.OPAQUE)
- format = PixelFormat.RGB_565;
-
- mRequestedFormat = format;
- if (mSurfaceControl != null) {
- updateSurface();
- }
}
- /**
- * @deprecated setType is now ignored.
- */
@Override
- @Deprecated
- public void setType(int type) { }
+ public void setType(int type) {
+ }
@Override
public void setKeepScreenOn(boolean screenOn) {
- runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
}
- /**
- * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
- *
- * After drawing into the provided {@link Canvas}, the caller must
- * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
- *
- * The caller must redraw the entire surface.
- * @return A canvas for drawing into the surface.
- */
@Override
public Canvas lockCanvas() {
- return internalLockCanvas(null, false);
- }
-
- /**
- * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
- *
- * After drawing into the provided {@link Canvas}, the caller must
- * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
- *
- * @param inOutDirty A rectangle that represents the dirty region that the caller wants
- * to redraw. This function may choose to expand the dirty rectangle if for example
- * the surface has been resized or if the previous contents of the surface were
- * not available. The caller must redraw the entire dirty region as represented
- * by the contents of the inOutDirty rectangle upon return from this function.
- * The caller may also pass <code>null</code> instead, in the case where the
- * entire surface should be redrawn.
- * @return A canvas for drawing into the surface.
- */
- @Override
- public Canvas lockCanvas(Rect inOutDirty) {
- return internalLockCanvas(inOutDirty, false);
+ return null;
}
@Override
- public Canvas lockHardwareCanvas() {
- return internalLockCanvas(null, true);
- }
-
- private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
- mSurfaceLock.lock();
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
- + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
-
- Canvas c = null;
- if (!mDrawingStopped && mSurfaceControl != null) {
- try {
- if (hardware) {
- c = mSurface.lockHardwareCanvas();
- } else {
- c = mSurface.lockCanvas(dirty);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "Exception locking surface", e);
- }
- }
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
- if (c != null) {
- mLastLockTime = SystemClock.uptimeMillis();
- return c;
- }
-
- // If the Surface is not ready to be drawn, then return null,
- // but throttle calls to this function so it isn't called more
- // than every 100ms.
- long now = SystemClock.uptimeMillis();
- long nextTime = mLastLockTime + 100;
- if (nextTime > now) {
- try {
- Thread.sleep(nextTime-now);
- } catch (InterruptedException e) {
- }
- now = SystemClock.uptimeMillis();
- }
- mLastLockTime = now;
- mSurfaceLock.unlock();
-
+ public Canvas lockCanvas(Rect dirty) {
return null;
}
- /**
- * Posts the new contents of the {@link Canvas} to the surface and
- * releases the {@link Canvas}.
- *
- * @param canvas The canvas previously obtained from {@link #lockCanvas}.
- */
@Override
public void unlockCanvasAndPost(Canvas canvas) {
- mSurface.unlockCanvasAndPost(canvas);
- mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
- return mSurface;
+ return null;
}
@Override
public Rect getSurfaceFrame() {
- return mSurfaceFrame;
+ return null;
}
};
-
- class SurfaceControlWithBackground extends SurfaceControl {
- private SurfaceControl mBackgroundControl;
- private boolean mOpaque = true;
- public boolean mVisible = false;
-
- public SurfaceControlWithBackground(SurfaceSession s,
- String name, int w, int h, int format, int flags)
- throws Exception {
- super(s, name, w, h, format, flags);
- mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
- PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
- mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- mBackgroundControl.setAlpha(alpha);
- }
-
- @Override
- public void setLayer(int zorder) {
- super.setLayer(zorder);
- // -3 is below all other child layers as SurfaceView never goes below -2
- mBackgroundControl.setLayer(-3);
- }
-
- @Override
- public void setPosition(float x, float y) {
- super.setPosition(x, y);
- mBackgroundControl.setPosition(x, y);
- }
-
- @Override
- public void setSize(int w, int h) {
- super.setSize(w, h);
- mBackgroundControl.setSize(w, h);
- }
-
- @Override
- public void setWindowCrop(Rect crop) {
- super.setWindowCrop(crop);
- mBackgroundControl.setWindowCrop(crop);
- }
-
- @Override
- public void setFinalCrop(Rect crop) {
- super.setFinalCrop(crop);
- mBackgroundControl.setFinalCrop(crop);
- }
-
- @Override
- public void setLayerStack(int layerStack) {
- super.setLayerStack(layerStack);
- mBackgroundControl.setLayerStack(layerStack);
- }
-
- @Override
- public void setOpaque(boolean isOpaque) {
- super.setOpaque(isOpaque);
- mOpaque = isOpaque;
- updateBackgroundVisibility();
- }
-
- @Override
- public void setSecure(boolean isSecure) {
- super.setSecure(isSecure);
- }
-
- @Override
- public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- super.setMatrix(dsdx, dtdx, dsdy, dtdy);
- mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
- }
-
- @Override
- public void hide() {
- super.hide();
- mVisible = false;
- updateBackgroundVisibility();
- }
-
- @Override
- public void show() {
- super.show();
- mVisible = true;
- updateBackgroundVisibility();
- }
-
- @Override
- public void destroy() {
- super.destroy();
- mBackgroundControl.destroy();
- }
-
- @Override
- public void release() {
- super.release();
- mBackgroundControl.release();
- }
-
- @Override
- public void setTransparentRegionHint(Region region) {
- super.setTransparentRegionHint(region);
- mBackgroundControl.setTransparentRegionHint(region);
- }
-
- @Override
- public void deferTransactionUntil(IBinder handle, long frame) {
- super.deferTransactionUntil(handle, frame);
- mBackgroundControl.deferTransactionUntil(handle, frame);
- }
-
- @Override
- public void deferTransactionUntil(Surface barrier, long frame) {
- super.deferTransactionUntil(barrier, frame);
- mBackgroundControl.deferTransactionUntil(barrier, frame);
- }
-
- void updateBackgroundVisibility() {
- if (mOpaque && mVisible) {
- mBackgroundControl.show();
- } else {
- mBackgroundControl.hide();
- }
- }
- }
}
+
diff --git a/android/view/View.java b/android/view/View.java
index b6be2961..c043dcac 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -1448,17 +1448,59 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* <p>Enables low quality mode for the drawing cache.</p>
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000;
/**
* <p>Enables high quality mode for the drawing cache.</p>
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000;
/**
* <p>Enables automatic quality mode for the drawing cache.</p>
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000;
private static final int[] DRAWING_CACHE_QUALITY_FLAGS = {
@@ -2300,9 +2342,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int PFLAG_HOVERED = 0x10000000;
/**
- * no longer needed, should be reused
+ * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
*/
- private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000;
+ private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
/** {@hide} */
static final int PFLAG_ACTIVATED = 0x40000000;
@@ -6355,6 +6397,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return null;
}
+ /** @hide */
+ public void setNotifyAutofillManagerOnClick(boolean notify) {
+ if (notify) {
+ mPrivateFlags |= PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+ } else {
+ mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+ }
+ }
+
+ private void notifyAutofillManagerOnClick() {
+ if ((mPrivateFlags & PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK) != 0) {
+ try {
+ getAutofillManager().notifyViewClicked(this);
+ } finally {
+ // Set it to already called so it's not called twice when called by
+ // performClickInternal()
+ mPrivateFlags |= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+ }
+ }
+ }
+
+ /**
+ * Entry point for {@link #performClick()} - other methods on View should call it instead of
+ * {@code performClick()} directly to make sure the autofill manager is notified when
+ * necessary (as subclasses could extend {@code performClick()} without calling the parent's
+ * method).
+ */
+ private boolean performClickInternal() {
+ // Must notify autofill manager before performing the click actions to avoid scenarios where
+ // the app has a click listener that changes the state of views the autofill service might
+ // be interested on.
+ notifyAutofillManagerOnClick();
+
+ return performClick();
+ }
+
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
@@ -6363,7 +6441,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
+ // NOTE: other methods on View should not call this method directly, but performClickInternal()
+ // instead, to guarantee that the autofill manager is notified when necessary (as subclasses
+ // could extend this method without calling super.performClick()).
public boolean performClick() {
+ // We still need to call this method to handle the cases where performClick() was called
+ // externally, instead of through performClickInternal()
+ notifyAutofillManagerOnClick();
+
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
@@ -8907,7 +8992,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #isDrawingCacheEnabled()
*
* @attr ref android.R.styleable#View_drawingCacheQuality
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@DrawingCacheQuality
public int getDrawingCacheQuality() {
return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
@@ -8925,7 +9024,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #isDrawingCacheEnabled()
*
* @attr ref android.R.styleable#View_drawingCacheQuality
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setDrawingCacheQuality(@DrawingCacheQuality int quality) {
setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
}
@@ -11433,7 +11546,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
if (isClickable()) {
- performClick();
+ performClickInternal();
return true;
}
} break;
@@ -12545,7 +12658,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!event.isCanceled()) {
- return performClick();
+ return performClickInternal();
}
}
}
@@ -13117,7 +13230,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
- performClick();
+ performClickInternal();
}
}
}
@@ -18103,7 +18216,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #getDrawingCache()
* @see #buildDrawingCache()
* @see #setLayerType(int, android.graphics.Paint)
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setDrawingCacheEnabled(boolean enabled) {
mCachingFailed = false;
setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
@@ -18116,7 +18243,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #setDrawingCacheEnabled(boolean)
* @see #getDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@ViewDebug.ExportedProperty(category = "drawing")
public boolean isDrawingCacheEnabled() {
return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
@@ -18130,10 +18271,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@SuppressWarnings({"UnusedDeclaration"})
public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
- Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) +
- ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" +
- (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) +
- ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
+ Log.d(VIEW_LOG_TAG, indent + this + " DIRTY("
+ + (mPrivateFlags & View.PFLAG_DIRTY_MASK)
+ + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID("
+ + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID)
+ + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
if (clear) {
mPrivateFlags &= clearMask;
}
@@ -18257,7 +18399,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return A non-scaled bitmap representing this view or null if cache is disabled.
*
* @see #getDrawingCache(boolean)
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public Bitmap getDrawingCache() {
return getDrawingCache(false);
}
@@ -18288,7 +18444,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #isDrawingCacheEnabled()
* @see #buildDrawingCache(boolean)
* @see #destroyDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public Bitmap getDrawingCache(boolean autoScale) {
if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
return null;
@@ -18308,7 +18478,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
* @see #getDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void destroyDrawingCache() {
if (mDrawingCache != null) {
mDrawingCache.recycle();
@@ -18330,7 +18514,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
* @see #getDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setDrawingCacheBackgroundColor(@ColorInt int color) {
if (color != mDrawingCacheBackgroundColor) {
mDrawingCacheBackgroundColor = color;
@@ -18342,7 +18540,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setDrawingCacheBackgroundColor(int)
*
* @return The background color to used for the drawing cache's bitmap
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@ColorInt
public int getDrawingCacheBackgroundColor() {
return mDrawingCacheBackgroundColor;
@@ -18352,7 +18564,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
*
* @see #buildDrawingCache(boolean)
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void buildDrawingCache() {
buildDrawingCache(false);
}
@@ -18379,7 +18605,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #getDrawingCache()
* @see #destroyDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void buildDrawingCache(boolean autoScale) {
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
@@ -19812,7 +20052,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean changed = false;
if (DBG) {
- Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
@@ -24858,7 +25098,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private final class PerformClick implements Runnable {
@Override
public void run() {
- performClick();
+ performClickInternal();
}
}
diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java
index 574137b3..45008627 100644
--- a/android/view/ViewConfiguration.java
+++ b/android/view/ViewConfiguration.java
@@ -84,12 +84,17 @@ public class ViewConfiguration {
/**
* Defines the duration in milliseconds a user needs to hold down the
- * appropriate button to bring up the accessibility shortcut (first time) or enable it
- * (once shortcut is configured).
+ * appropriate button to bring up the accessibility shortcut for the first time
*/
private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
/**
+ * 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;
+
+ /**
* Defines the duration in milliseconds we will wait to see if a touch event
* is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
@@ -851,6 +856,15 @@ public class ViewConfiguration {
}
/**
+ * @return The amount of time a user needs to press the relevant keys to activate the
+ * accessibility shortcut after it's confirmed that accessibility shortcut is used.
+ * @hide
+ */
+ public long getAccessibilityShortcutKeyTimeoutAfterConfirmation() {
+ return A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION;
+ }
+
+ /**
* The amount of friction applied to scrolls and flings.
*
* @return A scalar dimensionless value representing the coefficient of
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
index 3426485e..afa94131 100644
--- a/android/view/ViewDebug.java
+++ b/android/view/ViewDebug.java
@@ -528,84 +528,23 @@ public class ViewDebug {
/** @hide */
public static void profileViewAndChildren(final View view, BufferedWriter out)
throws IOException {
- profileViewAndChildren(view, out, true);
+ RenderNode node = RenderNode.create("ViewDebug", null);
+ profileViewAndChildren(view, node, out, true);
+ node.destroy();
}
- private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
- throws IOException {
-
+ private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
+ boolean root) throws IOException {
long durationMeasure =
(root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
- ? profileViewOperation(view, new ViewOperation<Void>() {
- public Void[] pre() {
- forceLayout(view);
- return null;
- }
-
- private void forceLayout(View view) {
- view.forceLayout();
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
- final int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- forceLayout(group.getChildAt(i));
- }
- }
- }
-
- public void run(Void... data) {
- view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
- }
-
- public void post(Void... data) {
- }
- })
- : 0;
+ ? profileViewMeasure(view) : 0;
long durationLayout =
(root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
- ? profileViewOperation(view, new ViewOperation<Void>() {
- public Void[] pre() {
- return null;
- }
-
- public void run(Void... data) {
- view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
- }
-
- public void post(Void... data) {
- }
- }) : 0;
+ ? profileViewLayout(view) : 0;
long durationDraw =
(root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
- ? profileViewOperation(view, new ViewOperation<Object>() {
- public Object[] pre() {
- final DisplayMetrics metrics =
- (view != null && view.getResources() != null) ?
- view.getResources().getDisplayMetrics() : null;
- final Bitmap bitmap = metrics != null ?
- Bitmap.createBitmap(metrics, metrics.widthPixels,
- metrics.heightPixels, Bitmap.Config.RGB_565) : null;
- final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
- return new Object[] {
- bitmap, canvas
- };
- }
+ ? profileViewDraw(view, node) : 0;
- public void run(Object... data) {
- if (data[1] != null) {
- view.draw((Canvas) data[1]);
- }
- }
-
- public void post(Object... data) {
- if (data[1] != null) {
- ((Canvas) data[1]).setBitmap(null);
- }
- if (data[0] != null) {
- ((Bitmap) data[0]).recycle();
- }
- }
- }) : 0;
out.write(String.valueOf(durationMeasure));
out.write(' ');
out.write(String.valueOf(durationLayout));
@@ -616,34 +555,86 @@ public class ViewDebug {
ViewGroup group = (ViewGroup) view;
final int count = group.getChildCount();
for (int i = 0; i < count; i++) {
- profileViewAndChildren(group.getChildAt(i), out, false);
+ profileViewAndChildren(group.getChildAt(i), node, out, false);
+ }
+ }
+ }
+
+ private static long profileViewMeasure(final View view) {
+ return profileViewOperation(view, new ViewOperation() {
+ @Override
+ public void pre() {
+ forceLayout(view);
+ }
+
+ private void forceLayout(View view) {
+ view.forceLayout();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ forceLayout(group.getChildAt(i));
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+ }
+ });
+ }
+
+ private static long profileViewLayout(View view) {
+ return profileViewOperation(view,
+ () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
+ }
+
+ private static long profileViewDraw(View view, RenderNode node) {
+ DisplayMetrics dm = view.getResources().getDisplayMetrics();
+ if (dm == null) {
+ return 0;
+ }
+
+ if (view.isHardwareAccelerated()) {
+ DisplayListCanvas canvas = node.start(dm.widthPixels, dm.heightPixels);
+ try {
+ return profileViewOperation(view, () -> view.draw(canvas));
+ } finally {
+ node.end(canvas);
+ }
+ } else {
+ Bitmap bitmap = Bitmap.createBitmap(
+ dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
+ Canvas canvas = new Canvas(bitmap);
+ try {
+ return profileViewOperation(view, () -> view.draw(canvas));
+ } finally {
+ canvas.setBitmap(null);
+ bitmap.recycle();
}
}
}
- interface ViewOperation<T> {
- T[] pre();
- void run(T... data);
- void post(T... data);
+ interface ViewOperation {
+ default void pre() {}
+
+ void run();
}
- private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
+ private static long profileViewOperation(View view, final ViewOperation operation) {
final CountDownLatch latch = new CountDownLatch(1);
final long[] duration = new long[1];
- view.post(new Runnable() {
- public void run() {
- try {
- T[] data = operation.pre();
- long start = Debug.threadCpuTimeNanos();
- //noinspection unchecked
- operation.run(data);
- duration[0] = Debug.threadCpuTimeNanos() - start;
- //noinspection unchecked
- operation.post(data);
- } finally {
- latch.countDown();
- }
+ view.post(() -> {
+ try {
+ operation.pre();
+ long start = Debug.threadCpuTimeNanos();
+ //noinspection unchecked
+ operation.run();
+ duration[0] = Debug.threadCpuTimeNanos() - start;
+ } finally {
+ latch.countDown();
}
});
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
index b2e5a163..929beaea 100644
--- a/android/view/ViewGroup.java
+++ b/android/view/ViewGroup.java
@@ -421,22 +421,78 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Used to indicate that no drawing cache should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_NO_CACHE = 0x0;
/**
* Used to indicate that the animation drawing cache should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
/**
* Used to indicate that the scrolling drawing cache should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
/**
* Used to indicate that all drawing caches should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_ALL_CACHES = 0x3;
// Layout Modes
@@ -3769,7 +3825,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Enables or disables the drawing cache for each child of this view group.
*
* @param enabled true to enable the cache, false to dispose of it
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
protected void setChildrenDrawingCacheEnabled(boolean enabled) {
if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) {
final View[] children = mChildren;
@@ -6331,7 +6401,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @return one or a combination of {@link #PERSISTENT_NO_CACHE},
* {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
* and {@link #PERSISTENT_ALL_CACHES}
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@ViewDebug.ExportedProperty(category = "drawing", mapping = {
@ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"),
@ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
@@ -6352,7 +6436,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
* {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
* and {@link #PERSISTENT_ALL_CACHES}
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setPersistentDrawingCache(int drawingCacheToKeep) {
mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
}
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 71106ada..99438d87 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -72,6 +72,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MergedConfiguration;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.view.Surface.OutOfResourcesException;
@@ -1668,8 +1669,6 @@ public final class ViewRootImpl implements ViewParent,
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
- //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
-
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
@@ -2827,7 +2826,7 @@ public final class ViewRootImpl implements ViewParent,
try {
mWindowDrawCountDown.await();
} catch (InterruptedException e) {
- Log.e(mTag, "Window redraw count down interruped!");
+ Log.e(mTag, "Window redraw count down interrupted!");
}
mWindowDrawCountDown = null;
}
@@ -2897,8 +2896,6 @@ public final class ViewRootImpl implements ViewParent,
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
- int resizeAlpha = 0;
-
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
@@ -3469,6 +3466,7 @@ public final class ViewRootImpl implements ViewParent,
}
void dispatchDetachedFromWindow() {
+ mFirstInputStage.onDetachedFromWindow();
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
@@ -3731,266 +3729,273 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_INVALIDATE:
- ((View) msg.obj).invalidate();
- break;
- case MSG_INVALIDATE_RECT:
- final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
- info.target.invalidate(info.left, info.top, info.right, info.bottom);
- info.recycle();
- break;
- case MSG_PROCESS_INPUT_EVENTS:
- mProcessInputEventsScheduled = false;
- doProcessInputEvents();
- break;
- case MSG_DISPATCH_APP_VISIBILITY:
- handleAppVisibility(msg.arg1 != 0);
- break;
- case MSG_DISPATCH_GET_NEW_SURFACE:
- handleGetNewSurface();
- break;
- case MSG_RESIZED: {
- // Recycled in the fall through...
- SomeArgs args = (SomeArgs) msg.obj;
- if (mWinFrame.equals(args.arg1)
- && mPendingOverscanInsets.equals(args.arg5)
- && mPendingContentInsets.equals(args.arg2)
- && mPendingStableInsets.equals(args.arg6)
- && mPendingVisibleInsets.equals(args.arg3)
- && mPendingOutsets.equals(args.arg7)
- && mPendingBackDropFrame.equals(args.arg8)
- && args.arg4 == null
- && args.argi1 == 0
- && mDisplay.getDisplayId() == args.argi3) {
+ case MSG_INVALIDATE:
+ ((View) msg.obj).invalidate();
break;
- }
- } // fall through...
- case MSG_RESIZED_REPORT:
- if (mAdded) {
+ case MSG_INVALIDATE_RECT:
+ final View.AttachInfo.InvalidateInfo info =
+ (View.AttachInfo.InvalidateInfo) msg.obj;
+ info.target.invalidate(info.left, info.top, info.right, info.bottom);
+ info.recycle();
+ break;
+ case MSG_PROCESS_INPUT_EVENTS:
+ mProcessInputEventsScheduled = false;
+ doProcessInputEvents();
+ break;
+ case MSG_DISPATCH_APP_VISIBILITY:
+ handleAppVisibility(msg.arg1 != 0);
+ break;
+ case MSG_DISPATCH_GET_NEW_SURFACE:
+ handleGetNewSurface();
+ break;
+ case MSG_RESIZED: {
+ // Recycled in the fall through...
SomeArgs args = (SomeArgs) msg.obj;
-
- final int displayId = args.argi3;
- MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
- final boolean displayChanged = mDisplay.getDisplayId() != displayId;
-
- if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
- // If configuration changed - notify about that and, maybe, about move to
- // display.
- performConfigurationChange(mergedConfiguration, false /* force */,
- displayChanged ? displayId : INVALID_DISPLAY /* same display */);
- } else if (displayChanged) {
- // Moved to display without config change - report last applied one.
- onMovedToDisplay(displayId, mLastConfigurationFromResources);
+ if (mWinFrame.equals(args.arg1)
+ && mPendingOverscanInsets.equals(args.arg5)
+ && mPendingContentInsets.equals(args.arg2)
+ && mPendingStableInsets.equals(args.arg6)
+ && mPendingVisibleInsets.equals(args.arg3)
+ && mPendingOutsets.equals(args.arg7)
+ && mPendingBackDropFrame.equals(args.arg8)
+ && args.arg4 == null
+ && args.argi1 == 0
+ && mDisplay.getDisplayId() == args.argi3) {
+ break;
}
+ } // fall through...
+ case MSG_RESIZED_REPORT:
+ if (mAdded) {
+ SomeArgs args = (SomeArgs) msg.obj;
+
+ final int displayId = args.argi3;
+ MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
+ final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+
+ if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
+ // If configuration changed - notify about that and, maybe,
+ // about move to display.
+ performConfigurationChange(mergedConfiguration, false /* force */,
+ displayChanged
+ ? displayId : INVALID_DISPLAY /* same display */);
+ } else if (displayChanged) {
+ // Moved to display without config change - report last applied one.
+ onMovedToDisplay(displayId, mLastConfigurationFromResources);
+ }
- final boolean framesChanged = !mWinFrame.equals(args.arg1)
- || !mPendingOverscanInsets.equals(args.arg5)
- || !mPendingContentInsets.equals(args.arg2)
- || !mPendingStableInsets.equals(args.arg6)
- || !mPendingVisibleInsets.equals(args.arg3)
- || !mPendingOutsets.equals(args.arg7);
-
- mWinFrame.set((Rect) args.arg1);
- mPendingOverscanInsets.set((Rect) args.arg5);
- mPendingContentInsets.set((Rect) args.arg2);
- mPendingStableInsets.set((Rect) args.arg6);
- mPendingVisibleInsets.set((Rect) args.arg3);
- mPendingOutsets.set((Rect) args.arg7);
- mPendingBackDropFrame.set((Rect) args.arg8);
- mForceNextWindowRelayout = args.argi1 != 0;
- mPendingAlwaysConsumeNavBar = args.argi2 != 0;
-
- args.recycle();
+ final boolean framesChanged = !mWinFrame.equals(args.arg1)
+ || !mPendingOverscanInsets.equals(args.arg5)
+ || !mPendingContentInsets.equals(args.arg2)
+ || !mPendingStableInsets.equals(args.arg6)
+ || !mPendingVisibleInsets.equals(args.arg3)
+ || !mPendingOutsets.equals(args.arg7);
+
+ mWinFrame.set((Rect) args.arg1);
+ mPendingOverscanInsets.set((Rect) args.arg5);
+ mPendingContentInsets.set((Rect) args.arg2);
+ mPendingStableInsets.set((Rect) args.arg6);
+ mPendingVisibleInsets.set((Rect) args.arg3);
+ mPendingOutsets.set((Rect) args.arg7);
+ mPendingBackDropFrame.set((Rect) args.arg8);
+ mForceNextWindowRelayout = args.argi1 != 0;
+ mPendingAlwaysConsumeNavBar = args.argi2 != 0;
+
+ args.recycle();
+
+ if (msg.what == MSG_RESIZED_REPORT) {
+ reportNextDraw();
+ }
- if (msg.what == MSG_RESIZED_REPORT) {
- reportNextDraw();
+ if (mView != null && framesChanged) {
+ forceLayout(mView);
+ }
+ requestLayout();
}
-
- if (mView != null && framesChanged) {
- forceLayout(mView);
+ break;
+ case MSG_WINDOW_MOVED:
+ if (mAdded) {
+ final int w = mWinFrame.width();
+ final int h = mWinFrame.height();
+ final int l = msg.arg1;
+ final int t = msg.arg2;
+ mWinFrame.left = l;
+ mWinFrame.right = l + w;
+ mWinFrame.top = t;
+ mWinFrame.bottom = t + h;
+
+ mPendingBackDropFrame.set(mWinFrame);
+ maybeHandleWindowMove(mWinFrame);
}
- requestLayout();
- }
- break;
- case MSG_WINDOW_MOVED:
- if (mAdded) {
- final int w = mWinFrame.width();
- final int h = mWinFrame.height();
- final int l = msg.arg1;
- final int t = msg.arg2;
- mWinFrame.left = l;
- mWinFrame.right = l + w;
- mWinFrame.top = t;
- mWinFrame.bottom = t + h;
-
- mPendingBackDropFrame.set(mWinFrame);
- maybeHandleWindowMove(mWinFrame);
- }
- break;
- case MSG_WINDOW_FOCUS_CHANGED: {
- if (mAdded) {
- boolean hasWindowFocus = msg.arg1 != 0;
- mAttachInfo.mHasWindowFocus = hasWindowFocus;
-
- profileRendering(hasWindowFocus);
-
- if (hasWindowFocus) {
- boolean inTouchMode = msg.arg2 != 0;
- ensureTouchModeLocally(inTouchMode);
-
- if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()){
- mFullRedrawNeeded = true;
- try {
- final WindowManager.LayoutParams lp = mWindowAttributes;
- final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
- mAttachInfo.mThreadedRenderer.initializeIfNeeded(
- mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
- } catch (OutOfResourcesException e) {
- Log.e(mTag, "OutOfResourcesException locking surface", e);
+ break;
+ case MSG_WINDOW_FOCUS_CHANGED: {
+ final boolean hasWindowFocus = msg.arg1 != 0;
+ if (mAdded) {
+ mAttachInfo.mHasWindowFocus = hasWindowFocus;
+
+ profileRendering(hasWindowFocus);
+
+ if (hasWindowFocus) {
+ boolean inTouchMode = msg.arg2 != 0;
+ ensureTouchModeLocally(inTouchMode);
+ if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
+ mFullRedrawNeeded = true;
try {
- if (!mWindowSession.outOfMemory(mWindow)) {
- Slog.w(mTag, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
+ final WindowManager.LayoutParams lp = mWindowAttributes;
+ final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
+ mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+ mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+ } catch (OutOfResourcesException e) {
+ Log.e(mTag, "OutOfResourcesException locking surface", e);
+ try {
+ if (!mWindowSession.outOfMemory(mWindow)) {
+ Slog.w(mTag, "No processes killed for memory;"
+ + " killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
}
- } catch (RemoteException ex) {
+ // Retry in a bit.
+ sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2),
+ 500);
+ return;
}
- // Retry in a bit.
- sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
- return;
}
}
- }
- mLastWasImTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
-
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPreWindowFocus(mView, hasWindowFocus);
- }
- if (mView != null) {
- mAttachInfo.mKeyDispatchState.reset();
- mView.dispatchWindowFocusChanged(hasWindowFocus);
- mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+ mLastWasImTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
- if (mAttachInfo.mTooltipHost != null) {
- mAttachInfo.mTooltipHost.hideTooltip();
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+ imm.onPreWindowFocus(mView, hasWindowFocus);
}
- }
+ if (mView != null) {
+ mAttachInfo.mKeyDispatchState.reset();
+ mView.dispatchWindowFocusChanged(hasWindowFocus);
+ mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
- // Note: must be done after the focus change callbacks,
- // so all of the view state is set up correctly.
- if (hasWindowFocus) {
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPostWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode,
- !mHasHadWindowFocus, mWindowAttributes.flags);
+ if (mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.hideTooltip();
+ }
}
- // Clear the forward bit. We can just do this directly, since
- // the window manager doesn't care about it.
- mWindowAttributes.softInputMode &=
- ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- ((WindowManager.LayoutParams)mView.getLayoutParams())
- .softInputMode &=
+
+ // Note: must be done after the focus change callbacks,
+ // so all of the view state is set up correctly.
+ if (hasWindowFocus) {
+ if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+ imm.onPostWindowFocus(mView, mView.findFocus(),
+ mWindowAttributes.softInputMode,
+ !mHasHadWindowFocus, mWindowAttributes.flags);
+ }
+ // Clear the forward bit. We can just do this directly, since
+ // the window manager doesn't care about it.
+ mWindowAttributes.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- mHasHadWindowFocus = true;
- } else {
- if (mPointerCapture) {
- handlePointerCaptureChanged(false);
+ ((WindowManager.LayoutParams) mView.getLayoutParams())
+ .softInputMode &=
+ ~WindowManager.LayoutParams
+ .SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ mHasHadWindowFocus = true;
+ } else {
+ if (mPointerCapture) {
+ handlePointerCaptureChanged(false);
+ }
}
}
- }
- } break;
- case MSG_DIE:
- doDie();
- break;
- case MSG_DISPATCH_INPUT_EVENT: {
- SomeArgs args = (SomeArgs)msg.obj;
- InputEvent event = (InputEvent)args.arg1;
- InputEventReceiver receiver = (InputEventReceiver)args.arg2;
- enqueueInputEvent(event, receiver, 0, true);
- args.recycle();
- } break;
- case MSG_SYNTHESIZE_INPUT_EVENT: {
- InputEvent event = (InputEvent)msg.obj;
- enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
- } break;
- case MSG_DISPATCH_KEY_FROM_IME: {
- if (LOCAL_LOGV) Log.v(
- TAG, "Dispatching key "
- + msg.obj + " from IME to " + mView);
- KeyEvent event = (KeyEvent)msg.obj;
- if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
- // The IME is trying to say this event is from the
- // system! Bad bad bad!
- //noinspection UnusedAssignment
- event = KeyEvent.changeFlags(event, event.getFlags() &
- ~KeyEvent.FLAG_FROM_SYSTEM);
- }
- enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
- } break;
- case MSG_CHECK_FOCUS: {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.checkFocus();
- }
- } break;
- case MSG_CLOSE_SYSTEM_DIALOGS: {
- if (mView != null) {
- mView.onCloseSystemDialogs((String)msg.obj);
- }
- } break;
- case MSG_DISPATCH_DRAG_EVENT:
- case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
- DragEvent event = (DragEvent)msg.obj;
- event.mLocalState = mLocalDragState; // only present when this app called startDrag()
- handleDragEvent(event);
- } break;
- case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
- } break;
- case MSG_UPDATE_CONFIGURATION: {
- Configuration config = (Configuration) msg.obj;
- if (config.isOtherSeqNewer(
- mLastReportedMergedConfiguration.getMergedConfiguration())) {
- // If we already have a newer merged config applied - use its global part.
- config = mLastReportedMergedConfiguration.getGlobalConfiguration();
- }
+ mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+ } break;
+ case MSG_DIE:
+ doDie();
+ break;
+ case MSG_DISPATCH_INPUT_EVENT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ InputEvent event = (InputEvent) args.arg1;
+ InputEventReceiver receiver = (InputEventReceiver) args.arg2;
+ enqueueInputEvent(event, receiver, 0, true);
+ args.recycle();
+ } break;
+ case MSG_SYNTHESIZE_INPUT_EVENT: {
+ InputEvent event = (InputEvent) msg.obj;
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
+ } break;
+ case MSG_DISPATCH_KEY_FROM_IME: {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Dispatching key " + msg.obj + " from IME to " + mView);
+ }
+ KeyEvent event = (KeyEvent) msg.obj;
+ if ((event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0) {
+ // The IME is trying to say this event is from the
+ // system! Bad bad bad!
+ //noinspection UnusedAssignment
+ event = KeyEvent.changeFlags(event,
+ event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+ }
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
+ } break;
+ case MSG_CHECK_FOCUS: {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.checkFocus();
+ }
+ } break;
+ case MSG_CLOSE_SYSTEM_DIALOGS: {
+ if (mView != null) {
+ mView.onCloseSystemDialogs((String) msg.obj);
+ }
+ } break;
+ case MSG_DISPATCH_DRAG_EVENT: {
+ } // fall through
+ case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
+ DragEvent event = (DragEvent) msg.obj;
+ // only present when this app called startDrag()
+ event.mLocalState = mLocalDragState;
+ handleDragEvent(event);
+ } break;
+ case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
+ handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+ } break;
+ case MSG_UPDATE_CONFIGURATION: {
+ Configuration config = (Configuration) msg.obj;
+ if (config.isOtherSeqNewer(
+ mLastReportedMergedConfiguration.getMergedConfiguration())) {
+ // If we already have a newer merged config applied - use its global part.
+ config = mLastReportedMergedConfiguration.getGlobalConfiguration();
+ }
- // Use the newer global config and last reported override config.
- mPendingMergedConfiguration.setConfiguration(config,
- mLastReportedMergedConfiguration.getOverrideConfiguration());
+ // Use the newer global config and last reported override config.
+ mPendingMergedConfiguration.setConfiguration(config,
+ mLastReportedMergedConfiguration.getOverrideConfiguration());
- performConfigurationChange(mPendingMergedConfiguration, false /* force */,
- INVALID_DISPLAY /* same display */);
- } break;
- case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
- setAccessibilityFocus(null, null);
- } break;
- case MSG_INVALIDATE_WORLD: {
- if (mView != null) {
- invalidateWorld(mView);
- }
- } break;
- case MSG_DISPATCH_WINDOW_SHOWN: {
- handleDispatchWindowShown();
- } break;
- case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
- final IResultReceiver receiver = (IResultReceiver) msg.obj;
- final int deviceId = msg.arg1;
- handleRequestKeyboardShortcuts(receiver, deviceId);
- } break;
- case MSG_UPDATE_POINTER_ICON: {
- MotionEvent event = (MotionEvent) msg.obj;
- resetPointerIcon(event);
- } break;
- case MSG_POINTER_CAPTURE_CHANGED: {
- final boolean hasCapture = msg.arg1 != 0;
- handlePointerCaptureChanged(hasCapture);
- } break;
- case MSG_DRAW_FINISHED: {
- pendingDrawFinished();
- } break;
+ performConfigurationChange(mPendingMergedConfiguration, false /* force */,
+ INVALID_DISPLAY /* same display */);
+ } break;
+ case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
+ setAccessibilityFocus(null, null);
+ } break;
+ case MSG_INVALIDATE_WORLD: {
+ if (mView != null) {
+ invalidateWorld(mView);
+ }
+ } break;
+ case MSG_DISPATCH_WINDOW_SHOWN: {
+ handleDispatchWindowShown();
+ } break;
+ case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
+ final IResultReceiver receiver = (IResultReceiver) msg.obj;
+ final int deviceId = msg.arg1;
+ handleRequestKeyboardShortcuts(receiver, deviceId);
+ } break;
+ case MSG_UPDATE_POINTER_ICON: {
+ MotionEvent event = (MotionEvent) msg.obj;
+ resetPointerIcon(event);
+ } break;
+ case MSG_POINTER_CAPTURE_CHANGED: {
+ final boolean hasCapture = msg.arg1 != 0;
+ handlePointerCaptureChanged(hasCapture);
+ } break;
+ case MSG_DRAW_FINISHED: {
+ pendingDrawFinished();
+ } break;
}
}
}
@@ -4203,6 +4208,18 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ protected void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (mNext != null) {
+ mNext.onWindowFocusChanged(hasWindowFocus);
+ }
+ }
+
+ protected void onDetachedFromWindow() {
+ if (mNext != null) {
+ mNext.onDetachedFromWindow();
+ }
+ }
+
protected boolean shouldDropInputEvent(QueuedInputEvent q) {
if (mView == null || !mAdded) {
Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
@@ -4956,9 +4973,9 @@ public final class ViewRootImpl implements ViewParent,
final MotionEvent event = (MotionEvent)q.mEvent;
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- mTrackball.cancel(event);
+ mTrackball.cancel();
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
- mJoystick.cancel(event);
+ mJoystick.cancel();
} else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
== InputDevice.SOURCE_TOUCH_NAVIGATION) {
mTouchNavigation.cancel(event);
@@ -4967,6 +4984,18 @@ public final class ViewRootImpl implements ViewParent,
}
super.onDeliverToNext(q);
}
+
+ @Override
+ protected void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (!hasWindowFocus) {
+ mJoystick.cancel();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mJoystick.cancel();
+ }
}
/**
@@ -5079,7 +5108,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- public void cancel(MotionEvent event) {
+ public void cancel() {
mLastTime = Integer.MIN_VALUE;
// If we reach this, we consumed a trackball event.
@@ -5263,14 +5292,11 @@ public final class ViewRootImpl implements ViewParent,
* Creates dpad events from unhandled joystick movements.
*/
final class SyntheticJoystickHandler extends Handler {
- private final static String TAG = "SyntheticJoystickHandler";
private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
- private int mLastXDirection;
- private int mLastYDirection;
- private int mLastXKeyCode;
- private int mLastYKeyCode;
+ private final JoystickAxesState mJoystickAxesState = new JoystickAxesState();
+ private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>();
public SyntheticJoystickHandler() {
super(true);
@@ -5281,11 +5307,10 @@ public final class ViewRootImpl implements ViewParent,
switch (msg.what) {
case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
- KeyEvent oldEvent = (KeyEvent)msg.obj;
- KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
- SystemClock.uptimeMillis(),
- oldEvent.getRepeatCount() + 1);
if (mAttachInfo.mHasWindowFocus) {
+ KeyEvent oldEvent = (KeyEvent) msg.obj;
+ KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+ SystemClock.uptimeMillis(), oldEvent.getRepeatCount() + 1);
enqueueInputEvent(e);
Message m = obtainMessage(msg.what, e);
m.setAsynchronous(true);
@@ -5297,97 +5322,176 @@ public final class ViewRootImpl implements ViewParent,
public void process(MotionEvent event) {
switch(event.getActionMasked()) {
- case MotionEvent.ACTION_CANCEL:
- cancel(event);
- break;
- case MotionEvent.ACTION_MOVE:
- update(event, true);
- break;
- default:
- Log.w(mTag, "Unexpected action: " + event.getActionMasked());
+ case MotionEvent.ACTION_CANCEL:
+ cancel();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ update(event);
+ break;
+ default:
+ Log.w(mTag, "Unexpected action: " + event.getActionMasked());
}
}
- private void cancel(MotionEvent event) {
+ private void cancel() {
removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
- update(event, false);
- }
-
- private void update(MotionEvent event, boolean synthesizeNewKeys) {
+ for (int i = 0; i < mDeviceKeyEvents.size(); i++) {
+ final KeyEvent keyEvent = mDeviceKeyEvents.valueAt(i);
+ if (keyEvent != null) {
+ enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent,
+ SystemClock.uptimeMillis(), 0));
+ }
+ }
+ mDeviceKeyEvents.clear();
+ mJoystickAxesState.resetState();
+ }
+
+ private void update(MotionEvent event) {
+ final int historySize = event.getHistorySize();
+ for (int h = 0; h < historySize; h++) {
+ final long time = event.getHistoricalEventTime(h);
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_X, 0, h));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_Y, 0, h));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, 0, h));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, 0, h));
+ }
final long time = event.getEventTime();
- final int metaState = event.getMetaState();
- final int deviceId = event.getDeviceId();
- final int source = event.getSource();
-
- int xDirection = joystickAxisValueToDirection(
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+ event.getAxisValue(MotionEvent.AXIS_X));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+ event.getAxisValue(MotionEvent.AXIS_Y));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
event.getAxisValue(MotionEvent.AXIS_HAT_X));
- if (xDirection == 0) {
- xDirection = joystickAxisValueToDirection(event.getX());
- }
-
- int yDirection = joystickAxisValueToDirection(
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
event.getAxisValue(MotionEvent.AXIS_HAT_Y));
- if (yDirection == 0) {
- yDirection = joystickAxisValueToDirection(event.getY());
- }
+ }
+
+ final class JoystickAxesState {
+ // State machine: from neutral state (no button press) can go into
+ // button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event.
+ // From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state,
+ // emitting an ACTION_UP event.
+ private static final int STATE_UP_OR_LEFT = -1;
+ private static final int STATE_NEUTRAL = 0;
+ private static final int STATE_DOWN_OR_RIGHT = 1;
+
+ final int[] mAxisStatesHat = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_HAT_X, AXIS_HAT_Y}
+ final int[] mAxisStatesStick = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_X, AXIS_Y}
+
+ void resetState() {
+ mAxisStatesHat[0] = STATE_NEUTRAL;
+ mAxisStatesHat[1] = STATE_NEUTRAL;
+ mAxisStatesStick[0] = STATE_NEUTRAL;
+ mAxisStatesStick[1] = STATE_NEUTRAL;
+ }
+
+ void updateStateForAxis(MotionEvent event, long time, int axis, float value) {
+ // Emit KeyEvent if necessary
+ // axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Y
+ final int axisStateIndex;
+ final int repeatMessage;
+ if (isXAxis(axis)) {
+ axisStateIndex = 0;
+ repeatMessage = MSG_ENQUEUE_X_AXIS_KEY_REPEAT;
+ } else if (isYAxis(axis)) {
+ axisStateIndex = 1;
+ repeatMessage = MSG_ENQUEUE_Y_AXIS_KEY_REPEAT;
+ } else {
+ Log.e(mTag, "Unexpected axis " + axis + " in updateStateForAxis!");
+ return;
+ }
+ final int newState = joystickAxisValueToState(value);
+
+ final int currentState;
+ if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+ currentState = mAxisStatesStick[axisStateIndex];
+ } else {
+ currentState = mAxisStatesHat[axisStateIndex];
+ }
- if (xDirection != mLastXDirection) {
- if (mLastXKeyCode != 0) {
- removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
- mLastXKeyCode = 0;
+ if (currentState == newState) {
+ return;
}
- mLastXDirection = xDirection;
+ final int metaState = event.getMetaState();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
- if (xDirection != 0 && synthesizeNewKeys) {
- mLastXKeyCode = xDirection > 0
- ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
- final KeyEvent e = new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
- enqueueInputEvent(e);
- Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
- m.setAsynchronous(true);
- sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ if (currentState == STATE_DOWN_OR_RIGHT || currentState == STATE_UP_OR_LEFT) {
+ // send a button release event
+ final int keyCode = joystickAxisAndStateToKeycode(axis, currentState);
+ if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ enqueueInputEvent(new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+ 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ // remove the corresponding pending UP event if focus lost/view detached
+ mDeviceKeyEvents.put(deviceId, null);
+ }
+ removeMessages(repeatMessage);
}
- }
- if (yDirection != mLastYDirection) {
- if (mLastYKeyCode != 0) {
- removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
- mLastYKeyCode = 0;
+ if (newState == STATE_DOWN_OR_RIGHT || newState == STATE_UP_OR_LEFT) {
+ // send a button down event
+ final int keyCode = joystickAxisAndStateToKeycode(axis, newState);
+ if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ KeyEvent keyEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode,
+ 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(keyEvent);
+ Message m = obtainMessage(repeatMessage, keyEvent);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ // store the corresponding ACTION_UP event so that it can be sent
+ // if focus is lost or root view is removed
+ mDeviceKeyEvents.put(deviceId,
+ new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+ 0, metaState, deviceId, 0,
+ KeyEvent.FLAG_FALLBACK | KeyEvent.FLAG_CANCELED,
+ source));
+ }
+ }
+ if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+ mAxisStatesStick[axisStateIndex] = newState;
+ } else {
+ mAxisStatesHat[axisStateIndex] = newState;
}
+ }
- mLastYDirection = yDirection;
+ private boolean isXAxis(int axis) {
+ return axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_HAT_X;
+ }
+ private boolean isYAxis(int axis) {
+ return axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_HAT_Y;
+ }
- if (yDirection != 0 && synthesizeNewKeys) {
- mLastYKeyCode = yDirection > 0
- ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
- final KeyEvent e = new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
- enqueueInputEvent(e);
- Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
- m.setAsynchronous(true);
- sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ private int joystickAxisAndStateToKeycode(int axis, int state) {
+ if (isXAxis(axis) && state == STATE_UP_OR_LEFT) {
+ return KeyEvent.KEYCODE_DPAD_LEFT;
+ }
+ if (isXAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+ return KeyEvent.KEYCODE_DPAD_RIGHT;
+ }
+ if (isYAxis(axis) && state == STATE_UP_OR_LEFT) {
+ return KeyEvent.KEYCODE_DPAD_UP;
}
+ if (isYAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+ return KeyEvent.KEYCODE_DPAD_DOWN;
+ }
+ Log.e(mTag, "Unknown axis " + axis + " or direction " + state);
+ return KeyEvent.KEYCODE_UNKNOWN; // should never happen
}
- }
- private int joystickAxisValueToDirection(float value) {
- if (value >= 0.5f) {
- return 1;
- } else if (value <= -0.5f) {
- return -1;
- } else {
- return 0;
+ private int joystickAxisValueToState(float value) {
+ if (value >= 0.5f) {
+ return STATE_DOWN_OR_RIGHT;
+ } else if (value <= -0.5f) {
+ return STATE_UP_OR_LEFT;
+ } else {
+ return STATE_NEUTRAL;
+ }
}
}
}
@@ -6108,7 +6212,6 @@ public final class ViewRootImpl implements ViewParent,
if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
}
- //Log.d(mTag, ">>>>>> CALLING relayout");
if (params != null && mOrigWindowType != params.type) {
// For compatibility with old apps, don't crash here.
if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
@@ -6129,7 +6232,6 @@ public final class ViewRootImpl implements ViewParent,
mPendingAlwaysConsumeNavBar =
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
- //Log.d(mTag, "<<<<<< BACK FROM relayout");
if (restore) {
params.restore();
}
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
index f671c349..d665dde3 100644
--- a/android/view/ViewStructure.java
+++ b/android/view/ViewStructure.java
@@ -365,6 +365,30 @@ public abstract class ViewStructure {
public abstract void setDataIsSensitive(boolean sensitive);
/**
+ * Sets the minimum width in ems of the text associated with this view, when supported.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for Assist.
+ */
+ public void setMinTextEms(@SuppressWarnings("unused") int minEms) {}
+
+ /**
+ * Sets the maximum width in ems of the text associated with this view, when supported.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for Assist.
+ */
+ public void setMaxTextEms(@SuppressWarnings("unused") int maxEms) {}
+
+ /**
+ * Sets the maximum length of the text associated with this view, when supported.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for Assist.
+ */
+ public void setMaxTextLength(@SuppressWarnings("unused") int maxLength) {}
+
+ /**
* Call when done populating a {@link ViewStructure} returned by
* {@link #asyncNewChild}.
*/
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
index 98f8dc8e..69cc1002 100644
--- a/android/view/WindowManagerInternal.java
+++ b/android/view/WindowManagerInternal.java
@@ -335,8 +335,8 @@ public abstract class WindowManagerInternal {
public abstract void setOnHardKeyboardStatusChangeListener(
OnHardKeyboardStatusChangeListener listener);
- /** Returns true if the stack with the input Id is currently visible. */
- public abstract boolean isStackVisible(int stackId);
+ /** Returns true if a stack in the windowing mode is currently visible. */
+ public abstract boolean isStackVisible(int windowingMode);
/**
* @return True if and only if the docked divider is currently in resize mode.
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index da72535d..137e551d 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -467,11 +467,8 @@ public interface WindowManagerPolicy {
*/
public boolean isDimming();
- /**
- * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if
- * not attached to any stack.
- */
- int getStackId();
+ /** @return the current windowing mode of this window. */
+ int getWindowingMode();
/**
* Returns true if the window is current in multi-windowing mode. i.e. it shares the
diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java
index 0f21c5c8..d7851171 100644
--- a/android/view/accessibility/AccessibilityCache.java
+++ b/android/view/accessibility/AccessibilityCache.java
@@ -329,8 +329,6 @@ public final class AccessibilityCache {
final long oldParentId = oldInfo.getParentNodeId();
if (info.getParentNodeId() != oldParentId) {
clearSubTreeLocked(windowId, oldParentId);
- } else {
- oldInfo.recycle();
}
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc576..11cb046a 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,152 +16,46 @@
package android.view.accessibility;
-import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
-
-import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.annotation.SystemService;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.os.Binder;
import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
import android.view.IWindow;
import android.view.View;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IntPair;
-
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
- * and provides facilities for querying the accessibility state of the system.
- * Accessibility events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
* for example an {@link android.app.Activity} starts, the focus or selection of a
* {@link android.view.View} changes etc. Parties interested in handling accessibility
* events implement and register an accessibility service which extends
- * {@link android.accessibilityservice.AccessibilityService}.
+ * {@code android.accessibilityservice.AccessibilityService}.
*
* @see AccessibilityEvent
- * @see AccessibilityNodeInfo
- * @see android.accessibilityservice.AccessibilityService
- * @see Context#getSystemService
- * @see Context#ACCESSIBILITY_SERVICE
+ * @see android.content.Context#getSystemService
*/
-@SystemService(Context.ACCESSIBILITY_SERVICE)
+@SuppressWarnings("UnusedDeclaration")
public final class AccessibilityManager {
- private static final boolean DEBUG = false;
-
- private static final String LOG_TAG = "AccessibilityManager";
-
- /** @hide */
- public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
-
- /** @hide */
- public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
-
- /** @hide */
- public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
-
- /** @hide */
- public static final int DALTONIZER_DISABLED = -1;
-
- /** @hide */
- public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
-
- /** @hide */
- public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
-
- /** @hide */
- public static final int AUTOCLICK_DELAY_DEFAULT = 600;
-
- /**
- * Activity action: Launch UI to manage which accessibility service or feature is assigned
- * to the navigation bar Accessibility button.
- * <p>
- * Input: Nothing.
- * </p>
- * <p>
- * Output: Nothing.
- * </p>
- *
- * @hide
- */
- @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
- "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
-
- static final Object sInstanceSync = new Object();
-
- private static AccessibilityManager sInstance;
-
- private final Object mLock = new Object();
-
- private IAccessibilityManager mService;
-
- final int mUserId;
-
- final Handler mHandler;
-
- final Handler.Callback mCallback;
-
- boolean mIsEnabled;
- int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
- boolean mIsTouchExplorationEnabled;
-
- boolean mIsHighTextContrastEnabled;
-
- private final ArrayMap<AccessibilityStateChangeListener, Handler>
- mAccessibilityStateChangeListeners = new ArrayMap<>();
-
- private final ArrayMap<TouchExplorationStateChangeListener, Handler>
- mTouchExplorationStateChangeListeners = new ArrayMap<>();
-
- private final ArrayMap<HighTextContrastChangeListener, Handler>
- mHighTextContrastStateChangeListeners = new ArrayMap<>();
-
- private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
- mServicesStateChangeListeners = new ArrayMap<>();
-
- /**
- * Map from a view's accessibility id to the list of request preparers set for that view
- */
- private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
/**
- * Listener for the system accessibility state. To listen for changes to the
- * accessibility state on the device, implement this interface and register
- * it with the system by calling {@link #addAccessibilityStateChangeListener}.
+ * Listener for the accessibility state.
*/
public interface AccessibilityStateChangeListener {
/**
- * Called when the accessibility enabled state changes.
+ * Called back on change in the accessibility state.
*
* @param enabled Whether accessibility is enabled.
*/
- void onAccessibilityStateChanged(boolean enabled);
+ public void onAccessibilityStateChanged(boolean enabled);
}
/**
@@ -177,24 +71,7 @@ public final class AccessibilityManager {
*
* @param enabled Whether touch exploration is enabled.
*/
- void onTouchExplorationStateChanged(boolean enabled);
- }
-
- /**
- * Listener for changes to the state of accessibility services. Changes include services being
- * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
- * {@see #addAccessibilityServicesStateChangeListener}.
- *
- * @hide
- */
- public interface AccessibilityServicesStateChangeListener {
-
- /**
- * Called when the state of accessibility services changes.
- *
- * @param manager The manager that is calling back
- */
- void onAccessibilityServicesStateChanged(AccessibilityManager manager);
+ public void onTouchExplorationStateChanged(boolean enabled);
}
/**
@@ -202,8 +79,6 @@ public final class AccessibilityManager {
* the high text contrast state on the device, implement this interface and
* register it with the system by calling
* {@link #addHighTextContrastStateChangeListener}.
- *
- * @hide
*/
public interface HighTextContrastChangeListener {
@@ -212,72 +87,26 @@ public final class AccessibilityManager {
*
* @param enabled Whether high text contrast is enabled.
*/
- void onHighTextContrastStateChanged(boolean enabled);
+ public void onHighTextContrastStateChanged(boolean enabled);
}
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
- @Override
- public void setState(int state) {
- // We do not want to change this immediately as the application may
- // have already checked that accessibility is on and fired an event,
- // that is now propagating up the view tree, Hence, if accessibility
- // is now off an exception will be thrown. We want to have the exception
- // enforcement to guard against apps that fire unnecessary accessibility
- // events when accessibility is off.
- mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
- }
-
- @Override
- public void notifyServicesStateChanged() {
- final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mServicesStateChangeListeners.isEmpty()) {
- return;
+ public void setState(int state) {
}
- listeners = new ArrayMap<>(mServicesStateChangeListeners);
- }
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final AccessibilityServicesStateChangeListener listener =
- mServicesStateChangeListeners.keyAt(i);
- mServicesStateChangeListeners.valueAt(i).post(() -> listener
- .onAccessibilityServicesStateChanged(AccessibilityManager.this));
- }
- }
+ public void notifyServicesStateChanged() {
+ }
- @Override
- public void setRelevantEventTypes(int eventTypes) {
- mRelevantEventTypes = eventTypes;
- }
- };
+ public void setRelevantEventTypes(int eventTypes) {
+ }
+ };
/**
* Get an AccessibilityManager instance (create one if necessary).
*
- * @param context Context in which this manager operates.
- *
- * @hide
*/
public static AccessibilityManager getInstance(Context context) {
- synchronized (sInstanceSync) {
- if (sInstance == null) {
- final int userId;
- if (Binder.getCallingUid() == Process.SYSTEM_UID
- || context.checkCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS)
- == PackageManager.PERMISSION_GRANTED
- || context.checkCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- == PackageManager.PERMISSION_GRANTED) {
- userId = UserHandle.USER_CURRENT;
- } else {
- userId = UserHandle.myUserId();
- }
- sInstance = new AccessibilityManager(context, null, userId);
- }
- }
return sInstance;
}
@@ -285,68 +114,21 @@ public final class AccessibilityManager {
* Create an instance.
*
* @param context A {@link Context}.
- * @param service An interface to the backing service.
- * @param userId User id under which to run.
- *
- * @hide
*/
public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
- // Constructor can't be chained because we can't create an instance of an inner class
- // before calling another constructor.
- mCallback = new MyCallback();
- mHandler = new Handler(context.getMainLooper(), mCallback);
- mUserId = userId;
- synchronized (mLock) {
- tryConnectToServiceLocked(service);
- }
- }
-
- /**
- * Create an instance.
- *
- * @param handler The handler to use
- * @param service An interface to the backing service.
- * @param userId User id under which to run.
- *
- * @hide
- */
- public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
- mCallback = new MyCallback();
- mHandler = handler;
- mUserId = userId;
- synchronized (mLock) {
- tryConnectToServiceLocked(service);
- }
}
- /**
- * @hide
- */
public IAccessibilityManagerClient getClient() {
return mClient;
}
/**
- * @hide
- */
- @VisibleForTesting
- public Handler.Callback getCallback() {
- return mCallback;
- }
-
- /**
- * Returns if the accessibility in the system is enabled.
+ * Returns if the {@link AccessibilityManager} is enabled.
*
- * @return True if accessibility is enabled, false otherwise.
+ * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
*/
public boolean isEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsEnabled;
- }
+ return false;
}
/**
@@ -355,13 +137,7 @@ public final class AccessibilityManager {
* @return True if touch exploration is enabled, false otherwise.
*/
public boolean isTouchExplorationEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsTouchExplorationEnabled;
- }
+ return true;
}
/**
@@ -371,169 +147,35 @@ public final class AccessibilityManager {
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
- * @return True if high text contrast is enabled, false otherwise.
- *
- * @hide
*/
public boolean isHighTextContrastEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsHighTextContrastEnabled;
- }
+ return false;
}
/**
* Sends an {@link AccessibilityEvent}.
- *
- * @param event The event to send.
- *
- * @throws IllegalStateException if accessibility is not enabled.
- *
- * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
- * events is through calling
- * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
- * instead of this method to allow predecessors to augment/filter events sent by
- * their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- if (!mIsEnabled) {
- Looper myLooper = Looper.myLooper();
- if (myLooper == Looper.getMainLooper()) {
- throw new IllegalStateException(
- "Accessibility off. Did you forget to check that?");
- } else {
- // If we're not running on the thread with the main looper, it's possible for
- // the state of accessibility to change between checking isEnabled and
- // calling this method. So just log the error rather than throwing the
- // exception.
- Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
- return;
- }
- }
- if ((event.getEventType() & mRelevantEventTypes) == 0) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
- + " that is not among "
- + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
- }
- return;
- }
- userId = mUserId;
- }
- try {
- event.setEventTime(SystemClock.uptimeMillis());
- // it is possible that this manager is in the same process as the service but
- // client using it is called through Binder from another process. Example: MMS
- // app adds a SMS notification and the NotificationManagerService calls this method
- long identityToken = Binder.clearCallingIdentity();
- service.sendAccessibilityEvent(event, userId);
- Binder.restoreCallingIdentity(identityToken);
- if (DEBUG) {
- Log.i(LOG_TAG, event + " sent");
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error during sending " + event + " ", re);
- } finally {
- event.recycle();
- }
}
/**
- * Requests feedback interruption from all accessibility services.
+ * Requests interruption of the accessibility feedback from all accessibility services.
*/
public void interrupt() {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- if (!mIsEnabled) {
- Looper myLooper = Looper.myLooper();
- if (myLooper == Looper.getMainLooper()) {
- throw new IllegalStateException(
- "Accessibility off. Did you forget to check that?");
- } else {
- // If we're not running on the thread with the main looper, it's possible for
- // the state of accessibility to change between checking isEnabled and
- // calling this method. So just log the error rather than throwing the
- // exception.
- Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
- return;
- }
- }
- userId = mUserId;
- }
- try {
- service.interrupt(userId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Requested interrupt from all services");
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
- }
}
/**
* Returns the {@link ServiceInfo}s of the installed accessibility services.
*
* @return An unmodifiable list with {@link ServiceInfo}s.
- *
- * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
*/
@Deprecated
public List<ServiceInfo> getAccessibilityServiceList() {
- List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
- List<ServiceInfo> services = new ArrayList<>();
- final int infoCount = infos.size();
- for (int i = 0; i < infoCount; i++) {
- AccessibilityServiceInfo info = infos.get(i);
- services.add(info.getResolveInfo().serviceInfo);
- }
- return Collections.unmodifiableList(services);
+ return Collections.emptyList();
}
- /**
- * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
- *
- * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
- */
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return Collections.emptyList();
- }
- userId = mUserId;
- }
-
- List<AccessibilityServiceInfo> services = null;
- try {
- services = service.getInstalledAccessibilityServiceList(userId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
- }
- if (services != null) {
- return Collections.unmodifiableList(services);
- } else {
- return Collections.emptyList();
- }
+ return Collections.emptyList();
}
/**
@@ -548,48 +190,21 @@ public final class AccessibilityManager {
* @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
* @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
* @see AccessibilityServiceInfo#FEEDBACK_VISUAL
- * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
*/
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
int feedbackTypeFlags) {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return Collections.emptyList();
- }
- userId = mUserId;
- }
-
- List<AccessibilityServiceInfo> services = null;
- try {
- services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
- }
- if (services != null) {
- return Collections.unmodifiableList(services);
- } else {
- return Collections.emptyList();
- }
+ return Collections.emptyList();
}
/**
* Registers an {@link AccessibilityStateChangeListener} for changes in
- * the global accessibility state of the system. Equivalent to calling
- * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
- * with a null handler.
+ * the global accessibility state of the system.
*
* @param listener The listener.
- * @return Always returns {@code true}.
+ * @return True if successfully registered.
*/
public boolean addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener) {
- addAccessibilityStateChangeListener(listener, null);
+ AccessibilityStateChangeListener listener) {
return true;
}
@@ -603,40 +218,22 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mAccessibilityStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
- /**
- * Unregisters an {@link AccessibilityStateChangeListener}.
- *
- * @param listener The listener.
- * @return True if the listener was previously registered.
- */
public boolean removeAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener) {
- synchronized (mLock) {
- int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
- mAccessibilityStateChangeListeners.remove(listener);
- return (index >= 0);
- }
+ AccessibilityStateChangeListener listener) {
+ return true;
}
/**
* Registers a {@link TouchExplorationStateChangeListener} for changes in
- * the global touch exploration state of the system. Equivalent to calling
- * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
- * with a null handler.
+ * the global touch exploration state of the system.
*
* @param listener The listener.
- * @return Always returns {@code true}.
+ * @return True if successfully registered.
*/
public boolean addTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- addTouchExplorationStateChangeListener(listener, null);
return true;
}
@@ -650,103 +247,17 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mTouchExplorationStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
/**
* Unregisters a {@link TouchExplorationStateChangeListener}.
*
* @param listener The listener.
- * @return True if listener was previously registered.
+ * @return True if successfully unregistered.
*/
public boolean removeTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- synchronized (mLock) {
- int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
- mTouchExplorationStateChangeListeners.remove(listener);
- return (index >= 0);
- }
- }
-
- /**
- * Registers a {@link AccessibilityServicesStateChangeListener}.
- *
- * @param listener The listener.
- * @param handler The handler on which the listener should be called back, or {@code null}
- * for a callback on the process's main handler.
- * @hide
- */
- public void addAccessibilityServicesStateChangeListener(
- @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mServicesStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
-
- /**
- * Unregisters a {@link AccessibilityServicesStateChangeListener}.
- *
- * @param listener The listener.
- *
- * @hide
- */
- public void removeAccessibilityServicesStateChangeListener(
- @NonNull AccessibilityServicesStateChangeListener listener) {
- // Final CopyOnWriteArrayList - no lock needed.
- mServicesStateChangeListeners.remove(listener);
- }
-
- /**
- * Registers a {@link AccessibilityRequestPreparer}.
- */
- public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
- if (mRequestPreparerLists == null) {
- mRequestPreparerLists = new SparseArray<>(1);
- }
- int id = preparer.getView().getAccessibilityViewId();
- List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
- if (requestPreparerList == null) {
- requestPreparerList = new ArrayList<>(1);
- mRequestPreparerLists.put(id, requestPreparerList);
- }
- requestPreparerList.add(preparer);
- }
-
- /**
- * Unregisters a {@link AccessibilityRequestPreparer}.
- */
- public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
- if (mRequestPreparerLists == null) {
- return;
- }
- int viewId = preparer.getView().getAccessibilityViewId();
- List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
- if (requestPreparerList != null) {
- requestPreparerList.remove(preparer);
- if (requestPreparerList.isEmpty()) {
- mRequestPreparerLists.remove(viewId);
- }
- }
- }
-
- /**
- * Get the preparers that are registered for an accessibility ID
- *
- * @param id The ID of interest
- * @return The list of preparers, or {@code null} if there are none.
- *
- * @hide
- */
- public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
- if (mRequestPreparerLists == null) {
- return null;
- }
- return mRequestPreparerLists.get(id);
+ return true;
}
/**
@@ -758,12 +269,7 @@ public final class AccessibilityManager {
* @hide
*/
public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mHighTextContrastStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
/**
* Unregisters a {@link HighTextContrastChangeListener}.
@@ -773,51 +279,7 @@ public final class AccessibilityManager {
* @hide
*/
public void removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {
- synchronized (mLock) {
- mHighTextContrastStateChangeListeners.remove(listener);
- }
- }
-
- /**
- * Check if the accessibility volume stream is active.
- *
- * @return True if accessibility volume is active (i.e. some service has requested it). False
- * otherwise.
- * @hide
- */
- public boolean isAccessibilityVolumeStreamActive() {
- List<AccessibilityServiceInfo> serviceInfos =
- getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- for (int i = 0; i < serviceInfos.size(); i++) {
- if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Report a fingerprint gesture to accessibility. Only available for the system process.
- *
- * @param keyCode The key code of the gesture
- * @return {@code true} if accessibility consumes the event. {@code false} if not.
- * @hide
- */
- public boolean sendFingerprintGesture(int keyCode) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return false;
- }
- }
- try {
- return service.sendFingerprintGesture(keyCode);
- } catch (RemoteException e) {
- return false;
- }
- }
+ @NonNull HighTextContrastChangeListener listener) {}
/**
* Sets the current state and notifies listeners, if necessary.
@@ -825,314 +287,14 @@ public final class AccessibilityManager {
* @param stateFlags The state flags.
*/
private void setStateLocked(int stateFlags) {
- final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
- final boolean touchExplorationEnabled =
- (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
- final boolean highTextContrastEnabled =
- (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
-
- final boolean wasEnabled = mIsEnabled;
- final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
- final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
-
- // Ensure listeners get current state from isZzzEnabled() calls.
- mIsEnabled = enabled;
- mIsTouchExplorationEnabled = touchExplorationEnabled;
- mIsHighTextContrastEnabled = highTextContrastEnabled;
-
- if (wasEnabled != enabled) {
- notifyAccessibilityStateChanged();
- }
-
- if (wasTouchExplorationEnabled != touchExplorationEnabled) {
- notifyTouchExplorationStateChanged();
- }
-
- if (wasHighTextContrastEnabled != highTextContrastEnabled) {
- notifyHighTextContrastStateChanged();
- }
}
- /**
- * Find an installed service with the specified {@link ComponentName}.
- *
- * @param componentName The name to match to the service.
- *
- * @return The info corresponding to the installed service, or {@code null} if no such service
- * is installed.
- * @hide
- */
- public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
- ComponentName componentName) {
- final List<AccessibilityServiceInfo> installedServiceInfos =
- getInstalledAccessibilityServiceList();
- if ((installedServiceInfos == null) || (componentName == null)) {
- return null;
- }
- for (int i = 0; i < installedServiceInfos.size(); i++) {
- if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
- return installedServiceInfos.get(i);
- }
- }
- return null;
- }
-
- /**
- * Adds an accessibility interaction connection interface for a given window.
- * @param windowToken The window token to which a connection is added.
- * @param connection The connection.
- *
- * @hide
- */
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return View.NO_ID;
- }
- userId = mUserId;
- }
- try {
- return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
- }
return View.NO_ID;
}
- /**
- * Removed an accessibility interaction connection interface for a given window.
- * @param windowToken The window token to which a connection is removed.
- *
- * @hide
- */
public void removeAccessibilityInteractionConnection(IWindow windowToken) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.removeAccessibilityInteractionConnection(windowToken);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
- }
- }
-
- /**
- * Perform the accessibility shortcut if the caller has permission.
- *
- * @hide
- */
- public void performAccessibilityShortcut() {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.performAccessibilityShortcut();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
- }
- }
-
- /**
- * Notifies that the accessibility button in the system's navigation area has been clicked
- *
- * @hide
- */
- public void notifyAccessibilityButtonClicked() {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.notifyAccessibilityButtonClicked();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
- }
- }
-
- /**
- * Notifies that the visibility of the accessibility button in the system's navigation area
- * has changed.
- *
- * @param shown {@code true} if the accessibility button is visible within the system
- * navigation area, {@code false} otherwise
- * @hide
- */
- public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.notifyAccessibilityButtonVisibilityChanged(shown);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
- }
- }
-
- /**
- * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
- * window. Intended for use by the System UI only.
- *
- * @param connection The connection to handle the actions. Set to {@code null} to avoid
- * affecting the actions.
- *
- * @hide
- */
- public void setPictureInPictureActionReplacingConnection(
- @Nullable IAccessibilityInteractionConnection connection) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.setPictureInPictureActionReplacingConnection(connection);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
- }
}
- private IAccessibilityManager getServiceLocked() {
- if (mService == null) {
- tryConnectToServiceLocked(null);
- }
- return mService;
- }
-
- private void tryConnectToServiceLocked(IAccessibilityManager service) {
- if (service == null) {
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- if (iBinder == null) {
- return;
- }
- service = IAccessibilityManager.Stub.asInterface(iBinder);
- }
-
- try {
- final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
- setStateLocked(IntPair.first(userStateAndRelevantEvents));
- mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
- mService = service;
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
- }
- }
-
- /**
- * Notifies the registered {@link AccessibilityStateChangeListener}s.
- */
- private void notifyAccessibilityStateChanged() {
- final boolean isEnabled;
- final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mAccessibilityStateChangeListeners.isEmpty()) {
- return;
- }
- isEnabled = mIsEnabled;
- listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
- }
-
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final AccessibilityStateChangeListener listener =
- mAccessibilityStateChangeListeners.keyAt(i);
- mAccessibilityStateChangeListeners.valueAt(i)
- .post(() -> listener.onAccessibilityStateChanged(isEnabled));
- }
- }
-
- /**
- * Notifies the registered {@link TouchExplorationStateChangeListener}s.
- */
- private void notifyTouchExplorationStateChanged() {
- final boolean isTouchExplorationEnabled;
- final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mTouchExplorationStateChangeListeners.isEmpty()) {
- return;
- }
- isTouchExplorationEnabled = mIsTouchExplorationEnabled;
- listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
- }
-
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final TouchExplorationStateChangeListener listener =
- mTouchExplorationStateChangeListeners.keyAt(i);
- mTouchExplorationStateChangeListeners.valueAt(i)
- .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
- }
- }
-
- /**
- * Notifies the registered {@link HighTextContrastChangeListener}s.
- */
- private void notifyHighTextContrastStateChanged() {
- final boolean isHighTextContrastEnabled;
- final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mHighTextContrastStateChangeListeners.isEmpty()) {
- return;
- }
- isHighTextContrastEnabled = mIsHighTextContrastEnabled;
- listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
- }
-
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final HighTextContrastChangeListener listener =
- mHighTextContrastStateChangeListeners.keyAt(i);
- mHighTextContrastStateChangeListeners.valueAt(i)
- .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
- }
- }
-
- /**
- * Determines if the accessibility button within the system navigation area is supported.
- *
- * @return {@code true} if the accessibility button is supported on this device,
- * {@code false} otherwise
- */
- public static boolean isAccessibilityButtonSupported() {
- final Resources res = Resources.getSystem();
- return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
- }
-
- private final class MyCallback implements Handler.Callback {
- public static final int MSG_SET_STATE = 1;
-
- @Override
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case MSG_SET_STATE: {
- // See comment at mClient
- final int state = message.arg1;
- synchronized (mLock) {
- setStateLocked(state);
- }
- } break;
- }
- return true;
- }
- }
}
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index 4fb2a99a..e564fa34 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -91,10 +91,10 @@ import java.util.Objects;
* </ul>
*
* <p>When the service returns datasets, the Android System displays an autofill dataset picker
- * UI affordance associated with the view, when the view is focused on and is part of a dataset.
- * The application can be notified when the affordance is shown by registering an
+ * UI associated with the view, when the view is focused on and is part of a dataset.
+ * The application can be notified when the UI is shown by registering an
* {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
- * selects a dataset from the affordance, all views present in the dataset are autofilled, through
+ * selects a dataset from the UI, all views present in the dataset are autofilled, through
* calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
*
* <p>When the service returns ids of savable views, the Android System keeps track of changes
@@ -108,7 +108,7 @@ import java.util.Objects;
* </ul>
*
* <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
- * shows a save UI affordance if the value of savable views have changed. If the user selects the
+ * shows an autofill save UI if the value of savable views have changed. If the user selects the
* option to Save, the current value of the views is then sent to the autofill service.
*
* <p>It is safe to call into its methods from any thread.
@@ -150,6 +150,12 @@ public final class AutofillManager {
* service authentication will contain the Bundle set by
* {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
*
+ * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service
+ * can also add this bundle to the {@link Intent} set as the
+ * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request,
+ * so the bundle can be recovered later on
+ * {@link android.service.autofill.SaveRequest#getClientState()}.
+ *
* <p>
* Type: {@link android.os.Bundle}
*/
@@ -311,6 +317,14 @@ public final class AutofillManager {
@GuardedBy("mLock")
@Nullable private ArraySet<AutofillId> mFillableIds;
+ /** If set, session is commited when the field is clicked. */
+ @GuardedBy("mLock")
+ @Nullable private AutofillId mSaveTriggerId;
+
+ /** If set, session is commited when the activity is finished; otherwise session is canceled. */
+ @GuardedBy("mLock")
+ private boolean mSaveOnFinish;
+
/** @hide */
public interface AutofillClient {
/**
@@ -834,6 +848,46 @@ public final class AutofillManager {
}
}
+
+ /**
+ * Called when a {@link View} is clicked. Currently only used by views that should trigger save.
+ *
+ * @hide
+ */
+ public void notifyViewClicked(View view) {
+ final AutofillId id = view.getAutofillId();
+
+ if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId);
+
+ synchronized (mLock) {
+ if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
+ if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
+ commitLocked();
+ mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED,
+ mContext.getPackageName());
+ }
+ }
+ }
+
+ /**
+ * Called by {@link android.app.Activity} to commit or cancel the session on finish.
+ *
+ * @hide
+ */
+ public void onActivityFinished() {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mSaveOnFinish) {
+ commitLocked();
+ } else {
+ if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service");
+ cancelLocked();
+ }
+ }
+ }
+
/**
* Called to indicate the current autofill context should be commited.
*
@@ -850,12 +904,15 @@ public final class AutofillManager {
return;
}
synchronized (mLock) {
- if (!mEnabled && !isActiveLocked()) {
- return;
- }
+ commitLocked();
+ }
+ }
- finishSessionLocked();
+ private void commitLocked() {
+ if (!mEnabled && !isActiveLocked()) {
+ return;
}
+ finishSessionLocked();
}
/**
@@ -874,12 +931,15 @@ public final class AutofillManager {
return;
}
synchronized (mLock) {
- if (!mEnabled && !isActiveLocked()) {
- return;
- }
+ cancelLocked();
+ }
+ }
- cancelSessionLocked();
+ private void cancelLocked() {
+ if (!mEnabled && !isActiveLocked()) {
+ return;
}
+ cancelSessionLocked();
}
/** @hide */
@@ -937,7 +997,12 @@ public final class AutofillManager {
}
private AutofillClient getClientLocked() {
- return mContext.getAutofillClient();
+ final AutofillClient client = mContext.getAutofillClient();
+ if (client == null && sDebug) {
+ Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
+ + mContext);
+ }
+ return client;
}
/** @hide */
@@ -959,6 +1024,10 @@ public final class AutofillManager {
final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
final Bundle responseData = new Bundle();
responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+ final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
+ if (newClientState != null) {
+ responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
+ }
try {
mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
mContext.getUserId());
@@ -1038,6 +1107,7 @@ public final class AutofillManager {
mState = STATE_UNKNOWN;
mTrackedViews = null;
mFillableIds = null;
+ mSaveTriggerId = null;
}
private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
@@ -1289,12 +1359,15 @@ public final class AutofillManager {
/**
* Set the tracked views.
*
- * @param trackedIds The views to be tracked
+ * @param trackedIds The views to be tracked.
* @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
+ * @param saveOnFinish Finish the session once the activity is finished.
* @param fillableIds Views that might anchor FillUI.
+ * @param saveTriggerId View that when clicked triggers commit().
*/
private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
- boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
+ boolean saveOnAllViewsInvisible, boolean saveOnFinish,
+ @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
synchronized (mLock) {
if (mEnabled && mSessionId == sessionId) {
if (saveOnAllViewsInvisible) {
@@ -1302,6 +1375,7 @@ public final class AutofillManager {
} else {
mTrackedViews = null;
}
+ mSaveOnFinish = saveOnFinish;
if (fillableIds != null) {
if (mFillableIds == null) {
mFillableIds = new ArraySet<>(fillableIds.length);
@@ -1314,10 +1388,30 @@ public final class AutofillManager {
+ ", mFillableIds" + mFillableIds);
}
}
+
+ if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) {
+ // Turn off trigger on previous view id.
+ setNotifyOnClickLocked(mSaveTriggerId, false);
+ }
+
+ if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) {
+ // Turn on trigger on new view id.
+ mSaveTriggerId = saveTriggerId;
+ setNotifyOnClickLocked(mSaveTriggerId, true);
+ }
}
}
}
+ private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) {
+ final View view = findView(id);
+ if (view == null) {
+ Log.w(TAG, "setNotifyOnClick(): invalid id: " + id);
+ return;
+ }
+ view.setNotifyAutofillManagerOnClick(notify);
+ }
+
private void setSaveUiState(int sessionId, boolean shown) {
if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
synchronized (mLock) {
@@ -1490,6 +1584,7 @@ public final class AutofillManager {
final String pfx = outerPrefix + " ";
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
+ pw.print(pfx); pw.print("context: "); pw.println(mContext);
pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1504,6 +1599,8 @@ public final class AutofillManager {
pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
}
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
+ pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
+ pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
}
private String getStateAsStringLocked() {
@@ -1752,7 +1849,7 @@ public final class AutofillManager {
* Callback for autofill related events.
*
* <p>Typically used for applications that display their own "auto-complete" views, so they can
- * enable / disable such views when the autofill UI affordance is shown / hidden.
+ * enable / disable such views when the autofill UI is shown / hidden.
*/
public abstract static class AutofillCallback {
@@ -1762,26 +1859,26 @@ public final class AutofillManager {
public @interface AutofillEventType {}
/**
- * The autofill input UI affordance associated with the view was shown.
+ * The autofill input UI associated with the view was shown.
*
- * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
+ * <p>If the view provides its own auto-complete UI and its currently shown, it
* should be hidden upon receiving this event.
*/
public static final int EVENT_INPUT_SHOWN = 1;
/**
- * The autofill input UI affordance associated with the view was hidden.
+ * The autofill input UI associated with the view was hidden.
*
- * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
+ * <p>If the view provides its own auto-complete UI that was hidden upon a
* {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
*/
public static final int EVENT_INPUT_HIDDEN = 2;
/**
- * The autofill input UI affordance associated with the view isn't shown because
+ * The autofill input UI associated with the view isn't shown because
* autofill is not available.
*
- * <p>If the view provides its own auto-complete UI affordance but was not displaying it
+ * <p>If the view provides its own auto-complete UI but was not displaying it
* to avoid flickering, it could shown it upon receiving this event.
*/
public static final int EVENT_INPUT_UNAVAILABLE = 3;
@@ -1883,12 +1980,12 @@ public final class AutofillManager {
@Override
public void setTrackedViews(int sessionId, AutofillId[] ids,
- boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
+ boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
+ AutofillId saveTriggerId) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() ->
- afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
- );
+ afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
+ saveOnFinish, fillableIds, saveTriggerId));
}
}
diff --git a/android/view/inputmethod/InputMethod.java b/android/view/inputmethod/InputMethod.java
index 0922422c..ab8886bb 100644
--- a/android/view/inputmethod/InputMethod.java
+++ b/android/view/inputmethod/InputMethod.java
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -90,8 +91,9 @@ public interface InputMethod {
* accept the first token given to you. Any after that may come from the
* client.
*/
+ @MainThread
public void attachToken(IBinder token);
-
+
/**
* Bind a new application environment in to the input method, so that it
* can later start and stop input processing.
@@ -104,6 +106,7 @@ public interface InputMethod {
* @see InputBinding
* @see #unbindInput()
*/
+ @MainThread
public void bindInput(InputBinding binding);
/**
@@ -114,6 +117,7 @@ public interface InputMethod {
* Typically this method is called when the application changes to be
* non-foreground.
*/
+ @MainThread
public void unbindInput();
/**
@@ -129,6 +133,7 @@ public interface InputMethod {
*
* @see EditorInfo
*/
+ @MainThread
public void startInput(InputConnection inputConnection, EditorInfo info);
/**
@@ -147,6 +152,7 @@ public interface InputMethod {
*
* @see EditorInfo
*/
+ @MainThread
public void restartInput(InputConnection inputConnection, EditorInfo attribute);
/**
@@ -177,6 +183,7 @@ public interface InputMethod {
* @see EditorInfo
* @hide
*/
+ @MainThread
default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
@NonNull IBinder startInputToken) {
@@ -195,6 +202,7 @@ public interface InputMethod {
*
* @param callback Interface that is called with the newly created session.
*/
+ @MainThread
public void createSession(SessionCallback callback);
/**
@@ -203,6 +211,7 @@ public interface InputMethod {
* @param session The {@link InputMethodSession} previously provided through
* SessionCallback.sessionCreated() that is to be changed.
*/
+ @MainThread
public void setSessionEnabled(InputMethodSession session, boolean enabled);
/**
@@ -214,6 +223,7 @@ public interface InputMethod {
* @param session The {@link InputMethodSession} previously provided through
* SessionCallback.sessionCreated() that is to be revoked.
*/
+ @MainThread
public void revokeSession(InputMethodSession session);
/**
@@ -244,6 +254,7 @@ public interface InputMethod {
* {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
* {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
+ @MainThread
public void showSoftInput(int flags, ResultReceiver resultReceiver);
/**
@@ -258,11 +269,13 @@ public interface InputMethod {
* {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
* {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
+ @MainThread
public void hideSoftInput(int flags, ResultReceiver resultReceiver);
/**
* Notify that the input method subtype is being changed in the same input method.
* @param subtype New subtype of the notified input method
*/
+ @MainThread
public void changeInputMethodSubtype(InputMethodSubtype subtype);
}
diff --git a/android/view/textclassifier/Log.java b/android/view/textclassifier/Log.java
new file mode 100644
index 00000000..83ca15df
--- /dev/null
+++ b/android/view/textclassifier/Log.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.util.Slog;
+
+/**
+ * Logging for android.view.textclassifier package.
+ */
+final class Log {
+
+ /**
+ * true: Enables full logging.
+ * false: Limits logging to debug level.
+ */
+ private static final boolean ENABLE_FULL_LOGGING = false;
+
+ private Log() {}
+
+ public static void d(String tag, String msg) {
+ Slog.d(tag, msg);
+ }
+
+ public static void e(String tag, String msg, Throwable tr) {
+ if (ENABLE_FULL_LOGGING) {
+ Slog.e(tag, msg, tr);
+ } else {
+ final String trString = (tr != null) ? tr.getClass().getSimpleName() : "??";
+ Slog.d(tag, String.format("%s (%s)", msg, trString));
+ }
+ }
+}
diff --git a/android/view/textclassifier/SmartSelection.java b/android/view/textclassifier/SmartSelection.java
index f0e83d1f..2c93a19b 100644
--- a/android/view/textclassifier/SmartSelection.java
+++ b/android/view/textclassifier/SmartSelection.java
@@ -16,6 +16,8 @@
package android.view.textclassifier;
+import android.content.res.AssetFileDescriptor;
+
/**
* Java wrapper for SmartSelection native library interface.
* This library is used for detecting entities in text.
@@ -42,6 +44,26 @@ final class SmartSelection {
}
/**
+ * Creates a new instance of SmartSelect predictor, using the provided model image, given as a
+ * file path.
+ */
+ SmartSelection(String path) {
+ mCtx = nativeNewFromPath(path);
+ }
+
+ /**
+ * Creates a new instance of SmartSelect predictor, using the provided model image, given as an
+ * AssetFileDescriptor.
+ */
+ SmartSelection(AssetFileDescriptor afd) {
+ mCtx = nativeNewFromAssetFileDescriptor(afd, afd.getStartOffset(), afd.getLength());
+ if (mCtx == 0L) {
+ throw new IllegalArgumentException(
+ "Couldn't initialize TC from given AssetFileDescriptor");
+ }
+ }
+
+ /**
* Given a string context and current selection, computes the SmartSelection suggestion.
*
* The begin and end are character indices into the context UTF8 string. selectionBegin is the
@@ -69,6 +91,15 @@ final class SmartSelection {
}
/**
+ * Annotates given input text. Every word of the input is a part of some annotation.
+ * The annotations are sorted by their position in the context string.
+ * The annotations do not overlap.
+ */
+ public AnnotatedSpan[] annotate(String text) {
+ return nativeAnnotate(mCtx, text);
+ }
+
+ /**
* Frees up the allocated memory.
*/
public void close() {
@@ -91,12 +122,19 @@ final class SmartSelection {
private static native long nativeNew(int fd);
+ private static native long nativeNewFromPath(String path);
+
+ private static native long nativeNewFromAssetFileDescriptor(AssetFileDescriptor afd,
+ long offset, long size);
+
private static native int[] nativeSuggest(
long context, String text, int selectionBegin, int selectionEnd);
private static native ClassificationResult[] nativeClassifyText(
long context, String text, int selectionBegin, int selectionEnd, int hintFlags);
+ private static native AnnotatedSpan[] nativeAnnotate(long context, String text);
+
private static native void nativeClose(long context);
private static native String nativeGetLanguage(int fd);
@@ -114,4 +152,29 @@ final class SmartSelection {
mScore = score;
}
}
+
+ /** Represents a result of Annotate call. */
+ public static final class AnnotatedSpan {
+ final int mStartIndex;
+ final int mEndIndex;
+ final ClassificationResult[] mClassification;
+
+ AnnotatedSpan(int startIndex, int endIndex, ClassificationResult[] classification) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mClassification = classification;
+ }
+
+ public int getStartIndex() {
+ return mStartIndex;
+ }
+
+ public int getEndIndex() {
+ return mEndIndex;
+ }
+
+ public ClassificationResult[] getClassification() {
+ return mClassification;
+ }
+ }
}
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index bb1e693f..c3601d9d 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -152,4 +152,12 @@ public interface TextClassifier {
*/
@WorkerThread
default void logEvent(String source, String event) {}
+
+ /**
+ * Returns this TextClassifier's settings.
+ * @hide
+ */
+ default TextClassifierConstants getSettings() {
+ return TextClassifierConstants.DEFAULT;
+ }
}
diff --git a/android/view/textclassifier/TextClassifierConstants.java b/android/view/textclassifier/TextClassifierConstants.java
new file mode 100644
index 00000000..51e6168e
--- /dev/null
+++ b/android/view/textclassifier/TextClassifierConstants.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.Nullable;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+/**
+ * TextClassifier specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * <pre>
+ * smart_selection_dark_launch (boolean)
+ * smart_selection_enabled_for_edit_text (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
+ *
+ * Example of setting the values for testing.
+ * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true
+ * @hide
+ */
+public final class TextClassifierConstants {
+
+ private static final String LOG_TAG = "TextClassifierConstants";
+
+ private static final String SMART_SELECTION_DARK_LAUNCH =
+ "smart_selection_dark_launch";
+ private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT =
+ "smart_selection_enabled_for_edit_text";
+
+ private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
+ private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
+
+ /** Default settings. */
+ static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
+
+ private final boolean mDarkLaunch;
+ private final boolean mSuggestSelectionEnabledForEditableText;
+
+ private TextClassifierConstants() {
+ mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
+ mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT;
+ }
+
+ private TextClassifierConstants(@Nullable String settings) {
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(settings);
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on with defaults.
+ Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings);
+ }
+ mDarkLaunch = parser.getBoolean(
+ SMART_SELECTION_DARK_LAUNCH,
+ SMART_SELECTION_DARK_LAUNCH_DEFAULT);
+ mSuggestSelectionEnabledForEditableText = parser.getBoolean(
+ SMART_SELECTION_ENABLED_FOR_EDIT_TEXT,
+ SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT);
+ }
+
+ static TextClassifierConstants loadFromString(String settings) {
+ return new TextClassifierConstants(settings);
+ }
+
+ public boolean isDarkLaunch() {
+ return mDarkLaunch;
+ }
+
+ public boolean isSuggestSelectionEnabledForEditableText() {
+ return mSuggestSelectionEnabledForEditableText;
+ }
+}
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 2aa81a2c..1c07be4b 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -24,18 +24,17 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.icu.text.BreakIterator;
import android.net.Uri;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.ContactsContract;
+import android.provider.Settings;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.method.WordIterator;
import android.text.style.ClickableSpan;
import android.text.util.Linkify;
-import android.util.Log;
import android.util.Patterns;
import android.view.View;
import android.widget.TextViewMetrics;
@@ -47,6 +46,7 @@ import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -91,6 +91,8 @@ final class TextClassifierImpl implements TextClassifier {
@GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
private SmartSelection mSmartSelection;
+ private TextClassifierConstants mSettings;
+
TextClassifierImpl(Context context) {
mContext = Preconditions.checkNotNull(context);
}
@@ -160,7 +162,7 @@ final class TextClassifierImpl implements TextClassifier {
}
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
- Log.e(LOG_TAG, "Error getting assist info.", t);
+ 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(
@@ -189,6 +191,15 @@ final class TextClassifierImpl implements TextClassifier {
}
}
+ @Override
+ public TextClassifierConstants getSettings() {
+ if (mSettings == null) {
+ mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString(
+ mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
+ }
+ return mSettings;
+ }
+
private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
synchronized (mSmartSelectionLock) {
localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index f368c74a..8e1f2183 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,213 +1,58 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package android.view.textservice;
-import android.annotation.SystemService;
-import android.content.Context;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.Log;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
-import com.android.internal.textservice.ITextServicesManager;
-
import java.util.Locale;
/**
- * System API to the overall text services, which arbitrates interaction between applications
- * and text services.
- *
- * The user can change the current text services in Settings. And also applications can specify
- * the target text services.
- *
- * <h3>Architecture Overview</h3>
- *
- * <p>There are three primary parties involved in the text services
- * framework (TSF) architecture:</p>
- *
- * <ul>
- * <li> The <strong>text services manager</strong> as expressed by this class
- * is the central point of the system that manages interaction between all
- * other parts. It is expressed as the client-side API here which exists
- * in each application context and communicates with a global system service
- * that manages the interaction across all processes.
- * <li> A <strong>text service</strong> implements a particular
- * interaction model allowing the client application to retrieve information of text.
- * The system binds to the current text service that is in use, causing it to be created and run.
- * <li> Multiple <strong>client applications</strong> arbitrate with the text service
- * manager for connections to text services.
- * </ul>
- *
- * <h3>Text services sessions</h3>
- * <ul>
- * <li>The <strong>spell checker session</strong> is one of the text services.
- * {@link android.view.textservice.SpellCheckerSession}</li>
- * </ul>
- *
+ * A stub class of TextServicesManager for Layout-Lib.
*/
-@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
public final class TextServicesManager {
- private static final String TAG = TextServicesManager.class.getSimpleName();
- private static final boolean DBG = false;
-
- private static TextServicesManager sInstance;
-
- private final ITextServicesManager mService;
-
- private TextServicesManager() throws ServiceNotFoundException {
- mService = ITextServicesManager.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
- }
+ private static final TextServicesManager sInstance = new TextServicesManager();
+ private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
/**
* Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
* @hide
*/
public static TextServicesManager getInstance() {
- synchronized (TextServicesManager.class) {
- if (sInstance == null) {
- try {
- sInstance = new TextServicesManager();
- } catch (ServiceNotFoundException e) {
- throw new IllegalStateException(e);
- }
- }
- return sInstance;
- }
- }
-
- /**
- * Returns the language component of a given locale string.
- */
- private static String parseLanguageFromLocaleString(String locale) {
- final int idx = locale.indexOf('_');
- if (idx < 0) {
- return locale;
- } else {
- return locale.substring(0, idx);
- }
+ return sInstance;
}
- /**
- * Get a spell checker session for the specified spell checker
- * @param locale the locale for the spell checker. If {@code locale} is null and
- * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
- * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
- * the locale specified in Settings will be returned only when it is same as {@code locale}.
- * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
- * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
- * selected.
- * @param listener a spell checker session lister for getting results from a spell checker.
- * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
- * languages in settings will be returned.
- * @return the spell checker session of the spell checker
- */
public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
- if (listener == null) {
- throw new NullPointerException();
- }
- if (!referToSpellCheckerLanguageSettings && locale == null) {
- throw new IllegalArgumentException("Locale should not be null if you don't refer"
- + " settings.");
- }
-
- if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
- return null;
- }
-
- final SpellCheckerInfo sci;
- try {
- sci = mService.getCurrentSpellChecker(null);
- } catch (RemoteException e) {
- return null;
- }
- if (sci == null) {
- return null;
- }
- SpellCheckerSubtype subtypeInUse = null;
- if (referToSpellCheckerLanguageSettings) {
- subtypeInUse = getCurrentSpellCheckerSubtype(true);
- if (subtypeInUse == null) {
- return null;
- }
- if (locale != null) {
- final String subtypeLocale = subtypeInUse.getLocale();
- final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
- if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
- return null;
- }
- }
- } else {
- final String localeStr = locale.toString();
- for (int i = 0; i < sci.getSubtypeCount(); ++i) {
- final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
- final String tempSubtypeLocale = subtype.getLocale();
- final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
- if (tempSubtypeLocale.equals(localeStr)) {
- subtypeInUse = subtype;
- break;
- } else if (tempSubtypeLanguage.length() >= 2 &&
- locale.getLanguage().equals(tempSubtypeLanguage)) {
- subtypeInUse = subtype;
- }
- }
- }
- if (subtypeInUse == null) {
- return null;
- }
- final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
- try {
- mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
- session.getTextServicesSessionListener(),
- session.getSpellCheckerSessionListener(), bundle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return session;
+ return null;
}
/**
* @hide
*/
public SpellCheckerInfo[] getEnabledSpellCheckers() {
- try {
- final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
- if (DBG) {
- Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
- }
- return retval;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return EMPTY_SPELL_CHECKER_INFO;
}
/**
* @hide
*/
public SpellCheckerInfo getCurrentSpellChecker() {
- try {
- // Passing null as a locale for ICS
- return mService.getCurrentSpellChecker(null);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return null;
}
/**
@@ -215,22 +60,13 @@ public final class TextServicesManager {
*/
public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
boolean allowImplicitlySelectedSubtype) {
- try {
- // Passing null as a locale until we support multiple enabled spell checker subtypes.
- return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return null;
}
/**
* @hide
*/
public boolean isSpellCheckerEnabled() {
- try {
- return mService.isSpellCheckerEnabled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return false;
}
}
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index dfc81b2b..202f2046 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,3001 +16,223 @@
package android.webkit;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.Widget;
+import com.android.layoutlib.bridge.MockView;
+
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Picture;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
-import android.os.StrictMode;
-import android.print.PrintDocumentAdapter;
-import android.security.KeyChain;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.DragEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ViewHierarchyEncoder;
-import android.view.ViewStructure;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillValue;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.textclassifier.TextClassifier;
-import android.widget.AbsoluteLayout;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
/**
- * <p>A View that displays web pages. This class is the basis upon which you
- * can roll your own web browser or simply display some online content within your Activity.
- * It uses the WebKit rendering engine to display
- * web pages and includes methods to navigate forward and backward
- * through a history, zoom in and out, perform text searches and more.
- *
- * <p>Note that, in order for your Activity to access the Internet and load web pages
- * in a WebView, you must add the {@code INTERNET} permissions to your
- * Android Manifest file:
- *
- * <pre>
- * {@code <uses-permission android:name="android.permission.INTERNET" />}
- * </pre>
- *
- * <p>This must be a child of the <a
- * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
- * element.
- *
- * <p>For more information, read
- * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>.
- *
- * <h3>Basic usage</h3>
- *
- * <p>By default, a WebView provides no browser-like widgets, does not
- * enable JavaScript and web page errors are ignored. If your goal is only
- * to display some HTML as a part of your UI, this is probably fine;
- * the user won't need to interact with the web page beyond reading
- * it, and the web page won't need to interact with the user. If you
- * actually want a full-blown web browser, then you probably want to
- * invoke the Browser application with a URL Intent rather than show it
- * with a WebView. For example:
- * <pre>
- * Uri uri = Uri.parse("https://www.example.com");
- * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- * startActivity(intent);
- * </pre>
- * <p>See {@link android.content.Intent} for more information.
- *
- * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
- * or set the entire Activity window as a WebView during {@link
- * android.app.Activity#onCreate(Bundle) onCreate()}:
- *
- * <pre class="prettyprint">
- * WebView webview = new WebView(this);
- * setContentView(webview);
- * </pre>
- *
- * <p>Then load the desired web page:
- *
- * <pre>
- * // Simplest usage: note that an exception will NOT be thrown
- * // if there is an error loading this page (see below).
- * webview.loadUrl("https://example.com/");
- *
- * // OR, you can also load from an HTML string:
- * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
- * webview.loadData(summary, "text/html", null);
- * // ... although note that there are restrictions on what this HTML can do.
- * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
- * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
- * </pre>
- *
- * <p>A WebView has several customization points where you can add your
- * own behavior. These are:
- *
- * <ul>
- * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
- * This class is called when something that might impact a
- * browser UI happens, for instance, progress updates and
- * JavaScript alerts are sent here (see <a
- * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
- * </li>
- * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
- * It will be called when things happen that impact the
- * rendering of the content, eg, errors or form submissions. You
- * can also intercept URL loading here (via {@link
- * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
- * shouldOverrideUrlLoading()}).</li>
- * <li>Modifying the {@link android.webkit.WebSettings}, such as
- * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
- * setJavaScriptEnabled()}. </li>
- * <li>Injecting Java objects into the WebView using the
- * {@link android.webkit.WebView#addJavascriptInterface} method. This
- * method allows you to inject Java objects into a page's JavaScript
- * context, so that they can be accessed by JavaScript in the page.</li>
- * </ul>
- *
- * <p>Here's a more complicated example, showing error handling,
- * settings, and progress notification:
- *
- * <pre class="prettyprint">
- * // Let's display the progress in the activity title bar, like the
- * // browser app does.
- * getWindow().requestFeature(Window.FEATURE_PROGRESS);
- *
- * webview.getSettings().setJavaScriptEnabled(true);
- *
- * final Activity activity = this;
- * webview.setWebChromeClient(new WebChromeClient() {
- * public void onProgressChanged(WebView view, int progress) {
- * // Activities and WebViews measure progress with different scales.
- * // The progress meter will automatically disappear when we reach 100%
- * activity.setProgress(progress * 1000);
- * }
- * });
- * webview.setWebViewClient(new WebViewClient() {
- * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
- * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
- * }
- * });
- *
- * webview.loadUrl("https://developer.android.com/");
- * </pre>
- *
- * <h3>Zoom</h3>
- *
- * <p>To enable the built-in zoom, set
- * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
- * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
- *
- * <p>NOTE: Using zoom if either the height or width is set to
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
- * and should be avoided.
- *
- * <h3>Cookie and window management</h3>
- *
- * <p>For obvious security reasons, your application has its own
- * cache, cookie store etc.&mdash;it does not share the Browser
- * application's data.
- *
- * <p>By default, requests by the HTML to open new windows are
- * ignored. This is {@code true} whether they be opened by JavaScript or by
- * the target attribute on a link. You can customize your
- * {@link WebChromeClient} to provide your own behavior for opening multiple windows,
- * and render them in whatever manner you want.
- *
- * <p>The standard behavior for an Activity is to be destroyed and
- * recreated when the device orientation or any other configuration changes. This will cause
- * the WebView to reload the current page. If you don't want that, you
- * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
- * changes, and then just leave the WebView alone. It'll automatically
- * re-orient itself as appropriate. Read <a
- * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
- * more information about how to handle configuration changes during runtime.
- *
- *
- * <h3>Building web pages to support different screen densities</h3>
- *
- * <p>The screen density of a device is based on the screen resolution. A screen with low density
- * has fewer available pixels per inch, where a screen with high density
- * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
- * screen is important because, other things being equal, a UI element (such as a button) whose
- * height and width are defined in terms of screen pixels will appear larger on the lower density
- * screen and smaller on the higher density screen.
- * For simplicity, Android collapses all actual screen densities into three generalized densities:
- * high, medium, and low.
- * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
- * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
- * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
- * are bigger).
- * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS,
- * and meta tag features to help you (as a web developer) target screens with different screen
- * densities.
- * <p>Here's a summary of the features you can use to handle different screen densities:
- * <ul>
- * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
- * default scaling factor used for the current device. For example, if the value of {@code
- * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
- * and default scaling is not applied to the web page; if the value is "1.5", then the device is
- * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
- * value is "0.75", then the device is considered a low density device (ldpi) and the content is
- * scaled 0.75x.</li>
- * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
- * densities for which this style sheet is to be used. The corresponding value should be either
- * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
- * density, or high density screens, respectively. For example:
- * <pre>
- * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
- * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
- * which is the high density pixel ratio.
- * </li>
- * </ul>
- *
- * <h3>HTML5 Video support</h3>
- *
- * <p>In order to support inline HTML5 video in your application you need to have hardware
- * acceleration turned on.
- *
- * <h3>Full screen support</h3>
- *
- * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
- * {@link android.webkit.WebChromeClient} and implement both
- * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
- * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
- * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
- * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
- * is loading.
- *
- * <h3>HTML5 Geolocation API support</h3>
- *
- * <p>For applications targeting Android N and later releases
- * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on
- * secure origins such as https. For such applications requests to geolocation api on non-secure
- * origins are automatically denied without invoking the corresponding
- * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
- * method.
- *
- * <h3>Layout size</h3>
- * <p>
- * It is recommended to set the WebView layout height to a fixed value or to
- * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
- * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
- * for the height none of the WebView's parents should use a
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
- * incorrect sizing of the views.
- *
- * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
- * enables the following behaviors:
- * <ul>
- * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
- * relative to the HTML body may not be sized correctly. </li>
- * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
- * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
- * </ul>
- *
- * <p>
- * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
- * supported. If such a width is used the WebView will attempt to use the width of the parent
- * instead.
- *
- * <h3>Metrics</h3>
- *
- * <p>
- * WebView may upload anonymous diagnostic data to Google when the user has consented. This data
- * helps Google improve WebView. Data is collected on a per-app basis for each app which has
- * instantiated a WebView. An individual app can opt out of this feature by putting the following
- * tag in its manifest:
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- * android:value="true" /&gt;
- * </pre>
- * <p>
- * Data will only be uploaded for a given app if the user has consented AND the app has not opted
- * out.
- *
- * <h3>Safe Browsing</h3>
- *
- * <p>
- * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
- * user to allow them to navigate back safely or proceed to the malicious page.
- * <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest:
- * <p>
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- * android:value="true" /&gt;
- * </pre>
+ * Mock version of the WebView.
+ * Only non override public methods from the real WebView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
*
*/
-// Implementation notes.
-// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
-// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
-// Methods are delegated to the provider implementation: all public API methods introduced in this
-// file are fully delegated, whereas public and protected methods from the View base classes are
-// only delegated where a specific need exists for them to do so.
-@Widget
-public class WebView extends AbsoluteLayout
- implements ViewTreeObserver.OnGlobalFocusChangeListener,
- ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
-
- private static final String LOGTAG = "WebView";
-
- // Throwing an exception for incorrect thread usage if the
- // build target is JB MR2 or newer. Defaults to false, and is
- // set in the WebView constructor.
- private static volatile boolean sEnforceThreadChecking = false;
-
- /**
- * Transportation object for returning WebView across thread boundaries.
- */
- public class WebViewTransport {
- private WebView mWebview;
+public class WebView extends MockView {
- /**
- * Sets the WebView to the transportation object.
- *
- * @param webview the WebView to transport
- */
- public synchronized void setWebView(WebView webview) {
- mWebview = webview;
- }
-
- /**
- * Gets the WebView object.
- *
- * @return the transported WebView object
- */
- public synchronized WebView getWebView() {
- return mWebview;
- }
- }
-
- /**
- * URI scheme for telephone number.
- */
- public static final String SCHEME_TEL = "tel:";
/**
- * URI scheme for email address.
- */
- public static final String SCHEME_MAILTO = "mailto:";
- /**
- * URI scheme for map address.
- */
- public static final String SCHEME_GEO = "geo:0,0?q=";
-
- /**
- * Interface to listen for find results.
- */
- public interface FindListener {
- /**
- * Notifies the listener about progress made by a find operation.
- *
- * @param activeMatchOrdinal the zero-based ordinal of the currently selected match
- * @param numberOfMatches how many matches have been found
- * @param isDoneCounting whether the find operation has actually completed. The listener
- * may be notified multiple times while the
- * operation is underway, and the numberOfMatches
- * value should not be considered final unless
- * isDoneCounting is {@code true}.
- */
- public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
- boolean isDoneCounting);
- }
-
- /**
- * Callback interface supplied to {@link #postVisualStateCallback} for receiving
- * notifications about the visual state.
- */
- public static abstract class VisualStateCallback {
- /**
- * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
- *
- * @param requestId The identifier passed to {@link #postVisualStateCallback} when this
- * callback was posted.
- */
- public abstract void onComplete(long requestId);
- }
-
- /**
- * Interface to listen for new pictures as they change.
- *
- * @deprecated This interface is now obsolete.
- */
- @Deprecated
- public interface PictureListener {
- /**
- * Used to provide notification that the WebView's picture has changed.
- * See {@link WebView#capturePicture} for details of the picture.
- *
- * @param view the WebView that owns the picture
- * @param picture the new picture. Applications targeting
- * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
- * will always receive a {@code null} Picture.
- * @deprecated Deprecated due to internal changes.
- */
- @Deprecated
- void onNewPicture(WebView view, @Nullable Picture picture);
- }
-
- public static class HitTestResult {
- /**
- * Default HitTestResult, where the target is unknown.
- */
- public static final int UNKNOWN_TYPE = 0;
- /**
- * @deprecated This type is no longer used.
- */
- @Deprecated
- public static final int ANCHOR_TYPE = 1;
- /**
- * HitTestResult for hitting a phone number.
- */
- public static final int PHONE_TYPE = 2;
- /**
- * HitTestResult for hitting a map address.
- */
- public static final int GEO_TYPE = 3;
- /**
- * HitTestResult for hitting an email address.
- */
- public static final int EMAIL_TYPE = 4;
- /**
- * HitTestResult for hitting an HTML::img tag.
- */
- public static final int IMAGE_TYPE = 5;
- /**
- * @deprecated This type is no longer used.
- */
- @Deprecated
- public static final int IMAGE_ANCHOR_TYPE = 6;
- /**
- * HitTestResult for hitting a HTML::a tag with src=http.
- */
- public static final int SRC_ANCHOR_TYPE = 7;
- /**
- * HitTestResult for hitting a HTML::a tag with src=http + HTML::img.
- */
- public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
- /**
- * HitTestResult for hitting an edit text area.
- */
- public static final int EDIT_TEXT_TYPE = 9;
-
- private int mType;
- private String mExtra;
-
- /**
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public HitTestResult() {
- mType = UNKNOWN_TYPE;
- }
-
- /**
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public void setType(int type) {
- mType = type;
- }
-
- /**
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public void setExtra(String extra) {
- mExtra = extra;
- }
-
- /**
- * Gets the type of the hit test result. See the XXX_TYPE constants
- * defined in this class.
- *
- * @return the type of the hit test result
- */
- public int getType() {
- return mType;
- }
-
- /**
- * Gets additional type-dependant information about the result. See
- * {@link WebView#getHitTestResult()} for details. May either be {@code null}
- * or contain extra information about this result.
- *
- * @return additional type-dependant information about the result
- */
- @Nullable
- public String getExtra() {
- return mExtra;
- }
- }
-
- /**
- * Constructs a new WebView with a Context object.
- *
- * @param context a Context object used to access application assets
+ * Construct a new WebView with a Context object.
+ * @param context A Context object used to access application assets.
*/
public WebView(Context context) {
this(context, null);
}
/**
- * Constructs a new WebView with layout parameters.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
+ * Construct a new WebView with layout parameters.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
*/
public WebView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
/**
- * Constructs a new WebView with layout parameters and a default style.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
+ * Construct a new WebView with layout parameters and a default style.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ * @param defStyle The default style resource ID.
*/
- public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ public WebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
}
-
- /**
- * Constructs a new WebView with layout parameters and a default style.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
- * @param defStyleRes a resource identifier of a style resource that
- * supplies default values for the view, used only if
- * defStyleAttr is 0 or can not be found in the theme. Can be 0
- * to not look for defaults.
- */
- public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- this(context, attrs, defStyleAttr, defStyleRes, null, false);
- }
-
- /**
- * Constructs a new WebView with layout parameters and a default style.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
- * @param privateBrowsing whether this WebView will be initialized in
- * private mode
- *
- * @deprecated Private browsing is no longer supported directly via
- * WebView and will be removed in a future release. Prefer using
- * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
- * and {@link WebStorage} for fine-grained control of privacy data.
- */
- @Deprecated
- public WebView(Context context, AttributeSet attrs, int defStyleAttr,
- boolean privateBrowsing) {
- this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
- }
-
- /**
- * Constructs a new WebView with layout parameters, a default style and a set
- * of custom JavaScript interfaces to be added to this WebView at initialization
- * time. This guarantees that these interfaces will be available when the JS
- * context is initialized.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
- * @param javaScriptInterfaces a Map of interface names, as keys, and
- * object implementing those interfaces, as
- * values
- * @param privateBrowsing whether this WebView will be initialized in
- * private mode
- * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to
- * be added synchronously, before a subsequent loadUrl call takes effect.
- */
- protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
- Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
- this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
- }
-
- /**
- * @hide
- */
- @SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.
- protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
- Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
- super(context, attrs, defStyleAttr, defStyleRes);
-
- // WebView is important by default, unless app developer overrode attribute.
- if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
- setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
- }
-
- if (context == null) {
- throw new IllegalArgumentException("Invalid context argument");
- }
- sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
- Build.VERSION_CODES.JELLY_BEAN_MR2;
- checkThread();
-
- ensureProviderCreated();
- mProvider.init(javaScriptInterfaces, privateBrowsing);
- // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
- CookieSyncManager.setGetInstanceIsAllowed();
- }
-
- /**
- * Specifies whether the horizontal scrollbar has overlay style.
- *
- * @deprecated This method has no effect.
- * @param overlay {@code true} if horizontal scrollbar should have overlay style
- */
- @Deprecated
+
+ // START FAKE PUBLIC METHODS
+
public void setHorizontalScrollbarOverlay(boolean overlay) {
}
- /**
- * Specifies whether the vertical scrollbar has overlay style.
- *
- * @deprecated This method has no effect.
- * @param overlay {@code true} if vertical scrollbar should have overlay style
- */
- @Deprecated
public void setVerticalScrollbarOverlay(boolean overlay) {
}
- /**
- * Gets whether horizontal scrollbar has overlay style.
- *
- * @deprecated This method is now obsolete.
- * @return {@code true}
- */
- @Deprecated
public boolean overlayHorizontalScrollbar() {
- // The old implementation defaulted to true, so return true for consistency
- return true;
+ return false;
}
- /**
- * Gets whether vertical scrollbar has overlay style.
- *
- * @deprecated This method is now obsolete.
- * @return {@code false}
- */
- @Deprecated
public boolean overlayVerticalScrollbar() {
- // The old implementation defaulted to false, so return false for consistency
return false;
}
- /**
- * Gets the visible height (in pixels) of the embedded title bar (if any).
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public int getVisibleTitleHeight() {
- checkThread();
- return mProvider.getVisibleTitleHeight();
- }
-
- /**
- * Gets the SSL certificate for the main top-level page or {@code null} if there is
- * no certificate (the site is not secure).
- *
- * @return the SSL certificate for the main top-level page
- */
- @Nullable
- public SslCertificate getCertificate() {
- checkThread();
- return mProvider.getCertificate();
- }
-
- /**
- * Sets the SSL certificate for the main top-level page.
- *
- * @deprecated Calling this function has no useful effect, and will be
- * ignored in future releases.
- */
- @Deprecated
- public void setCertificate(SslCertificate certificate) {
- checkThread();
- mProvider.setCertificate(certificate);
- }
-
- //-------------------------------------------------------------------------
- // Methods called by activity
- //-------------------------------------------------------------------------
-
- /**
- * Sets a username and password pair for the specified host. This data is
- * used by the WebView to autocomplete username and password fields in web
- * forms. Note that this is unrelated to the credentials used for HTTP
- * authentication.
- *
- * @param host the host that required the credentials
- * @param username the username for the given host
- * @param password the password for the given host
- * @see WebViewDatabase#clearUsernamePassword
- * @see WebViewDatabase#hasUsernamePassword
- * @deprecated Saving passwords in WebView will not be supported in future versions.
- */
- @Deprecated
public void savePassword(String host, String username, String password) {
- checkThread();
- mProvider.savePassword(host, username, password);
}
- /**
- * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase}
- * instance.
- *
- * @param host the host to which the credentials apply
- * @param realm the realm to which the credentials apply
- * @param username the username
- * @param password the password
- * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead
- */
- @Deprecated
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
- checkThread();
- mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
}
- /**
- * Retrieves HTTP authentication credentials for a given host and realm from the {@link
- * WebViewDatabase} instance.
- * @param host the host to which the credentials apply
- * @param realm the realm to which the credentials apply
- * @return the credentials as a String array, if found. The first element
- * is the username and the second element is the password. {@code null} if
- * no credentials are found.
- * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
- */
- @Deprecated
- @Nullable
public String[] getHttpAuthUsernamePassword(String host, String realm) {
- checkThread();
- return mProvider.getHttpAuthUsernamePassword(host, realm);
+ return null;
}
- /**
- * Destroys the internal state of this WebView. This method should be called
- * after this WebView has been removed from the view system. No other
- * methods may be called on this WebView after destroy.
- */
public void destroy() {
- checkThread();
- mProvider.destroy();
}
- /**
- * Enables platform notifications of data state and proxy changes.
- * Notifications are enabled by default.
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
public static void enablePlatformNotifications() {
- // noop
}
- /**
- * Disables platform notifications of data state and proxy changes.
- * Notifications are enabled by default.
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
public static void disablePlatformNotifications() {
- // noop
}
- /**
- * Used only by internal tests to free up memory.
- *
- * @hide
- */
- public static void freeMemoryForTests() {
- getFactory().getStatics().freeMemoryForTests();
- }
-
- /**
- * Informs WebView of the network state. This is used to set
- * the JavaScript property window.navigator.isOnline and
- * generates the online/offline event as specified in HTML5, sec. 5.7.7
- *
- * @param networkUp a boolean indicating if network is available
- */
- public void setNetworkAvailable(boolean networkUp) {
- checkThread();
- mProvider.setNetworkAvailable(networkUp);
- }
-
- /**
- * Saves the state of this WebView used in
- * {@link android.app.Activity#onSaveInstanceState}. Please note that this
- * method no longer stores the display data for this WebView. The previous
- * behavior could potentially leak files if {@link #restoreState} was never
- * called.
- *
- * @param outState the Bundle to store this WebView's state
- * @return the same copy of the back/forward list used to save the state, {@code null} if the
- * method fails.
- */
- @Nullable
- public WebBackForwardList saveState(Bundle outState) {
- checkThread();
- return mProvider.saveState(outState);
- }
-
- /**
- * Saves the current display data to the Bundle given. Used in conjunction
- * with {@link #saveState}.
- * @param b a Bundle to store the display data
- * @param dest the file to store the serialized picture data. Will be
- * overwritten with this WebView's picture data.
- * @return {@code true} if the picture was successfully saved
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public boolean savePicture(Bundle b, final File dest) {
- checkThread();
- return mProvider.savePicture(b, dest);
- }
-
- /**
- * Restores the display data that was saved in {@link #savePicture}. Used in
- * conjunction with {@link #restoreState}. Note that this will not work if
- * this WebView is hardware accelerated.
- *
- * @param b a Bundle containing the saved display data
- * @param src the file where the picture data was stored
- * @return {@code true} if the picture was successfully restored
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public boolean restorePicture(Bundle b, File src) {
- checkThread();
- return mProvider.restorePicture(b, src);
- }
-
- /**
- * Restores the state of this WebView from the given Bundle. This method is
- * intended for use in {@link android.app.Activity#onRestoreInstanceState}
- * and should be called to restore the state of this WebView. If
- * it is called after this WebView has had a chance to build state (load
- * pages, create a back/forward list, etc.) there may be undesirable
- * side-effects. Please note that this method no longer restores the
- * display data for this WebView.
- *
- * @param inState the incoming Bundle of state
- * @return the restored back/forward list or {@code null} if restoreState failed
- */
- @Nullable
- public WebBackForwardList restoreState(Bundle inState) {
- checkThread();
- return mProvider.restoreState(inState);
- }
-
- /**
- * Loads the given URL with the specified additional HTTP headers.
- * <p>
- * Also see compatibility note on {@link #evaluateJavascript}.
- *
- * @param url the URL of the resource to load
- * @param additionalHttpHeaders the additional headers to be used in the
- * HTTP request for this URL, specified as a map from name to
- * value. Note that if this map contains any of the headers
- * that are set by default by this WebView, such as those
- * controlling caching, accept types or the User-Agent, their
- * values may be overridden by this WebView's defaults.
- */
- public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
- checkThread();
- mProvider.loadUrl(url, additionalHttpHeaders);
- }
-
- /**
- * Loads the given URL.
- * <p>
- * Also see compatibility note on {@link #evaluateJavascript}.
- *
- * @param url the URL of the resource to load
- */
public void loadUrl(String url) {
- checkThread();
- mProvider.loadUrl(url);
- }
-
- /**
- * Loads the URL with postData using "POST" method into this WebView. If url
- * is not a network URL, it will be loaded with {@link #loadUrl(String)}
- * instead, ignoring the postData param.
- *
- * @param url the URL of the resource to load
- * @param postData the data will be passed to "POST" request, which must be
- * be "application/x-www-form-urlencoded" encoded.
- */
- public void postUrl(String url, byte[] postData) {
- checkThread();
- if (URLUtil.isNetworkUrl(url)) {
- mProvider.postUrl(url, postData);
- } else {
- mProvider.loadUrl(url);
- }
}
- /**
- * Loads the given data into this WebView using a 'data' scheme URL.
- * <p>
- * Note that JavaScript's same origin policy means that script running in a
- * page loaded using this method will be unable to access content loaded
- * using any scheme other than 'data', including 'http(s)'. To avoid this
- * restriction, use {@link
- * #loadDataWithBaseURL(String,String,String,String,String)
- * loadDataWithBaseURL()} with an appropriate base URL.
- * <p>
- * The encoding parameter specifies whether the data is base64 or URL
- * encoded. If the data is base64 encoded, the value of the encoding
- * parameter must be 'base64'. For all other values of the parameter,
- * including {@code null}, it is assumed that the data uses ASCII encoding for
- * octets inside the range of safe URL characters and use the standard %xx
- * hex encoding of URLs for octets outside that range. For example, '#',
- * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
- * <p>
- * The 'data' scheme URL formed by this method uses the default US-ASCII
- * charset. If you need need to set a different charset, you should form a
- * 'data' scheme URL which explicitly specifies a charset parameter in the
- * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
- * Note that the charset obtained from the mediatype portion of a data URL
- * always overrides that specified in the HTML or XML document itself.
- *
- * @param data a String of data in the given encoding
- * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
- * defaults to 'text/html'.
- * @param encoding the encoding of the data
- */
- public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) {
- checkThread();
- mProvider.loadData(data, mimeType, encoding);
+ public void loadData(String data, String mimeType, String encoding) {
}
- /**
- * Loads the given data into this WebView, using baseUrl as the base URL for
- * the content. The base URL is used both to resolve relative URLs and when
- * applying JavaScript's same origin policy. The historyUrl is used for the
- * history entry.
- * <p>
- * Note that content specified in this way can access local device files
- * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
- * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
- * <p>
- * If the base URL uses the data scheme, this method is equivalent to
- * calling {@link #loadData(String,String,String) loadData()} and the
- * historyUrl is ignored, and the data will be treated as part of a data: URL.
- * If the base URL uses any other scheme, then the data will be loaded into
- * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded
- * entities in the string will not be decoded.
- * <p>
- * Note that the baseUrl is sent in the 'Referer' HTTP header when
- * requesting subresources (images, etc.) of the page loaded using this method.
- *
- * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
- * 'about:blank'.
- * @param data a String of data in the given encoding
- * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
- * defaults to 'text/html'.
- * @param encoding the encoding of the data
- * @param historyUrl the URL to use as the history entry. If {@code null} defaults
- * to 'about:blank'. If non-null, this must be a valid URL.
- */
- public void loadDataWithBaseURL(@Nullable String baseUrl, String data,
- @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {
- checkThread();
- mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
+ public void loadDataWithBaseURL(String baseUrl, String data,
+ String mimeType, String encoding, String failUrl) {
}
- /**
- * Asynchronously evaluates JavaScript in the context of the currently displayed page.
- * If non-null, |resultCallback| will be invoked with any result returned from that
- * execution. This method must be called on the UI thread and the callback will
- * be made on the UI thread.
- * <p>
- * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
- * later, JavaScript state from an empty WebView is no longer persisted across navigations like
- * {@link #loadUrl(String)}. For example, global variables and functions defined before calling
- * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
- * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
- *
- * @param script the JavaScript to execute.
- * @param resultCallback A callback to be invoked when the script execution
- * completes with the result of the execution (if any).
- * May be {@code null} if no notification of the result is required.
- */
- public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) {
- checkThread();
- mProvider.evaluateJavaScript(script, resultCallback);
- }
-
- /**
- * Saves the current view as a web archive.
- *
- * @param filename the filename where the archive should be placed
- */
- public void saveWebArchive(String filename) {
- checkThread();
- mProvider.saveWebArchive(filename);
- }
-
- /**
- * Saves the current view as a web archive.
- *
- * @param basename the filename where the archive should be placed
- * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename
- * is assumed to be a directory in which a filename will be
- * chosen according to the URL of the current page.
- * @param callback called after the web archive has been saved. The
- * parameter for onReceiveValue will either be the filename
- * under which the file was saved, or {@code null} if saving the
- * file failed.
- */
- public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String>
- callback) {
- checkThread();
- mProvider.saveWebArchive(basename, autoname, callback);
- }
-
- /**
- * Stops the current load.
- */
public void stopLoading() {
- checkThread();
- mProvider.stopLoading();
}
- /**
- * Reloads the current URL.
- */
public void reload() {
- checkThread();
- mProvider.reload();
}
- /**
- * Gets whether this WebView has a back history item.
- *
- * @return {@code true} iff this WebView has a back history item
- */
public boolean canGoBack() {
- checkThread();
- return mProvider.canGoBack();
+ return false;
}
- /**
- * Goes back in the history of this WebView.
- */
public void goBack() {
- checkThread();
- mProvider.goBack();
}
- /**
- * Gets whether this WebView has a forward history item.
- *
- * @return {@code true} iff this WebView has a forward history item
- */
public boolean canGoForward() {
- checkThread();
- return mProvider.canGoForward();
+ return false;
}
- /**
- * Goes forward in the history of this WebView.
- */
public void goForward() {
- checkThread();
- mProvider.goForward();
}
- /**
- * Gets whether the page can go back or forward the given
- * number of steps.
- *
- * @param steps the negative or positive number of steps to move the
- * history
- */
public boolean canGoBackOrForward(int steps) {
- checkThread();
- return mProvider.canGoBackOrForward(steps);
+ return false;
}
- /**
- * Goes to the history item that is the number of steps away from
- * the current item. Steps is negative if backward and positive
- * if forward.
- *
- * @param steps the number of steps to take back or forward in the back
- * forward list
- */
public void goBackOrForward(int steps) {
- checkThread();
- mProvider.goBackOrForward(steps);
- }
-
- /**
- * Gets whether private browsing is enabled in this WebView.
- */
- public boolean isPrivateBrowsingEnabled() {
- checkThread();
- return mProvider.isPrivateBrowsingEnabled();
}
- /**
- * Scrolls the contents of this WebView up by half the view size.
- *
- * @param top {@code true} to jump to the top of the page
- * @return {@code true} if the page was scrolled
- */
public boolean pageUp(boolean top) {
- checkThread();
- return mProvider.pageUp(top);
+ return false;
}
-
- /**
- * Scrolls the contents of this WebView down by half the page size.
- *
- * @param bottom {@code true} to jump to bottom of page
- * @return {@code true} if the page was scrolled
- */
+
public boolean pageDown(boolean bottom) {
- checkThread();
- return mProvider.pageDown(bottom);
- }
-
- /**
- * Posts a {@link VisualStateCallback}, which will be called when
- * the current state of the WebView is ready to be drawn.
- *
- * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
- * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The
- * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
- * the DOM at the current time are ready to be drawn the next time the {@link WebView}
- * draws.
- *
- * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
- * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also
- * contain updates applied after the callback was posted.
- *
- * <p>The state of the DOM covered by this API includes the following:
- * <ul>
- * <li>primitive HTML elements (div, img, span, etc..)</li>
- * <li>images</li>
- * <li>CSS animations</li>
- * <li>WebGL</li>
- * <li>canvas</li>
- * </ul>
- * It does not include the state of:
- * <ul>
- * <li>the video tag</li>
- * </ul>
- *
- * <p>To guarantee that the {@link WebView} will successfully render the first frame
- * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
- * must be met:
- * <ul>
- * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
- * the {@link WebView} must be attached to the view hierarchy.</li>
- * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
- * then the {@link WebView} must be attached to the view hierarchy and must be made
- * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
- * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
- * {@link WebView} must be attached to the view hierarchy and its
- * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
- * values and must be made {@link View#VISIBLE VISIBLE} from the
- * {@link VisualStateCallback#onComplete} method.</li>
- * </ul>
- *
- * <p>When using this API it is also recommended to enable pre-rasterization if the {@link
- * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for
- * more details and do consider its caveats.
- *
- * @param requestId An id that will be returned in the callback to allow callers to match
- * requests with callbacks.
- * @param callback The callback to be invoked.
- */
- public void postVisualStateCallback(long requestId, VisualStateCallback callback) {
- checkThread();
- mProvider.insertVisualStateCallback(requestId, callback);
+ return false;
}
- /**
- * Clears this WebView so that onDraw() will draw nothing but white background,
- * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
- * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
- * and release page resources (including any running JavaScript).
- */
- @Deprecated
public void clearView() {
- checkThread();
- mProvider.clearView();
}
-
- /**
- * Gets a new picture that captures the current contents of this WebView.
- * The picture is of the entire document being displayed, and is not
- * limited to the area currently displayed by this WebView. Also, the
- * picture is a static copy and is unaffected by later changes to the
- * content being displayed.
- * <p>
- * Note that due to internal changes, for API levels between
- * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
- * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
- * picture does not include fixed position elements or scrollable divs.
- * <p>
- * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture
- * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve
- * additional conversion at a cost in memory and performance. Also the
- * {@link android.graphics.Picture#createFromStream} and
- * {@link android.graphics.Picture#writeToStream} methods are not supported on the
- * returned object.
- *
- * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or
- * {@link #saveWebArchive} to save the content to a file.
- *
- * @return a picture that captures the current contents of this WebView
- */
- @Deprecated
+
public Picture capturePicture() {
- checkThread();
- return mProvider.capturePicture();
- }
-
- /**
- * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
- * to provide a print document name.
- */
- @Deprecated
- public PrintDocumentAdapter createPrintDocumentAdapter() {
- checkThread();
- return mProvider.createPrintDocumentAdapter("default");
+ return null;
}
- /**
- * Creates a PrintDocumentAdapter that provides the content of this WebView for printing.
- *
- * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot
- * be drawn during the conversion process - any such draws are undefined. It is recommended
- * to use a dedicated off screen WebView for the printing. If necessary, an application may
- * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
- * wrapped around the object returned and observing the onStart and onFinish methods. See
- * {@link android.print.PrintDocumentAdapter} for more information.
- *
- * @param documentName The user-facing name of the printed document. See
- * {@link android.print.PrintDocumentInfo}
- */
- public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
- checkThread();
- return mProvider.createPrintDocumentAdapter(documentName);
- }
-
- /**
- * Gets the current scale of this WebView.
- *
- * @return the current scale
- *
- * @deprecated This method is prone to inaccuracy due to race conditions
- * between the web rendering and UI threads; prefer
- * {@link WebViewClient#onScaleChanged}.
- */
- @Deprecated
- @ViewDebug.ExportedProperty(category = "webview")
public float getScale() {
- checkThread();
- return mProvider.getScale();
+ return 0;
}
- /**
- * Sets the initial scale for this WebView. 0 means default.
- * The behavior for the default scale depends on the state of
- * {@link WebSettings#getUseWideViewPort()} and
- * {@link WebSettings#getLoadWithOverviewMode()}.
- * If the content fits into the WebView control by width, then
- * the zoom is set to 100%. For wide content, the behavior
- * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}.
- * If its value is {@code true}, the content will be zoomed out to be fit
- * by width into the WebView control, otherwise not.
- *
- * If initial scale is greater than 0, WebView starts with this value
- * as initial scale.
- * Please note that unlike the scale properties in the viewport meta tag,
- * this method doesn't take the screen density into account.
- *
- * @param scaleInPercent the initial scale in percent
- */
public void setInitialScale(int scaleInPercent) {
- checkThread();
- mProvider.setInitialScale(scaleInPercent);
}
- /**
- * Invokes the graphical zoom picker widget for this WebView. This will
- * result in the zoom widget appearing on the screen to control the zoom
- * level of this WebView.
- */
public void invokeZoomPicker() {
- checkThread();
- mProvider.invokeZoomPicker();
- }
-
- /**
- * Gets a HitTestResult based on the current cursor node. If a HTML::a
- * tag is found and the anchor has a non-JavaScript URL, the HitTestResult
- * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field.
- * If the anchor does not have a URL or if it is a JavaScript URL, the type
- * will be UNKNOWN_TYPE and the URL has to be retrieved through
- * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
- * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in
- * the "extra" field. A type of
- * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as
- * a child node. If a phone number is found, the HitTestResult type is set
- * to PHONE_TYPE and the phone number is set in the "extra" field of
- * HitTestResult. If a map address is found, the HitTestResult type is set
- * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
- * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
- * and the email is set in the "extra" field of HitTestResult. Otherwise,
- * HitTestResult type is set to UNKNOWN_TYPE.
- */
- public HitTestResult getHitTestResult() {
- checkThread();
- return mProvider.getHitTestResult();
}
- /**
- * Requests the anchor or image element URL at the last tapped point.
- * If hrefMsg is {@code null}, this method returns immediately and does not
- * dispatch hrefMsg to its target. If the tapped point hits an image,
- * an anchor, or an image in an anchor, the message associates
- * strings in named keys in its data. The value paired with the key
- * may be an empty string.
- *
- * @param hrefMsg the message to be dispatched with the result of the
- * request. The message data contains three keys. "url"
- * returns the anchor's href attribute. "title" returns the
- * anchor's text. "src" returns the image's src attribute.
- */
- public void requestFocusNodeHref(@Nullable Message hrefMsg) {
- checkThread();
- mProvider.requestFocusNodeHref(hrefMsg);
+ public void requestFocusNodeHref(Message hrefMsg) {
}
- /**
- * Requests the URL of the image last touched by the user. msg will be sent
- * to its target with a String representing the URL as its object.
- *
- * @param msg the message to be dispatched with the result of the request
- * as the data member with "url" as key. The result can be {@code null}.
- */
public void requestImageRef(Message msg) {
- checkThread();
- mProvider.requestImageRef(msg);
}
- /**
- * Gets the URL for the current page. This is not always the same as the URL
- * passed to WebViewClient.onPageStarted because although the load for
- * that URL has begun, the current page may not have changed.
- *
- * @return the URL for the current page
- */
- @ViewDebug.ExportedProperty(category = "webview")
public String getUrl() {
- checkThread();
- return mProvider.getUrl();
+ return null;
}
- /**
- * Gets the original URL for the current page. This is not always the same
- * as the URL passed to WebViewClient.onPageStarted because although the
- * load for that URL has begun, the current page may not have changed.
- * Also, there may have been redirects resulting in a different URL to that
- * originally requested.
- *
- * @return the URL that was originally requested for the current page
- */
- @ViewDebug.ExportedProperty(category = "webview")
- public String getOriginalUrl() {
- checkThread();
- return mProvider.getOriginalUrl();
- }
-
- /**
- * Gets the title for the current page. This is the title of the current page
- * until WebViewClient.onReceivedTitle is called.
- *
- * @return the title for the current page
- */
- @ViewDebug.ExportedProperty(category = "webview")
public String getTitle() {
- checkThread();
- return mProvider.getTitle();
+ return null;
}
- /**
- * Gets the favicon for the current page. This is the favicon of the current
- * page until WebViewClient.onReceivedIcon is called.
- *
- * @return the favicon for the current page
- */
public Bitmap getFavicon() {
- checkThread();
- return mProvider.getFavicon();
+ return null;
}
- /**
- * Gets the touch icon URL for the apple-touch-icon <link> element, or
- * a URL on this site's server pointing to the standard location of a
- * touch icon.
- *
- * @hide
- */
- public String getTouchIconUrl() {
- return mProvider.getTouchIconUrl();
- }
-
- /**
- * Gets the progress for the current page.
- *
- * @return the progress for the current page between 0 and 100
- */
public int getProgress() {
- checkThread();
- return mProvider.getProgress();
+ return 0;
}
-
- /**
- * Gets the height of the HTML content.
- *
- * @return the height of the HTML content
- */
- @ViewDebug.ExportedProperty(category = "webview")
+
public int getContentHeight() {
- checkThread();
- return mProvider.getContentHeight();
- }
-
- /**
- * Gets the width of the HTML content.
- *
- * @return the width of the HTML content
- * @hide
- */
- @ViewDebug.ExportedProperty(category = "webview")
- public int getContentWidth() {
- return mProvider.getContentWidth();
+ return 0;
}
- /**
- * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
- * is a global requests, not restricted to just this WebView. This can be
- * useful if the application has been paused.
- */
public void pauseTimers() {
- checkThread();
- mProvider.pauseTimers();
}
- /**
- * Resumes all layout, parsing, and JavaScript timers for all WebViews.
- * This will resume dispatching all timers.
- */
public void resumeTimers() {
- checkThread();
- mProvider.resumeTimers();
- }
-
- /**
- * Does a best-effort attempt to pause any processing that can be paused
- * safely, such as animations and geolocation. Note that this call
- * does not pause JavaScript. To pause JavaScript globally, use
- * {@link #pauseTimers}.
- *
- * To resume WebView, call {@link #onResume}.
- */
- public void onPause() {
- checkThread();
- mProvider.onPause();
- }
-
- /**
- * Resumes a WebView after a previous call to {@link #onPause}.
- */
- public void onResume() {
- checkThread();
- mProvider.onResume();
- }
-
- /**
- * Gets whether this WebView is paused, meaning onPause() was called.
- * Calling onResume() sets the paused state back to {@code false}.
- *
- * @hide
- */
- public boolean isPaused() {
- return mProvider.isPaused();
- }
-
- /**
- * Informs this WebView that memory is low so that it can free any available
- * memory.
- * @deprecated Memory caches are automatically dropped when no longer needed, and in response
- * to system memory pressure.
- */
- @Deprecated
- public void freeMemory() {
- checkThread();
- mProvider.freeMemory();
}
- /**
- * Clears the resource cache. Note that the cache is per-application, so
- * this will clear the cache for all WebViews used.
- *
- * @param includeDiskFiles if {@code false}, only the RAM cache is cleared
- */
- public void clearCache(boolean includeDiskFiles) {
- checkThread();
- mProvider.clearCache(includeDiskFiles);
+ public void clearCache() {
}
- /**
- * Removes the autocomplete popup from the currently focused form field, if
- * present. Note this only affects the display of the autocomplete popup,
- * it does not remove any saved form data from this WebView's store. To do
- * that, use {@link WebViewDatabase#clearFormData}.
- */
public void clearFormData() {
- checkThread();
- mProvider.clearFormData();
}
- /**
- * Tells this WebView to clear its internal back/forward list.
- */
public void clearHistory() {
- checkThread();
- mProvider.clearHistory();
}
- /**
- * Clears the SSL preferences table stored in response to proceeding with
- * SSL certificate errors.
- */
public void clearSslPreferences() {
- checkThread();
- mProvider.clearSslPreferences();
- }
-
- /**
- * Clears the client certificate preferences stored in response
- * to proceeding/cancelling client cert requests. Note that WebView
- * automatically clears these preferences when it receives a
- * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are
- * shared by all the WebViews that are created by the embedder application.
- *
- * @param onCleared A runnable to be invoked when client certs are cleared.
- * The runnable will be called in UI thread.
- */
- public static void clearClientCertPreferences(@Nullable Runnable onCleared) {
- getFactory().getStatics().clearClientCertPreferences(onCleared);
- }
-
- /**
- * Starts Safe Browsing initialization.
- * <p>
- * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
- * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
- * devices {@code callback} will receive {@code false}.
- * <p>
- * This does not enable the Safe Browsing feature itself, and should only be called if Safe
- * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
- * prepares resources used for Safe Browsing.
- * <p>
- * This should be called with the Application Context (and will always use the Application
- * context to do its work regardless).
- *
- * @param context Application Context.
- * @param callback will be called on the UI thread with {@code true} if initialization is
- * successful, {@code false} otherwise.
- */
- public static void startSafeBrowsing(Context context,
- @Nullable ValueCallback<Boolean> callback) {
- getFactory().getStatics().initSafeBrowsing(context, callback);
- }
-
- /**
- * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
- * global for all the WebViews.
- * <p>
- * Each rule should take one of these:
- * <table>
- * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
- * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
- * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
- * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
- * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
- * </table>
- * <p>
- * All other rules, including wildcards, are invalid.
- *
- * @param urls the list of URLs
- * @param callback will be called with {@code true} if URLs are successfully added to the
- * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
- * be run on the UI thread
- */
- public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
- @Nullable ValueCallback<Boolean> callback) {
- getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
- }
-
- /**
- * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
- *
- * @return the url pointing to a privacy policy document which can be displayed to users.
- */
- @NonNull
- public static Uri getSafeBrowsingPrivacyPolicyUrl() {
- return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
- }
-
- /**
- * Gets the WebBackForwardList for this WebView. This contains the
- * back/forward list for use in querying each item in the history stack.
- * This is a copy of the private WebBackForwardList so it contains only a
- * snapshot of the current state. Multiple calls to this method may return
- * different objects. The object returned from this method will not be
- * updated to reflect any new state.
- */
- public WebBackForwardList copyBackForwardList() {
- checkThread();
- return mProvider.copyBackForwardList();
-
- }
-
- /**
- * Registers the listener to be notified as find-on-page operations
- * progress. This will replace the current listener.
- *
- * @param listener an implementation of {@link FindListener}
- */
- public void setFindListener(FindListener listener) {
- checkThread();
- setupFindListenerIfNeeded();
- mFindListener.mUserFindListener = listener;
- }
-
- /**
- * Highlights and scrolls to the next match found by
- * {@link #findAllAsync}, wrapping around page boundaries as necessary.
- * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
- * has not been called yet, or if {@link #clearMatches} has been called since the
- * last find operation, this function does nothing.
- *
- * @param forward the direction to search
- * @see #setFindListener
- */
- public void findNext(boolean forward) {
- checkThread();
- mProvider.findNext(forward);
- }
-
- /**
- * Finds all instances of find on the page and highlights them.
- * Notifies any registered {@link FindListener}.
- *
- * @param find the string to find
- * @return the number of occurrences of the String "find" that were found
- * @deprecated {@link #findAllAsync} is preferred.
- * @see #setFindListener
- */
- @Deprecated
- public int findAll(String find) {
- checkThread();
- StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
- return mProvider.findAll(find);
- }
-
- /**
- * Finds all instances of find on the page and highlights them,
- * asynchronously. Notifies any registered {@link FindListener}.
- * Successive calls to this will cancel any pending searches.
- *
- * @param find the string to find.
- * @see #setFindListener
- */
- public void findAllAsync(String find) {
- checkThread();
- mProvider.findAllAsync(find);
- }
-
- /**
- * Starts an ActionMode for finding text in this WebView. Only works if this
- * WebView is attached to the view system.
- *
- * @param text if non-null, will be the initial text to search for.
- * Otherwise, the last String searched for in this WebView will
- * be used to start.
- * @param showIme if {@code true}, show the IME, assuming the user will begin typing.
- * If {@code false} and text is non-null, perform a find all.
- * @return {@code true} if the find dialog is shown, {@code false} otherwise
- * @deprecated This method does not work reliably on all Android versions;
- * implementing a custom find dialog using WebView.findAllAsync()
- * provides a more robust solution.
- */
- @Deprecated
- public boolean showFindDialog(@Nullable String text, boolean showIme) {
- checkThread();
- return mProvider.showFindDialog(text, showIme);
}
- /**
- * Gets the first substring consisting of the address of a physical
- * location. Currently, only addresses in the United States are detected,
- * and consist of:
- * <ul>
- * <li>a house number</li>
- * <li>a street name</li>
- * <li>a street type (Road, Circle, etc), either spelled out or
- * abbreviated</li>
- * <li>a city name</li>
- * <li>a state or territory, either spelled out or two-letter abbr</li>
- * <li>an optional 5 digit or 9 digit zip code</li>
- * </ul>
- * All names must be correctly capitalized, and the zip code, if present,
- * must be valid for the state. The street type must be a standard USPS
- * spelling or abbreviation. The state or territory must also be spelled
- * or abbreviated using USPS standards. The house number may not exceed
- * five digits.
- *
- * @param addr the string to search for addresses
- * @return the address, or if no address is found, {@code null}
- */
- @Nullable
public static String findAddress(String addr) {
- // TODO: Rewrite this in Java so it is not needed to start up chromium
- // Could also be deprecated
- return getFactory().getStatics().findAddress(addr);
- }
-
- /**
- * For apps targeting the L release, WebView has a new default behavior that reduces
- * memory footprint and increases performance by intelligently choosing
- * the portion of the HTML document that needs to be drawn. These
- * optimizations are transparent to the developers. However, under certain
- * circumstances, an App developer may want to disable them:
- * <ol>
- * <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
- * of the page that is way outside the visible portion of the page.</li>
- * <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
- * Note that capturePicture is a deprecated API.</li>
- * </ol>
- * Enabling drawing the entire HTML document has a significant performance
- * cost. This method should be called before any WebViews are created.
- */
- public static void enableSlowWholeDocumentDraw() {
- getFactory().getStatics().enableSlowWholeDocumentDraw();
+ return null;
}
- /**
- * Clears the highlighting surrounding text matches created by
- * {@link #findAllAsync}.
- */
- public void clearMatches() {
- checkThread();
- mProvider.clearMatches();
- }
-
- /**
- * Queries the document to see if it contains any image references. The
- * message object will be dispatched with arg1 being set to 1 if images
- * were found and 0 if the document does not reference any images.
- *
- * @param response the message that will be dispatched with the result
- */
public void documentHasImages(Message response) {
- checkThread();
- mProvider.documentHasImages(response);
}
- /**
- * Sets the WebViewClient that will receive various notifications and
- * requests. This will replace the current handler.
- *
- * @param client an implementation of WebViewClient
- * @see #getWebViewClient
- */
public void setWebViewClient(WebViewClient client) {
- checkThread();
- mProvider.setWebViewClient(client);
- }
-
- /**
- * Gets the WebViewClient.
- *
- * @return the WebViewClient, or a default client if not yet set
- * @see #setWebViewClient
- */
- public WebViewClient getWebViewClient() {
- checkThread();
- return mProvider.getWebViewClient();
}
- /**
- * Registers the interface to be used when content can not be handled by
- * the rendering engine, and should be downloaded instead. This will replace
- * the current handler.
- *
- * @param listener an implementation of DownloadListener
- */
public void setDownloadListener(DownloadListener listener) {
- checkThread();
- mProvider.setDownloadListener(listener);
}
- /**
- * Sets the chrome handler. This is an implementation of WebChromeClient for
- * use in handling JavaScript dialogs, favicons, titles, and the progress.
- * This will replace the current handler.
- *
- * @param client an implementation of WebChromeClient
- * @see #getWebChromeClient
- */
public void setWebChromeClient(WebChromeClient client) {
- checkThread();
- mProvider.setWebChromeClient(client);
}
- /**
- * Gets the chrome handler.
- *
- * @return the WebChromeClient, or {@code null} if not yet set
- * @see #setWebChromeClient
- */
- @Nullable
- public WebChromeClient getWebChromeClient() {
- checkThread();
- return mProvider.getWebChromeClient();
- }
-
- /**
- * Sets the Picture listener. This is an interface used to receive
- * notifications of a new Picture.
- *
- * @param listener an implementation of WebView.PictureListener
- * @deprecated This method is now obsolete.
- */
- @Deprecated
- public void setPictureListener(PictureListener listener) {
- checkThread();
- mProvider.setPictureListener(listener);
- }
-
- /**
- * Injects the supplied Java object into this WebView. The object is
- * injected into the JavaScript context of the main frame, using the
- * supplied name. This allows the Java object's methods to be
- * accessed from JavaScript. For applications targeted to API
- * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- * and above, only public methods that are annotated with
- * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
- * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
- * all public methods (including the inherited ones) can be accessed, see the
- * important security note below for implications.
- * <p> Note that injected objects will not appear in JavaScript until the page is next
- * (re)loaded. JavaScript should be enabled before injecting the object. For example:
- * <pre>
- * class JsObject {
- * {@literal @}JavascriptInterface
- * public String toString() { return "injectedObject"; }
- * }
- * webview.getSettings().setJavaScriptEnabled(true);
- * webView.addJavascriptInterface(new JsObject(), "injectedObject");
- * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
- * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
- * <p>
- * <strong>IMPORTANT:</strong>
- * <ul>
- * <li> This method can be used to allow JavaScript to control the host
- * application. This is a powerful feature, but also presents a security
- * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
- * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
- * are still vulnerable if the app runs on a device running Android earlier than 4.2.
- * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- * and to ensure the method is called only when running on Android 4.2 or later.
- * With these older versions, JavaScript could use reflection to access an
- * injected object's public fields. Use of this method in a WebView
- * containing untrusted content could allow an attacker to manipulate the
- * host application in unintended ways, executing Java code with the
- * permissions of the host application. Use extreme care when using this
- * method in a WebView which could contain untrusted content.</li>
- * <li> JavaScript interacts with Java object on a private, background
- * thread of this WebView. Care is therefore required to maintain thread
- * safety.
- * </li>
- * <li> The Java object's fields are not accessible.</li>
- * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
- * and above, methods of injected Java objects are enumerable from
- * JavaScript.</li>
- * </ul>
- *
- * @param object the Java object to inject into this WebView's JavaScript
- * context. {@code null} values are ignored.
- * @param name the name used to expose the object in JavaScript
- */
- public void addJavascriptInterface(Object object, String name) {
- checkThread();
- mProvider.addJavascriptInterface(object, name);
- }
-
- /**
- * Removes a previously injected Java object from this WebView. Note that
- * the removal will not be reflected in JavaScript until the page is next
- * (re)loaded. See {@link #addJavascriptInterface}.
- *
- * @param name the name used to expose the object in JavaScript
- */
- public void removeJavascriptInterface(@NonNull String name) {
- checkThread();
- mProvider.removeJavascriptInterface(name);
- }
-
- /**
- * Creates a message channel to communicate with JS and returns the message
- * ports that represent the endpoints of this message channel. The HTML5 message
- * channel functionality is described
- * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
- * </a>
- *
- * <p>The returned message channels are entangled and already in started state.
- *
- * @return the two message ports that form the message channel.
- */
- public WebMessagePort[] createWebMessageChannel() {
- checkThread();
- return mProvider.createWebMessageChannel();
- }
-
- /**
- * Post a message to main frame. The embedded application can restrict the
- * messages to a certain target origin. See
- * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
- * HTML5 spec</a> for how target origin can be used.
- * <p>
- * A target origin can be set as a wildcard ("*"). However this is not recommended.
- * See the page above for security issues.
- *
- * @param message the WebMessage
- * @param targetOrigin the target origin.
- */
- public void postWebMessage(WebMessage message, Uri targetOrigin) {
- checkThread();
- mProvider.postMessageToMainFrame(message, targetOrigin);
- }
-
- /**
- * Gets the WebSettings object used to control the settings for this
- * WebView.
- *
- * @return a WebSettings object that can be used to control this WebView's
- * settings
- */
- public WebSettings getSettings() {
- checkThread();
- return mProvider.getSettings();
- }
-
- /**
- * Enables debugging of web contents (HTML / CSS / JavaScript)
- * loaded into any WebViews of this application. This flag can be enabled
- * in order to facilitate debugging of web layouts and JavaScript
- * code running inside WebViews. Please refer to WebView documentation
- * for the debugging guide.
- *
- * The default is {@code false}.
- *
- * @param enabled whether to enable web contents debugging
- */
- public static void setWebContentsDebuggingEnabled(boolean enabled) {
- getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
- }
-
- /**
- * Gets the list of currently loaded plugins.
- *
- * @return the list of currently loaded plugins
- * @deprecated This was used for Gears, which has been deprecated.
- * @hide
- */
- @Deprecated
- public static synchronized PluginList getPluginList() {
- return new PluginList();
- }
-
- /**
- * @deprecated This was used for Gears, which has been deprecated.
- * @hide
- */
- @Deprecated
- public void refreshPlugins(boolean reloadOpenPages) {
- checkThread();
- }
-
- /**
- * Puts this WebView into text selection mode. Do not rely on this
- * functionality; it will be deprecated in the future.
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public void emulateShiftHeld() {
- checkThread();
- }
-
- /**
- * @deprecated WebView no longer needs to implement
- * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
- */
- @Override
- // Cannot add @hide as this can always be accessed via the interface.
- @Deprecated
- public void onChildViewAdded(View parent, View child) {}
-
- /**
- * @deprecated WebView no longer needs to implement
- * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
- */
- @Override
- // Cannot add @hide as this can always be accessed via the interface.
- @Deprecated
- public void onChildViewRemoved(View p, View child) {}
-
- /**
- * @deprecated WebView should not have implemented
- * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
- */
- @Override
- // Cannot add @hide as this can always be accessed via the interface.
- @Deprecated
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- }
-
- /**
- * @deprecated Only the default case, {@code true}, will be supported in a future version.
- */
- @Deprecated
- public void setMapTrackballToArrowKeys(boolean setMap) {
- checkThread();
- mProvider.setMapTrackballToArrowKeys(setMap);
+ public void addJavascriptInterface(Object obj, String interfaceName) {
}
-
- public void flingScroll(int vx, int vy) {
- checkThread();
- mProvider.flingScroll(vx, vy);
- }
-
- /**
- * Gets the zoom controls for this WebView, as a separate View. The caller
- * is responsible for inserting this View into the layout hierarchy.
- * <p/>
- * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced
- * built-in zoom mechanisms for the WebView, as opposed to these separate
- * zoom controls. The built-in mechanisms are preferred and can be enabled
- * using {@link WebSettings#setBuiltInZoomControls}.
- *
- * @deprecated the built-in zoom mechanisms are preferred
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
- */
- @Deprecated
public View getZoomControls() {
- checkThread();
- return mProvider.getZoomControls();
+ return null;
}
- /**
- * Gets whether this WebView can be zoomed in.
- *
- * @return {@code true} if this WebView can be zoomed in
- *
- * @deprecated This method is prone to inaccuracy due to race conditions
- * between the web rendering and UI threads; prefer
- * {@link WebViewClient#onScaleChanged}.
- */
- @Deprecated
- public boolean canZoomIn() {
- checkThread();
- return mProvider.canZoomIn();
- }
-
- /**
- * Gets whether this WebView can be zoomed out.
- *
- * @return {@code true} if this WebView can be zoomed out
- *
- * @deprecated This method is prone to inaccuracy due to race conditions
- * between the web rendering and UI threads; prefer
- * {@link WebViewClient#onScaleChanged}.
- */
- @Deprecated
- public boolean canZoomOut() {
- checkThread();
- return mProvider.canZoomOut();
- }
-
- /**
- * Performs a zoom operation in this WebView.
- *
- * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's
- * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
- */
- public void zoomBy(float zoomFactor) {
- checkThread();
- if (zoomFactor < 0.01)
- throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
- if (zoomFactor > 100.0)
- throw new IllegalArgumentException("zoomFactor must be less than 100.");
- mProvider.zoomBy(zoomFactor);
- }
-
- /**
- * Performs zoom in in this WebView.
- *
- * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes
- */
public boolean zoomIn() {
- checkThread();
- return mProvider.zoomIn();
+ return false;
}
- /**
- * Performs zoom out in this WebView.
- *
- * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
- */
public boolean zoomOut() {
- checkThread();
- return mProvider.zoomOut();
- }
-
- /**
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public void debugDump() {
- checkThread();
- }
-
- /**
- * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
- * @hide
- */
- @Override
- public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
- mProvider.dumpViewHierarchyWithProperties(out, level);
- }
-
- /**
- * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
- * @hide
- */
- @Override
- public View findHierarchyView(String className, int hashCode) {
- return mProvider.findHierarchyView(className, hashCode);
- }
-
- /** @hide */
- @IntDef({
- RENDERER_PRIORITY_WAIVED,
- RENDERER_PRIORITY_BOUND,
- RENDERER_PRIORITY_IMPORTANT
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RendererPriority {}
-
- /**
- * The renderer associated with this WebView is bound with
- * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
- * {@link WebView} renderers will be strong targets for out of memory
- * killing.
- *
- * Use with {@link #setRendererPriorityPolicy}.
- */
- public static final int RENDERER_PRIORITY_WAIVED = 0;
- /**
- * The renderer associated with this WebView is bound with
- * the default priority for services.
- *
- * Use with {@link #setRendererPriorityPolicy}.
- */
- public static final int RENDERER_PRIORITY_BOUND = 1;
- /**
- * The renderer associated with this WebView is bound with
- * {@link Context#BIND_IMPORTANT}.
- *
- * Use with {@link #setRendererPriorityPolicy}.
- */
- public static final int RENDERER_PRIORITY_IMPORTANT = 2;
-
- /**
- * Set the renderer priority policy for this {@link WebView}. The
- * priority policy will be used to determine whether an out of
- * process renderer should be considered to be a target for OOM
- * killing.
- *
- * Because a renderer can be associated with more than one
- * WebView, the final priority it is computed as the maximum of
- * any attached WebViews. When a WebView is destroyed it will
- * cease to be considerered when calculating the renderer
- * priority. Once no WebViews remain associated with the renderer,
- * the priority of the renderer will be reduced to
- * {@link #RENDERER_PRIORITY_WAIVED}.
- *
- * The default policy is to set the priority to
- * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
- * and this should not be changed unless the caller also handles
- * renderer crashes with
- * {@link WebViewClient#onRenderProcessGone}. Any other setting
- * will result in WebView renderers being killed by the system
- * more aggressively than the application.
- *
- * @param rendererRequestedPriority the minimum priority at which
- * this WebView desires the renderer process to be bound.
- * @param waivedWhenNotVisible if {@code true}, this flag specifies that
- * when this WebView is not visible, it will be treated as
- * if it had requested a priority of
- * {@link #RENDERER_PRIORITY_WAIVED}.
- */
- public void setRendererPriorityPolicy(
- @RendererPriority int rendererRequestedPriority,
- boolean waivedWhenNotVisible) {
- mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
- }
-
- /**
- * Get the requested renderer priority for this WebView.
- *
- * @return the requested renderer priority policy.
- */
- @RendererPriority
- public int getRendererRequestedPriority() {
- return mProvider.getRendererRequestedPriority();
- }
-
- /**
- * Return whether this WebView requests a priority of
- * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
- *
- * @return whether this WebView requests a priority of
- * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
- */
- public boolean getRendererPriorityWaivedWhenNotVisible() {
- return mProvider.getRendererPriorityWaivedWhenNotVisible();
- }
-
- /**
- * Sets the {@link TextClassifier} for this WebView.
- */
- public void setTextClassifier(@Nullable TextClassifier textClassifier) {
- mProvider.setTextClassifier(textClassifier);
- }
-
- /**
- * Returns the {@link TextClassifier} used by this WebView.
- * If no TextClassifier has been set, this WebView uses the default set by the system.
- */
- @NonNull
- public TextClassifier getTextClassifier() {
- return mProvider.getTextClassifier();
- }
-
- //-------------------------------------------------------------------------
- // Interface for WebView providers
- //-------------------------------------------------------------------------
-
- /**
- * Gets the WebViewProvider. Used by providers to obtain the underlying
- * implementation, e.g. when the application responds to
- * WebViewClient.onCreateWindow() request.
- *
- * @hide WebViewProvider is not public API.
- */
- @SystemApi
- public WebViewProvider getWebViewProvider() {
- return mProvider;
- }
-
- /**
- * Callback interface, allows the provider implementation to access non-public methods
- * and fields, and make super-class calls in this WebView instance.
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public class PrivateAccess {
- // ---- Access to super-class methods ----
- public int super_getScrollBarStyle() {
- return WebView.super.getScrollBarStyle();
- }
-
- public void super_scrollTo(int scrollX, int scrollY) {
- WebView.super.scrollTo(scrollX, scrollY);
- }
-
- public void super_computeScroll() {
- WebView.super.computeScroll();
- }
-
- public boolean super_onHoverEvent(MotionEvent event) {
- return WebView.super.onHoverEvent(event);
- }
-
- public boolean super_performAccessibilityAction(int action, Bundle arguments) {
- return WebView.super.performAccessibilityActionInternal(action, arguments);
- }
-
- public boolean super_performLongClick() {
- return WebView.super.performLongClick();
- }
-
- public boolean super_setFrame(int left, int top, int right, int bottom) {
- return WebView.super.setFrame(left, top, right, bottom);
- }
-
- public boolean super_dispatchKeyEvent(KeyEvent event) {
- return WebView.super.dispatchKeyEvent(event);
- }
-
- public boolean super_onGenericMotionEvent(MotionEvent event) {
- return WebView.super.onGenericMotionEvent(event);
- }
-
- public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) {
- return WebView.super.requestFocus(direction, previouslyFocusedRect);
- }
-
- public void super_setLayoutParams(ViewGroup.LayoutParams params) {
- WebView.super.setLayoutParams(params);
- }
-
- public void super_startActivityForResult(Intent intent, int requestCode) {
- WebView.super.startActivityForResult(intent, requestCode);
- }
-
- // ---- Access to non-public methods ----
- public void overScrollBy(int deltaX, int deltaY,
- int scrollX, int scrollY,
- int scrollRangeX, int scrollRangeY,
- int maxOverScrollX, int maxOverScrollY,
- boolean isTouchEvent) {
- WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
- maxOverScrollX, maxOverScrollY, isTouchEvent);
- }
-
- public void awakenScrollBars(int duration) {
- WebView.this.awakenScrollBars(duration);
- }
-
- public void awakenScrollBars(int duration, boolean invalidate) {
- WebView.this.awakenScrollBars(duration, invalidate);
- }
-
- public float getVerticalScrollFactor() {
- return WebView.this.getVerticalScrollFactor();
- }
-
- public float getHorizontalScrollFactor() {
- return WebView.this.getHorizontalScrollFactor();
- }
-
- public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- WebView.this.setMeasuredDimension(measuredWidth, measuredHeight);
- }
-
- public void onScrollChanged(int l, int t, int oldl, int oldt) {
- WebView.this.onScrollChanged(l, t, oldl, oldt);
- }
-
- public int getHorizontalScrollbarHeight() {
- return WebView.this.getHorizontalScrollbarHeight();
- }
-
- public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
- int l, int t, int r, int b) {
- WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
- }
-
- // ---- Access to (non-public) fields ----
- /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
- public void setScrollXRaw(int scrollX) {
- WebView.this.mScrollX = scrollX;
- }
-
- /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */
- public void setScrollYRaw(int scrollY) {
- WebView.this.mScrollY = scrollY;
- }
-
- }
-
- //-------------------------------------------------------------------------
- // Package-private internal stuff
- //-------------------------------------------------------------------------
-
- // Only used by android.webkit.FindActionModeCallback.
- void setFindDialogFindListener(FindListener listener) {
- checkThread();
- setupFindListenerIfNeeded();
- mFindListener.mFindDialogFindListener = listener;
- }
-
- // Only used by android.webkit.FindActionModeCallback.
- void notifyFindDialogDismissed() {
- checkThread();
- mProvider.notifyFindDialogDismissed();
- }
-
- //-------------------------------------------------------------------------
- // Private internal stuff
- //-------------------------------------------------------------------------
-
- private WebViewProvider mProvider;
-
- /**
- * In addition to the FindListener that the user may set via the WebView.setFindListener
- * API, FindActionModeCallback will register it's own FindListener. We keep them separate
- * via this class so that the two FindListeners can potentially exist at once.
- */
- private class FindListenerDistributor implements FindListener {
- private FindListener mFindDialogFindListener;
- private FindListener mUserFindListener;
-
- @Override
- public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
- boolean isDoneCounting) {
- if (mFindDialogFindListener != null) {
- mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
- isDoneCounting);
- }
-
- if (mUserFindListener != null) {
- mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
- isDoneCounting);
- }
- }
- }
- private FindListenerDistributor mFindListener;
-
- private void setupFindListenerIfNeeded() {
- if (mFindListener == null) {
- mFindListener = new FindListenerDistributor();
- mProvider.setFindListener(mFindListener);
- }
- }
-
- private void ensureProviderCreated() {
- checkThread();
- if (mProvider == null) {
- // As this can get called during the base class constructor chain, pass the minimum
- // number of dependencies here; the rest are deferred to init().
- mProvider = getFactory().createWebView(this, new PrivateAccess());
- }
- }
-
- private static WebViewFactoryProvider getFactory() {
- return WebViewFactory.getProvider();
- }
-
- private final Looper mWebViewThread = Looper.myLooper();
-
- private void checkThread() {
- // Ignore mWebViewThread == null because this can be called during in the super class
- // constructor, before this class's own constructor has even started.
- if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
- Throwable throwable = new Throwable(
- "A WebView method was called on thread '" +
- Thread.currentThread().getName() + "'. " +
- "All WebView methods must be called on the same thread. " +
- "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
- ", FYI main Looper is " + Looper.getMainLooper() + ")");
- Log.w(LOGTAG, Log.getStackTraceString(throwable));
- StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
-
- if (sEnforceThreadChecking) {
- throw new RuntimeException(throwable);
- }
- }
- }
-
- //-------------------------------------------------------------------------
- // Override View methods
- //-------------------------------------------------------------------------
-
- // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures
- // there's a corresponding override (or better, caller) for each of them in here.
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mProvider.getViewDelegate().onAttachedToWindow();
- }
-
- /** @hide */
- @Override
- protected void onDetachedFromWindowInternal() {
- mProvider.getViewDelegate().onDetachedFromWindow();
- super.onDetachedFromWindowInternal();
- }
-
- /** @hide */
- @Override
- public void onMovedToDisplay(int displayId, Configuration config) {
- mProvider.getViewDelegate().onMovedToDisplay(displayId, config);
- }
-
- @Override
- public void setLayoutParams(ViewGroup.LayoutParams params) {
- mProvider.getViewDelegate().setLayoutParams(params);
- }
-
- @Override
- public void setOverScrollMode(int mode) {
- super.setOverScrollMode(mode);
- // This method may be called in the constructor chain, before the WebView provider is
- // created.
- ensureProviderCreated();
- mProvider.getViewDelegate().setOverScrollMode(mode);
- }
-
- @Override
- public void setScrollBarStyle(int style) {
- mProvider.getViewDelegate().setScrollBarStyle(style);
- super.setScrollBarStyle(style);
- }
-
- @Override
- protected int computeHorizontalScrollRange() {
- return mProvider.getScrollDelegate().computeHorizontalScrollRange();
- }
-
- @Override
- protected int computeHorizontalScrollOffset() {
- return mProvider.getScrollDelegate().computeHorizontalScrollOffset();
- }
-
- @Override
- protected int computeVerticalScrollRange() {
- return mProvider.getScrollDelegate().computeVerticalScrollRange();
- }
-
- @Override
- protected int computeVerticalScrollOffset() {
- return mProvider.getScrollDelegate().computeVerticalScrollOffset();
- }
-
- @Override
- protected int computeVerticalScrollExtent() {
- return mProvider.getScrollDelegate().computeVerticalScrollExtent();
- }
-
- @Override
- public void computeScroll() {
- mProvider.getScrollDelegate().computeScroll();
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onHoverEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onTouchEvent(event);
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onGenericMotionEvent(event);
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onTrackballEvent(event);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event);
- }
-
- /*
- TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not
- to be delegating them too.
-
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyPreIme(keyCode, event);
- }
- @Override
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyLongPress(keyCode, event);
- }
- @Override
- public boolean onKeyShortcut(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyShortcut(keyCode, event);
- }
- */
-
- @Override
- public AccessibilityNodeProvider getAccessibilityNodeProvider() {
- AccessibilityNodeProvider provider =
- mProvider.getViewDelegate().getAccessibilityNodeProvider();
- return provider == null ? super.getAccessibilityNodeProvider() : provider;
- }
-
- @Deprecated
- @Override
- public boolean shouldDelayChildPressedState() {
- return mProvider.getViewDelegate().shouldDelayChildPressedState();
- }
-
- @Override
- public CharSequence getAccessibilityClassName() {
- return WebView.class.getName();
- }
-
- @Override
- public void onProvideVirtualStructure(ViewStructure structure) {
- mProvider.getViewDelegate().onProvideVirtualStructure(structure);
- }
-
- /**
- * {@inheritDoc}
- *
- * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
- * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
- * understood by the {@link android.service.autofill.AutofillService} implementations:
- *
- * <ol>
- * <li>Only the HTML nodes inside a {@code FORM} are generated.
- * <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the
- * node representing the WebView.
- * <li>If a web page has multiple {@code FORM}s, only the data for the current form is
- * represented&mdash;if the user taps a field from another form, then the current autofill
- * context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and
- * a new context is created for that {@code FORM}.
- * <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in
- * the view structure until the user taps a field from a {@code FORM} inside the
- * {@code IFRAME}, in which case it would be treated the same way as multiple forms described
- * above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the
- * {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node.
- * <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
- * {@link ViewStructure#setAutofillHints(String[])}.
- * <li>If the view is editable, the {@link ViewStructure#setAutofillType(int)} and
- * {@link ViewStructure#setAutofillValue(AutofillValue)} must be set.
- * <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
- * <li>Other HTML attributes can be represented through
- * {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}.
- * </ol>
- *
- * <p>If the WebView implementation can determine that the value of a field was set statically
- * (for example, not through Javascript), it should also call
- * {@code structure.setDataIsSensitive(false)}.
- *
- * <p>For example, an HTML form with 2 fields for username and password:
- *
- * <pre class="prettyprint">
- * &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
- * &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
- * </pre>
- *
- * <p>Would map to:
- *
- * <pre class="prettyprint">
- * int index = structure.addChildCount(2);
- * ViewStructure username = structure.newChild(index);
- * username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child
- * username.setAutofillHints("username");
- * username.setHtmlInfo(username.newHtmlInfoBuilder("input")
- * .addAttribute("type", "text")
- * .addAttribute("name", "username")
- * .build());
- * username.setHint("Email or username");
- * username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
- * username.setAutofillValue(AutofillValue.forText("Type your username"));
- * // Value of the field is not sensitive because it was created statically and not changed.
- * username.setDataIsSensitive(false);
- *
- * ViewStructure password = structure.newChild(index + 1);
- * username.setAutofillId(structure, 2); // id 2 - second child
- * password.setAutofillHints("current-password");
- * password.setHtmlInfo(password.newHtmlInfoBuilder("input")
- * .addAttribute("type", "password")
- * .addAttribute("name", "password")
- * .build());
- * password.setHint("Password");
- * password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
- * </pre>
- */
- @Override
- public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
- mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
- }
-
- @Override
- public void autofill(SparseArray<AutofillValue>values) {
- mProvider.getViewDelegate().autofill(values);
- }
-
- /** @hide */
- @Override
- public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfoInternal(info);
- mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
- }
-
- /** @hide */
- @Override
- public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
- super.onInitializeAccessibilityEventInternal(event);
- mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
- }
-
- /** @hide */
- @Override
- public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
- return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
- }
-
- /** @hide */
- @Override
- protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
- int l, int t, int r, int b) {
- mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
- }
-
- @Override
- protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
- mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY);
- }
-
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- mProvider.getViewDelegate().onWindowVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mProvider.getViewDelegate().onDraw(canvas);
- }
-
- @Override
- public boolean performLongClick() {
- return mProvider.getViewDelegate().performLongClick();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- mProvider.getViewDelegate().onConfigurationChanged(newConfig);
- }
-
- /**
- * Creates a new InputConnection for an InputMethod to interact with the WebView.
- * This is similar to {@link View#onCreateInputConnection} but note that WebView
- * calls InputConnection methods on a thread other than the UI thread.
- * If these methods are overridden, then the overriding methods should respect
- * thread restrictions when calling View methods or accessing data.
- */
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
- }
-
- @Override
- public boolean onDragEvent(DragEvent event) {
- return mProvider.getViewDelegate().onDragEvent(event);
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- // This method may be called in the constructor chain, before the WebView provider is
- // created.
- ensureProviderCreated();
- mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus);
- super.onWindowFocusChanged(hasWindowFocus);
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect);
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- }
-
- /** @hide */
- @Override
- protected boolean setFrame(int left, int top, int right, int bottom) {
- return mProvider.getViewDelegate().setFrame(left, top, right, bottom);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int ow, int oh) {
- super.onSizeChanged(w, h, ow, oh);
- mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh);
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- return mProvider.getViewDelegate().dispatchKeyEvent(event);
- }
-
- @Override
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
- return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate);
- }
-
- @Override
- public void setBackgroundColor(int color) {
- mProvider.getViewDelegate().setBackgroundColor(color);
- }
-
- @Override
- public void setLayerType(int layerType, Paint paint) {
- super.setLayerType(layerType, paint);
- mProvider.getViewDelegate().setLayerType(layerType, paint);
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- mProvider.getViewDelegate().preDispatchDraw(canvas);
- super.dispatchDraw(canvas);
- }
-
- @Override
- public void onStartTemporaryDetach() {
- super.onStartTemporaryDetach();
- mProvider.getViewDelegate().onStartTemporaryDetach();
- }
-
- @Override
- public void onFinishTemporaryDetach() {
- super.onFinishTemporaryDetach();
- mProvider.getViewDelegate().onFinishTemporaryDetach();
- }
-
- @Override
- public Handler getHandler() {
- return mProvider.getViewDelegate().getHandler(super.getHandler());
- }
-
- @Override
- public View findFocus() {
- return mProvider.getViewDelegate().findFocus(super.findFocus());
- }
-
- /**
- * If WebView has already been loaded into the current process this method will return the
- * package that was used to load it. Otherwise, the package that would be used if the WebView
- * was loaded right now will be returned; this does not cause WebView to be loaded, so this
- * information may become outdated at any time.
- * The WebView package changes either when the current WebView package is updated, disabled, or
- * uninstalled. It can also be changed through a Developer Setting.
- * If the WebView package changes, any app process that has loaded WebView will be killed. The
- * next time the app starts and loads WebView it will use the new WebView package instead.
- * @return the current WebView package, or {@code null} if there is none.
- */
- @Nullable
- public static PackageInfo getCurrentWebViewPackage() {
- PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
- if (webviewPackage != null) {
- return webviewPackage;
- }
-
- IWebViewUpdateService service = WebViewFactory.getUpdateService();
- if (service == null) {
- return null;
- }
- try {
- return service.getCurrentWebViewPackage();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
- *
- * @param requestCode The integer request code originally supplied to
- * startActivityForResult(), allowing you to identify who this
- * result came from.
- * @param resultCode The integer result code returned by the child activity
- * through its setResult().
- * @param data An Intent, which can return result data to the caller
- * (various data can be attached to Intent "extras").
- * @hide
- */
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
- }
-
- /** @hide */
- @Override
- protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
- super.encodeProperties(encoder);
-
- checkThread();
- encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
- encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
- encoder.addProperty("webview:scale", mProvider.getScale());
- encoder.addProperty("webview:title", mProvider.getTitle());
- encoder.addProperty("webview:url", mProvider.getUrl());
- encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
+ return false;
}
}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index afd11881..384f4f83 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -165,7 +165,7 @@ public class Editor {
private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
- private static final float MAGNIFIER_ZOOM = 1.5f;
+ private static final float MAGNIFIER_ZOOM = 1.25f;
@IntDef({MagnifierHandleTrigger.SELECTION_START,
MagnifierHandleTrigger.SELECTION_END,
MagnifierHandleTrigger.INSERTION})
@@ -3888,7 +3888,7 @@ public class Editor {
if (selected == null || selected.isEmpty()) {
menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
com.android.internal.R.string.autofill)
- .setShowAsAction(MenuItem.SHOW_AS_OVERFLOW_ALWAYS);
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
}
@@ -4539,23 +4539,21 @@ public class Editor {
final Layout layout = mTextView.getLayout();
final int lineNumber = layout.getLineForOffset(offset);
// Horizontally snap to character offset.
- final float xPosInView = getHorizontal(mTextView.getLayout(), offset);
+ final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
+ + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
// Vertically snap to middle of current line.
final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
- + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
- final int[] coordinatesOnScreen = new int[2];
- mTextView.getLocationOnScreen(coordinatesOnScreen);
- final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft()
- - mTextView.getScrollX() + coordinatesOnScreen[0];
- final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop()
- - mTextView.getScrollY() + coordinatesOnScreen[1];
+ + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
+ + mTextView.getTotalPaddingTop() - mTextView.getScrollY();
- mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+ suspendBlink();
+ mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
}
protected final void dismissMagnifier() {
if (mMagnifier != null) {
mMagnifier.dismiss();
+ resumeBlink();
}
}
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index 1b26f8e2..631f3882 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -131,7 +131,7 @@ public class RemoteViews implements Parcelable, Filter {
*
* @hide
*/
- private ApplicationInfo mApplication;
+ public ApplicationInfo mApplication;
/**
* The resource ID of the layout file. (Added to the parcel)
@@ -1519,8 +1519,7 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
- return mNestedViews.mApplication.packageName.equals(parentInfo.packageName)
- && mNestedViews.mApplication.uid == parentInfo.uid;
+ return mNestedViews.hasSameAppInfo(parentInfo);
}
@Override
@@ -2138,8 +2137,7 @@ public class RemoteViews implements Parcelable, Filter {
if (landscape == null || portrait == null) {
throw new RuntimeException("Both RemoteViews must be non-null");
}
- if (landscape.mApplication.uid != portrait.mApplication.uid
- || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) {
+ if (!landscape.hasSameAppInfo(portrait.mApplication)) {
throw new RuntimeException("Both RemoteViews must share the same package and user");
}
mApplication = portrait.mApplication;
@@ -2653,7 +2651,11 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling
* {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
- * to launch the provided {@link PendingIntent}.
+ * to launch the provided {@link PendingIntent}. The source bounds
+ * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked
+ * view in screen space.
+ * Note that any activity options associated with the pendingIntent may get overridden
+ * before starting the intent.
*
* When setting the on-click action of items within collections (eg. {@link ListView},
* {@link StackView} etc.), this method will not work. Instead, use {@link
@@ -3551,6 +3553,15 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Returns true if the {@link #mApplication} is same as the provided info.
+ *
+ * @hide
+ */
+ public boolean hasSameAppInfo(ApplicationInfo info) {
+ return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+ }
+
+ /**
* Parcelable.Creator that instantiates RemoteViews objects
*/
public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
diff --git a/android/widget/RemoteViewsAdapter.java b/android/widget/RemoteViewsAdapter.java
index 09686521..e5ae0ca0 100644
--- a/android/widget/RemoteViewsAdapter.java
+++ b/android/widget/RemoteViewsAdapter.java
@@ -16,11 +16,15 @@
package android.widget;
-import android.Manifest;
+import android.annotation.WorkerThread;
+import android.app.IServiceConnection;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -28,7 +32,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -38,7 +41,6 @@ import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.RemoteViews.OnClickHandler;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
import java.lang.ref.WeakReference;
@@ -48,73 +50,80 @@ import java.util.LinkedList;
import java.util.concurrent.Executor;
/**
- * An adapter to a RemoteViewsService which fetches and caches RemoteViews
- * to be later inflated as child views.
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as
+ * child views.
+ *
+ * The adapter runs in the host process, typically a Launcher app.
+ *
+ * It makes a service connection to the {@link RemoteViewsService} running in the
+ * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via
+ * the platform to get the bind permissions) and all interaction with the service is done on the
+ * background thread.
+ *
+ * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the
+ * connection is only made when new RemoteViews are required.
+ * @hide
*/
-/** @hide */
public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
- private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
private static final String TAG = "RemoteViewsAdapter";
// The max number of items in the cache
- private static final int sDefaultCacheSize = 40;
+ private static final int DEFAULT_CACHE_SIZE = 40;
// The delay (in millis) to wait until attempting to unbind from a service after a request.
// This ensures that we don't stay continually bound to the service and that it can be destroyed
// if we need the memory elsewhere in the system.
- private static final int sUnbindServiceDelay = 5000;
+ private static final int UNBIND_SERVICE_DELAY = 5000;
// Default height for the default loading view, in case we cannot get inflate the first view
- private static final int sDefaultLoadingViewHeight = 50;
+ private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
+
+ // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
+ // structures;
+ private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache>
+ sCachedRemoteViewsCaches = new HashMap<>();
+ private static final HashMap<RemoteViewsCacheKey, Runnable>
+ sRemoteViewsCacheRemoveRunnables = new HashMap<>();
- // Type defs for controlling different messages across the main and worker message queues
- private static final int sDefaultMessageType = 0;
- private static final int sUnbindServiceMessageType = 1;
+ private static HandlerThread sCacheRemovalThread;
+ private static Handler sCacheRemovalQueue;
+
+ // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
+ // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
+ // duration, the cache is dropped.
+ private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
private final Context mContext;
private final Intent mIntent;
private final int mAppWidgetId;
private final Executor mAsyncViewLoadExecutor;
- private RemoteViewsAdapterServiceConnection mServiceConnection;
- private WeakReference<RemoteAdapterConnectionCallback> mCallback;
private OnClickHandler mRemoteViewsOnClickHandler;
private final FixedSizeRemoteViewsCache mCache;
private int mVisibleWindowLowerBound;
private int mVisibleWindowUpperBound;
- // A flag to determine whether we should notify data set changed after we connect
- private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
-
// The set of requested views that are to be notified when the associated RemoteViews are
// loaded.
private RemoteViewsFrameLayoutRefSet mRequestedViews;
- private HandlerThread mWorkerThread;
+ private final HandlerThread mWorkerThread;
// items may be interrupted within the normally processed queues
- private Handler mWorkerQueue;
- private Handler mMainQueue;
-
- // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
- // structures;
- private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache>
- sCachedRemoteViewsCaches = new HashMap<>();
- private static final HashMap<RemoteViewsCacheKey, Runnable>
- sRemoteViewsCacheRemoveRunnables = new HashMap<>();
-
- private static HandlerThread sCacheRemovalThread;
- private static Handler sCacheRemovalQueue;
-
- // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
- // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
- // duration, the cache is dropped.
- private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
+ private final Handler mMainHandler;
+ private final RemoteServiceHandler mServiceHandler;
+ private final RemoteAdapterConnectionCallback mCallback;
// Used to indicate to the AdapterView that it can use this Adapter immediately after
// construction (happens when we have a cached FixedSizeRemoteViewsCache).
private boolean mDataReady = false;
/**
+ * USed to dedupe {@link RemoteViews#mApplication} so that we do not hold on to
+ * multiple copies of the same ApplicationInfo object.
+ */
+ private ApplicationInfo mLastRemoteViewAppInfo;
+
+ /**
* An interface for the RemoteAdapter to notify other classes when adapters
* are actually connected to/disconnected from their actual services.
*/
@@ -151,154 +160,192 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
}
+ static final int MSG_REQUEST_BIND = 1;
+ static final int MSG_NOTIFY_DATA_SET_CHANGED = 2;
+ static final int MSG_LOAD_NEXT_ITEM = 3;
+ static final int MSG_UNBIND_SERVICE = 4;
+
+ private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1;
+ private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2;
+ private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3;
+ private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4;
+ private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5;
+
/**
- * The service connection that gets populated when the RemoteViewsService is
- * bound. This must be a static inner class to ensure that no references to the outer
- * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
- * garbage collected, and would cause us to leak activities due to the caching mechanism for
- * FrameLayouts in the adapter).
+ * Handler for various interactions with the {@link RemoteViewsService}.
*/
- private static class RemoteViewsAdapterServiceConnection extends
- IRemoteViewsAdapterConnection.Stub {
- private boolean mIsConnected;
- private boolean mIsConnecting;
- private WeakReference<RemoteViewsAdapter> mAdapter;
+ private static class RemoteServiceHandler extends Handler implements ServiceConnection {
+
+ private final WeakReference<RemoteViewsAdapter> mAdapter;
+ private final Context mContext;
+
private IRemoteViewsFactory mRemoteViewsFactory;
- public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
- mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
+ // The last call to notifyDataSetChanged didn't succeed, try again on next service bind.
+ private boolean mNotifyDataSetChangedPending = false;
+ private boolean mBindRequested = false;
+
+ RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) {
+ super(workerLooper);
+ mAdapter = new WeakReference<>(adapter);
+ mContext = context;
}
- public synchronized void bind(Context context, int appWidgetId, Intent intent) {
- if (!mIsConnecting) {
- try {
- RemoteViewsAdapter adapter;
- final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if ((adapter = mAdapter.get()) != null) {
- mgr.bindRemoteViewsService(context.getOpPackageName(), appWidgetId,
- intent, asBinder());
- } else {
- Slog.w(TAG, "bind: adapter was null");
- }
- mIsConnecting = true;
- } catch (Exception e) {
- Log.e("RVAServiceConnection", "bind(): " + e.getMessage());
- mIsConnecting = false;
- mIsConnected = false;
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // This is called on the same thread.
+ mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+ enqueueDeferredUnbindServiceMessage();
+
+ RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter == null) {
+ return;
+ }
+
+ if (mNotifyDataSetChangedPending) {
+ mNotifyDataSetChangedPending = false;
+ Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED);
+ handleMessage(msg);
+ msg.recycle();
+ } else {
+ if (!sendNotifyDataSetChange(false)) {
+ return;
}
+
+ // Request meta data so that we have up to date data when calling back to
+ // the remote adapter callback
+ adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED);
}
}
- public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
- try {
- RemoteViewsAdapter adapter;
- final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if ((adapter = mAdapter.get()) != null) {
- mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent);
- } else {
- Slog.w(TAG, "unbind: adapter was null");
- }
- mIsConnecting = false;
- } catch (Exception e) {
- Log.e("RVAServiceConnection", "unbind(): " + e.getMessage());
- mIsConnecting = false;
- mIsConnected = false;
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mRemoteViewsFactory = null;
+ RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter != null) {
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED);
}
}
- public synchronized void onServiceConnected(IBinder service) {
- mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+ @Override
+ public void handleMessage(Message msg) {
+ RemoteViewsAdapter adapter = mAdapter.get();
- // Remove any deferred unbind messages
- final RemoteViewsAdapter adapter = mAdapter.get();
- if (adapter == null) return;
-
- // Queue up work that we need to do for the callback to run
- adapter.mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
- // Handle queued notifyDataSetChanged() if necessary
- adapter.onNotifyDataSetChanged();
- } else {
- IRemoteViewsFactory factory =
- adapter.mServiceConnection.getRemoteViewsFactory();
- try {
- if (!factory.isCreated()) {
- // We only call onDataSetChanged() if this is the factory was just
- // create in response to this bind
- factory.onDataSetChanged();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error notifying factory of data set changed in " +
- "onServiceConnected(): " + e.getMessage());
-
- // Return early to prevent anything further from being notified
- // (effectively nothing has changed)
- return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error notifying factory of data set changed in " +
- "onServiceConnected(): " + e.getMessage());
- }
+ switch (msg.what) {
+ case MSG_REQUEST_BIND: {
+ if (adapter == null || mRemoteViewsFactory != null) {
+ enqueueDeferredUnbindServiceMessage();
+ }
+ if (mBindRequested) {
+ return;
+ }
+ int flags = Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+ final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags);
+ Intent intent = (Intent) msg.obj;
+ int appWidgetId = msg.arg1;
+ mBindRequested = AppWidgetManager.getInstance(mContext)
+ .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags);
+ return;
+ }
+ case MSG_NOTIFY_DATA_SET_CHANGED: {
+ enqueueDeferredUnbindServiceMessage();
+ if (adapter == null) {
+ return;
+ }
+ if (mRemoteViewsFactory == null) {
+ mNotifyDataSetChangedPending = true;
+ adapter.requestBindService();
+ return;
+ }
+ if (!sendNotifyDataSetChange(true)) {
+ return;
+ }
- // Request meta data so that we have up to date data when calling back to
- // the remote adapter callback
- adapter.updateTemporaryMetaData();
-
- // Notify the host that we've connected
- adapter.mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- synchronized (adapter.mCache) {
- adapter.mCache.commitTemporaryMetaData();
- }
-
- final RemoteAdapterConnectionCallback callback =
- adapter.mCallback.get();
- if (callback != null) {
- callback.onRemoteAdapterConnected();
- }
- }
- });
+ // Flush the cache so that we can reload new items from the service
+ synchronized (adapter.mCache) {
+ adapter.mCache.reset();
}
- // Enqueue unbind message
- adapter.enqueueDeferredUnbindServiceMessage();
- mIsConnected = true;
- mIsConnecting = false;
- }
- });
- }
+ // Re-request the new metadata (only after the notification to the factory)
+ adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+ int newCount;
+ int[] visibleWindow;
+ synchronized (adapter.mCache.getTemporaryMetaData()) {
+ newCount = adapter.mCache.getTemporaryMetaData().count;
+ visibleWindow = adapter.getVisibleWindow(newCount);
+ }
- public synchronized void onServiceDisconnected() {
- mIsConnected = false;
- mIsConnecting = false;
- mRemoteViewsFactory = null;
+ // Pre-load (our best guess of) the views which are currently visible in the
+ // AdapterView. This mitigates flashing and flickering of loading views when a
+ // widget notifies that its data has changed.
+ for (int position : visibleWindow) {
+ // Because temporary meta data is only ever modified from this thread
+ // (ie. mWorkerThread), it is safe to assume that count is a valid
+ // representation.
+ if (position < newCount) {
+ adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
+ }
+ }
- // Clear the main/worker queues
- final RemoteViewsAdapter adapter = mAdapter.get();
- if (adapter == null) return;
+ // Propagate the notification back to the base adapter
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+ adapter.mMainHandler.sendEmptyMessage(
+ MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
+ return;
+ }
- adapter.mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- // Dequeue any unbind messages
- adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
+ case MSG_LOAD_NEXT_ITEM: {
+ if (adapter == null || mRemoteViewsFactory == null) {
+ return;
+ }
+ removeMessages(MSG_UNBIND_SERVICE);
+ // Get the next index to load
+ final int position = adapter.mCache.getNextIndexToLoad();
+ if (position > -1) {
+ // Load the item, and notify any existing RemoteViewsFrameLayouts
+ adapter.updateRemoteViews(mRemoteViewsFactory, position, true);
- final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
- if (callback != null) {
- callback.onRemoteAdapterDisconnected();
+ // Queue up for the next one to load
+ sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+ } else {
+ // No more items to load, so queue unbind
+ enqueueDeferredUnbindServiceMessage();
}
+ return;
+ }
+ case MSG_UNBIND_SERVICE: {
+ unbindNow();
+ return;
}
- });
+ }
}
- public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
- return mRemoteViewsFactory;
+ protected void unbindNow() {
+ if (mBindRequested) {
+ mBindRequested = false;
+ mContext.unbindService(this);
+ }
+ mRemoteViewsFactory = null;
}
- public synchronized boolean isConnected() {
- return mIsConnected;
+ private boolean sendNotifyDataSetChange(boolean always) {
+ try {
+ if (always || !mRemoteViewsFactory.isCreated()) {
+ mRemoteViewsFactory.onDataSetChanged();
+ }
+ return true;
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+ return false;
+ }
+ }
+
+ private void enqueueDeferredUnbindServiceMessage() {
+ removeMessages(MSG_UNBIND_SERVICE);
+ sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
}
}
@@ -309,6 +356,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
static class RemoteViewsFrameLayout extends AppWidgetHostView {
private final FixedSizeRemoteViewsCache mCache;
+ public int cacheIndex = -1;
+
public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
super(context);
mCache = cache;
@@ -359,26 +408,23 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
* Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
* adapter that have not yet had their RemoteViews loaded.
*/
- private class RemoteViewsFrameLayoutRefSet {
- private final SparseArray<LinkedList<RemoteViewsFrameLayout>> mReferences =
- new SparseArray<>();
- private final HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
- mViewToLinkedList = new HashMap<>();
+ private class RemoteViewsFrameLayoutRefSet
+ extends SparseArray<LinkedList<RemoteViewsFrameLayout>> {
/**
* Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
*/
public void add(int position, RemoteViewsFrameLayout layout) {
- LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+ LinkedList<RemoteViewsFrameLayout> refs = get(position);
// Create the list if necessary
if (refs == null) {
- refs = new LinkedList<RemoteViewsFrameLayout>();
- mReferences.put(position, refs);
+ refs = new LinkedList<>();
+ put(position, refs);
}
- mViewToLinkedList.put(layout, refs);
// Add the references to the list
+ layout.cacheIndex = position;
refs.add(layout);
}
@@ -389,18 +435,13 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
if (view == null) return;
- final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+ // Remove this set from the original mapping
+ final LinkedList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
if (refs != null) {
// Notify all the references for that position of the newly loaded RemoteViews
for (final RemoteViewsFrameLayout ref : refs) {
ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler, true);
- if (mViewToLinkedList.containsKey(ref)) {
- mViewToLinkedList.remove(ref);
- }
}
- refs.clear();
- // Remove this set from the original mapping
- mReferences.remove(position);
}
}
@@ -408,20 +449,14 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
* We need to remove views from this set if they have been recycled by the AdapterView.
*/
public void removeView(RemoteViewsFrameLayout rvfl) {
- if (mViewToLinkedList.containsKey(rvfl)) {
- mViewToLinkedList.get(rvfl).remove(rvfl);
- mViewToLinkedList.remove(rvfl);
+ if (rvfl.cacheIndex < 0) {
+ return;
}
- }
-
- /**
- * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
- */
- public void clear() {
- // We currently just clear the references, and leave all the previous layouts returned
- // in their default state of the loading view.
- mReferences.clear();
- mViewToLinkedList.clear();
+ final LinkedList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex);
+ if (refs != null) {
+ refs.remove(rvfl);
+ }
+ rvfl.cacheIndex = -1;
}
}
@@ -512,7 +547,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
*
*/
private static class FixedSizeRemoteViewsCache {
- private static final String TAG = "FixedSizeRemoteViewsCache";
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
// The meta data objects are made final so that they can be locked on independently
@@ -534,7 +568,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// too much memory.
private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
- // An array of indices to load, Indices which are explicitely requested are set to true,
+ // An array of indices to load, Indices which are explicitly requested are set to true,
// and those determined by the preloading algorithm to prefetch are set to false.
private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
@@ -676,7 +710,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
}
- int count = 0;
+ int count;
synchronized (mMetaData) {
count = mMetaData.count;
}
@@ -791,9 +825,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// Initialize the worker thread
mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
mWorkerThread.start();
- mWorkerQueue = new Handler(mWorkerThread.getLooper());
- mMainQueue = new Handler(Looper.myLooper(), this);
+ mMainHandler = new Handler(Looper.myLooper(), this);
+ mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
+ context.getApplicationContext());
mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
+ mCallback = callback;
if (sCacheRemovalThread == null) {
sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
@@ -801,10 +837,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
}
- // Initialize the cache and the service connection on startup
- mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
- mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
-
RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
mAppWidgetId);
@@ -819,7 +851,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
}
} else {
- mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
+ mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE);
}
if (!mDataReady) {
requestBindService();
@@ -830,9 +862,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
@Override
protected void finalize() throws Throwable {
try {
- if (mWorkerThread != null) {
- mWorkerThread.quit();
- }
+ mServiceHandler.unbindNow();
+ mWorkerThread.quit();
} finally {
super.finalize();
}
@@ -869,16 +900,13 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
sCachedRemoteViewsCaches.put(key, mCache);
}
- Runnable r = new Runnable() {
- @Override
- public void run() {
- synchronized (sCachedRemoteViewsCaches) {
- if (sCachedRemoteViewsCaches.containsKey(key)) {
- sCachedRemoteViewsCaches.remove(key);
- }
- if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
- sRemoteViewsCacheRemoveRunnables.remove(key);
- }
+ Runnable r = () -> {
+ synchronized (sCachedRemoteViewsCaches) {
+ if (sCachedRemoteViewsCaches.containsKey(key)) {
+ sCachedRemoteViewsCaches.remove(key);
+ }
+ if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+ sRemoteViewsCacheRemoveRunnables.remove(key);
}
}
};
@@ -887,54 +915,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
}
- private void loadNextIndexInBackground() {
- mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- if (mServiceConnection.isConnected()) {
- // Get the next index to load
- int position = -1;
- synchronized (mCache) {
- position = mCache.getNextIndexToLoad();
- }
- if (position > -1) {
- // Load the item, and notify any existing RemoteViewsFrameLayouts
- updateRemoteViews(position, true);
-
- // Queue up for the next one to load
- loadNextIndexInBackground();
- } else {
- // No more items to load, so queue unbind
- enqueueDeferredUnbindServiceMessage();
- }
- }
- }
- });
- }
-
- private void processException(String method, Exception e) {
- Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
-
- // If we encounter a crash when updating, we should reset the metadata & cache and trigger
- // a notifyDataSetChanged to update the widget accordingly
- final RemoteViewsMetaData metaData = mCache.getMetaData();
- synchronized (metaData) {
- metaData.reset();
- }
- synchronized (mCache) {
- mCache.reset();
- }
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- superNotifyDataSetChanged();
- }
- });
- }
-
- private void updateTemporaryMetaData() {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+ @WorkerThread
+ private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
try {
// get the properties/first view (so that we can use it to
// measure our dummy views)
@@ -958,40 +940,54 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
tmpMetaData.count = count;
tmpMetaData.loadingTemplate = loadingTemplate;
}
- } catch(RemoteException e) {
- processException("updateMetaData", e);
- } catch(RuntimeException e) {
- processException("updateMetaData", e);
+ } catch (RemoteException | RuntimeException e) {
+ Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
+
+ // If we encounter a crash when updating, we should reset the metadata & cache
+ // and trigger a notifyDataSetChanged to update the widget accordingly
+ synchronized (mCache.getMetaData()) {
+ mCache.getMetaData().reset();
+ }
+ synchronized (mCache) {
+ mCache.reset();
+ }
+ mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
}
}
- private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+ @WorkerThread
+ private void updateRemoteViews(IRemoteViewsFactory factory, int position,
+ boolean notifyWhenLoaded) {
// Load the item information from the remote service
- RemoteViews remoteViews = null;
- long itemId = 0;
+ final RemoteViews remoteViews;
+ final long itemId;
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
- } catch (RemoteException e) {
+
+ if (remoteViews == null) {
+ throw new RuntimeException("Null remoteViews");
+ }
+ } catch (RemoteException | RuntimeException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
- return;
}
- if (remoteViews == null) {
- // If a null view was returned, we break early to prevent it from getting
- // into our cache and causing problems later. The effect is that the child at this
- // position will remain as a loading view until it is updated.
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
- "returned from RemoteViewsFactory.");
- return;
+ if (remoteViews.mApplication != null) {
+ // We keep track of last application info. This helps when all the remoteViews have
+ // same applicationInfo, which should be the case for a typical adapter. But if every
+ // view has different application info, there will not be any optimization.
+ if (mLastRemoteViewAppInfo != null
+ && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
+ // We should probably also update the remoteViews for nested ViewActions.
+ // Hopefully, RemoteViews in an adapter would be less complicated.
+ remoteViews.mApplication = mLastRemoteViewAppInfo;
+ } else {
+ mLastRemoteViewAppInfo = remoteViews.mApplication;
+ }
}
int layoutId = remoteViews.getLayoutId();
@@ -1004,21 +1000,15 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
synchronized (mCache) {
if (viewTypeInRange) {
- int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
- mVisibleWindowUpperBound, cacheCount);
+ int[] visibleWindow = getVisibleWindow(cacheCount);
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId, visibleWindow);
- // Notify all the views that we have previously returned for this index that
- // there is new data for it.
- final RemoteViews rv = remoteViews;
if (notifyWhenLoaded) {
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
- }
- });
+ // Notify all the views that we have previously returned for this index that
+ // there is new data for it.
+ Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
+ remoteViews).sendToTarget();
}
} else {
// We need to log an error here, as the the view type count specified by the
@@ -1057,7 +1047,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public int getItemViewType(int position) {
- int typeId = 0;
+ final int typeId;
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
typeId = mCache.getMetaDataAt(position).typeId;
@@ -1088,14 +1078,13 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
synchronized (mCache) {
RemoteViews rv = mCache.getRemoteViewsAt(position);
boolean isInCache = (rv != null);
- boolean isConnected = mServiceConnection.isConnected();
boolean hasNewItems = false;
if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
}
- if (!isInCache && !isConnected) {
+ if (!isInCache) {
// Requesting bind service will trigger a super.notifyDataSetChanged(), which will
// in turn trigger another request to getView()
requestBindService();
@@ -1115,7 +1104,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
if (isInCache) {
// Apply the view synchronously if possible, to avoid flickering
layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
- if (hasNewItems) loadNextIndexInBackground();
+ if (hasNewItems) {
+ mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+ }
} else {
// If the views is not loaded, apply the loading view. If the loading view doesn't
// exist, the layout will create a default view based on the firstView height.
@@ -1125,7 +1116,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
false);
mRequestedViews.add(position, layout);
mCache.queueRequestedPositionToLoad(position);
- loadNextIndexInBackground();
+ mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
}
return layout;
}
@@ -1149,69 +1140,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
return getCount() <= 0;
}
- private void onNotifyDataSetChanged() {
- // Complete the actual notifyDataSetChanged() call initiated earlier
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
- try {
- factory.onDataSetChanged();
- } catch (RemoteException e) {
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
- // Return early to prevent from further being notified (since nothing has
- // changed)
- return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
- return;
- }
-
- // Flush the cache so that we can reload new items from the service
- synchronized (mCache) {
- mCache.reset();
- }
-
- // Re-request the new metadata (only after the notification to the factory)
- updateTemporaryMetaData();
- int newCount;
- int[] visibleWindow;
- synchronized(mCache.getTemporaryMetaData()) {
- newCount = mCache.getTemporaryMetaData().count;
- visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
- mVisibleWindowUpperBound, newCount);
- }
-
- // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
- // This mitigates flashing and flickering of loading views when a widget notifies that
- // its data has changed.
- for (int i: visibleWindow) {
- // Because temporary meta data is only ever modified from this thread (ie.
- // mWorkerThread), it is safe to assume that count is a valid representation.
- if (i < newCount) {
- updateRemoteViews(i, false);
- }
- }
-
- // Propagate the notification back to the base adapter
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mCache) {
- mCache.commitTemporaryMetaData();
- }
-
- superNotifyDataSetChanged();
- enqueueDeferredUnbindServiceMessage();
- }
- });
-
- // Reset the notify flagflag
- mNotifyDataSetChangedAfterOnServiceConnected = false;
- }
-
/**
* Returns a sorted array of all integers between lower and upper.
*/
- private int[] getVisibleWindow(int lower, int upper, int count) {
+ private int[] getVisibleWindow(int count) {
+ int lower = mVisibleWindowLowerBound;
+ int upper = mVisibleWindowUpperBound;
// In the case that the window is invalid or uninitialized, return an empty window.
if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
return new int[0];
@@ -1241,23 +1175,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public void notifyDataSetChanged() {
- // Dequeue any unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
-
- // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
- // connect
- if (!mServiceConnection.isConnected()) {
- mNotifyDataSetChangedAfterOnServiceConnected = true;
- requestBindService();
- return;
- }
-
- mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- onNotifyDataSetChanged();
- }
- });
+ mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+ mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
}
void superNotifyDataSetChanged() {
@@ -1266,35 +1185,38 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
@Override
public boolean handleMessage(Message msg) {
- boolean result = false;
switch (msg.what) {
- case sUnbindServiceMessageType:
- if (mServiceConnection.isConnected()) {
- mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
+ case MSG_MAIN_HANDLER_COMMIT_METADATA: {
+ mCache.commitTemporaryMetaData();
+ return true;
+ }
+ case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
+ superNotifyDataSetChanged();
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
+ if (mCallback != null) {
+ mCallback.onRemoteAdapterConnected();
+ }
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
+ if (mCallback != null) {
+ mCallback.onRemoteAdapterDisconnected();
+ }
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
+ mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
+ return true;
}
- result = true;
- break;
- default:
- break;
}
- return result;
- }
-
- private void enqueueDeferredUnbindServiceMessage() {
- // Remove any existing deferred-unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
- mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
+ return false;
}
- private boolean requestBindService() {
- // Try binding the service (which will start it if it's not already running)
- if (!mServiceConnection.isConnected()) {
- mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
- }
-
- // Remove any existing deferred-unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
- return mServiceConnection.isConnected();
+ private void requestBindService() {
+ mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+ Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
}
private static class HandlerThreadExecutor implements Executor {
@@ -1322,7 +1244,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
remoteViews = views;
float density = context.getResources().getDisplayMetrics().density;
- defaultHeight = Math.round(sDefaultLoadingViewHeight * density);
+ defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
}
public void loadFirstViewHeight(
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 3be42a5b..5e22650a 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -95,11 +95,15 @@ public final class SelectionActionModeHelper {
}
public void startActionModeAsync(boolean adjustSelection) {
+ // Check if the smart selection should run for editable text.
+ adjustSelection &= !mTextView.isTextEditable()
+ || mTextView.getTextClassifier().getSettings()
+ .isSuggestSelectionEnabledForEditableText();
+
mSelectionTracker.onOriginalSelection(
getText(mTextView),
mTextView.getSelectionStart(),
- mTextView.getSelectionEnd(),
- mTextView.isTextEditable());
+ mTextView.getSelectionEnd());
cancelAsyncTask();
if (skipTextClassification()) {
startActionMode(null);
@@ -196,7 +200,10 @@ public final class SelectionActionModeHelper {
private void startActionMode(@Nullable SelectionResult result) {
final CharSequence text = getText(mTextView);
if (result != null && text instanceof Spannable) {
- Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+ // Do not change the selection if TextClassifier should be dark launched.
+ if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
+ Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+ }
mTextClassification = result.mClassification;
} else {
mTextClassification = null;
@@ -377,7 +384,7 @@ public final class SelectionActionModeHelper {
}
private void resetTextClassificationHelper() {
- mTextClassificationHelper.reset(
+ mTextClassificationHelper.init(
mTextView.getTextClassifier(),
getText(mTextView),
mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -415,8 +422,7 @@ public final class SelectionActionModeHelper {
/**
* Called when the original selection happens, before smart selection is triggered.
*/
- public void onOriginalSelection(
- CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+ public void onOriginalSelection(CharSequence text, int selectionStart, int selectionEnd) {
// If we abandoned a selection and created a new one very shortly after, we may still
// have a pending request to log ABANDON, which we flush here.
mDelayedLogAbandon.flush();
@@ -812,11 +818,11 @@ public final class SelectionActionModeHelper {
TextClassificationHelper(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
- reset(textClassifier, text, selectionStart, selectionEnd, locales);
+ init(textClassifier, text, selectionStart, selectionEnd, locales);
}
@UiThread
- public void reset(TextClassifier textClassifier,
+ public void init(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
mTextClassifier = Preconditions.checkNotNull(textClassifier);
mText = Preconditions.checkNotNull(text).toString();
@@ -839,8 +845,12 @@ public final class SelectionActionModeHelper {
trimText();
final TextSelection selection = mTextClassifier.suggestSelection(
mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
- mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
- mSelectionEnd = Math.min(mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+ // Do not classify new selection boundaries if TextClassifier should be dark launched.
+ if (!mTextClassifier.getSettings().isDarkLaunch()) {
+ mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
+ mSelectionEnd = Math.min(
+ mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+ }
return performClassification(selection);
}
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index 24ae03c3..d9bc51ff 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -10338,6 +10338,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
structure.setTextStyle(getTextSize(), getCurrentTextColor(),
AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
+ } else {
+ structure.setMinTextEms(getMinEms());
+ structure.setMaxTextEms(getMaxEms());
+ int maxLength = -1;
+ for (InputFilter filter: getFilters()) {
+ if (filter instanceof InputFilter.LengthFilter) {
+ maxLength = ((InputFilter.LengthFilter) filter).getMax();
+ break;
+ }
+ }
+ structure.setMaxTextLength(maxLength);
}
}
structure.setHint(getHint());