summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorJason Monk <jmonk@google.com>2017-10-19 18:17:25 +0000
committerJason Monk <jmonk@google.com>2017-10-19 18:17:25 +0000
commit07f9f65561c2b81bcd189b895b31bb2ad0438d74 (patch)
tree49f76f879a89c256a4f65b674086be50760bdffb /android
parentd439404c9988df6001e4ff8bce31537e2692660e (diff)
downloadandroid-28-07f9f65561c2b81bcd189b895b31bb2ad0438d74.tar.gz
Revert "Import Android SDK Platform P [4402356]"
This reverts commit d439404c9988df6001e4ff8bce31537e2692660e. Change-Id: I825790bdf38523800388bc1bb531cecfcd7e60bd
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/assist/AssistStructure.java57
-rw-r--r--android/app/job/JobScheduler.java41
-rw-r--r--android/app/job/JobService.java102
-rw-r--r--android/app/usage/UsageStatsManager.java19
-rw-r--r--android/arch/lifecycle/ActivityFullLifecycleTest.java58
-rw-r--r--android/arch/lifecycle/AndroidViewModel.java7
-rw-r--r--android/arch/lifecycle/ClassesInfoCache.java16
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/DispatcherActivityCallbackTest.java77
-rw-r--r--android/arch/lifecycle/Lifecycle.java7
-rw-r--r--android/arch/lifecycle/LifecycleDispatcher.java182
-rw-r--r--android/arch/lifecycle/LifecycleOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistry.java63
-rw-r--r--android/arch/lifecycle/LifecycleRegistryOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistryTest.java19
-rw-r--r--android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java (renamed from android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java)3
-rw-r--r--android/arch/lifecycle/LiveData.java410
-rw-r--r--android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java177
-rw-r--r--android/arch/lifecycle/LiveDataTest.java224
-rw-r--r--android/arch/lifecycle/MediatorLiveData.java39
-rw-r--r--android/arch/lifecycle/MissingClassTest.java44
-rw-r--r--android/arch/lifecycle/PartiallyCoveredActivityTest.java200
-rw-r--r--android/arch/lifecycle/ProcessLifecycleOwner.java5
-rw-r--r--android/arch/lifecycle/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/CollectingActivity.java (renamed from android/arch/lifecycle/testapp/CollectingLifecycleOwner.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/ContiguousDiffHelper.java (renamed from android/arch/paging/PagedStorageDiffHelper.java)65
-rw-r--r--android/arch/paging/ContiguousDiffHelperTest.java (renamed from android/arch/paging/PagedStorageDiffHelperTest.java)67
-rw-r--r--android/arch/paging/ContiguousPagedList.java408
-rw-r--r--android/arch/paging/ContiguousPagedListTest.java62
-rw-r--r--android/arch/paging/DataSource.java14
-rw-r--r--android/arch/paging/KeyedDataSource.java15
-rw-r--r--android/arch/paging/LivePagedListProvider.java132
-rw-r--r--android/arch/paging/NullPaddedList.java75
-rw-r--r--android/arch/paging/Page.java51
-rw-r--r--android/arch/paging/PageArrayList.java130
-rw-r--r--android/arch/paging/PageArrayListTest.java49
-rw-r--r--android/arch/paging/PageResult.java56
-rw-r--r--android/arch/paging/PagedList.java145
-rw-r--r--android/arch/paging/PagedListAdapterHelper.java109
-rw-r--r--android/arch/paging/PagedListAdapterHelperTest.java59
-rw-r--r--android/arch/paging/PagedStorage.java433
-rw-r--r--android/arch/paging/PositionalDataSource.java25
-rw-r--r--android/arch/paging/SnapshotPagedList.java64
-rw-r--r--android/arch/paging/SparseDiffHelper.java99
-rw-r--r--android/arch/paging/StringPagedList.java56
-rw-r--r--android/arch/paging/TestExecutor.java2
-rw-r--r--android/arch/paging/TiledDataSource.java58
-rw-r--r--android/arch/paging/TiledPagedList.java276
-rw-r--r--android/arch/paging/TiledPagedListTest.java124
-rw-r--r--android/arch/persistence/room/InvalidationTracker.java4
-rw-r--r--android/arch/persistence/room/Relation.java18
-rw-r--r--android/arch/persistence/room/Room.java2
-rw-r--r--android/arch/persistence/room/RoomDatabase.java15
-rw-r--r--android/arch/persistence/room/RoomWarnings.java8
-rw-r--r--android/arch/persistence/room/Transaction.java37
-rw-r--r--android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java1
-rw-r--r--android/arch/persistence/room/integration/testapp/database/CustomerDao.java2
-rw-r--r--android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java47
-rw-r--r--android/arch/persistence/room/integration/testapp/test/InvalidationTest.java120
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java8
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java471
-rw-r--r--android/arch/persistence/room/migration/Migration.java3
-rw-r--r--android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java5
-rw-r--r--android/arch/persistence/room/paging/LimitOffsetDataSource.java36
-rw-r--r--android/arch/persistence/room/util/StringUtil.java7
-rw-r--r--android/content/ContentProvider.java3
-rw-r--r--android/content/ContentResolver.java36
-rw-r--r--android/content/Intent.java52
-rw-r--r--android/content/IntentFilter.java63
-rw-r--r--android/content/pm/FeatureInfo.java13
-rw-r--r--android/content/pm/LauncherApps.java51
-rw-r--r--android/content/pm/PackageManagerInternal.java5
-rw-r--r--android/content/pm/PackageParser.java23
-rw-r--r--android/content/pm/PermissionInfo.java5
-rw-r--r--android/content/pm/ShortcutInfo.java207
-rw-r--r--android/content/pm/ShortcutServiceInternal.java7
-rw-r--r--android/content/res/ResourcesImpl.java2
-rw-r--r--android/database/SQLiteDatabaseIoPerfTest.java176
-rw-r--r--android/database/SQLiteDatabasePerfTest.java223
-rw-r--r--android/graphics/Bitmap.java2
-rw-r--r--android/graphics/BitmapFactory.java3
-rw-r--r--android/media/AudioAttributes.java23
-rw-r--r--android/media/MediaMetadataRetriever.java2
-rw-r--r--android/media/MediaRecorder.java6
-rw-r--r--android/media/tv/TvInputManager.java7
-rw-r--r--android/net/LinkProperties.java30
-rw-r--r--android/net/ip/ConnectivityPacketTracker.java23
-rw-r--r--android/net/ip/IpManager.java26
-rw-r--r--android/net/util/SharedLog.java4
-rw-r--r--android/os/BatteryStats.java757
-rw-r--r--android/os/Debug.java20
-rw-r--r--android/os/ParcelFileDescriptor.java4
-rw-r--r--android/os/PatternMatcher.java16
-rw-r--r--android/os/ServiceManager.java105
-rw-r--r--android/os/SystemProperties.java6
-rw-r--r--android/os/UserManager.java19
-rw-r--r--android/preference/SeekBarVolumizer.java3
-rw-r--r--android/provider/Settings.java25
-rw-r--r--android/service/autofill/AutofillService.java77
-rw-r--r--android/service/autofill/SaveInfo.java88
-rw-r--r--android/service/autofill/SaveRequest.java12
-rw-r--r--android/service/notification/ZenModeConfig.java79
-rw-r--r--android/slice/Slice.java (renamed from android/app/slice/Slice.java)154
-rw-r--r--android/slice/SliceItem.java (renamed from android/app/slice/SliceItem.java)26
-rw-r--r--android/slice/SliceProvider.java (renamed from android/app/slice/SliceProvider.java)38
-rw-r--r--android/slice/SliceQuery.java (renamed from android/app/slice/SliceQuery.java)15
-rw-r--r--android/slice/views/ActionRow.java (renamed from android/app/slice/views/ActionRow.java)8
-rw-r--r--android/slice/views/GridView.java (renamed from android/app/slice/views/GridView.java)18
-rw-r--r--android/slice/views/LargeSliceAdapter.java (renamed from android/app/slice/views/LargeSliceAdapter.java)10
-rw-r--r--android/slice/views/LargeTemplateView.java (renamed from android/app/slice/views/LargeTemplateView.java)15
-rw-r--r--android/slice/views/MessageView.java (renamed from android/app/slice/views/MessageView.java)10
-rw-r--r--android/slice/views/RemoteInputView.java (renamed from android/app/slice/views/RemoteInputView.java)2
-rw-r--r--android/slice/views/ShortcutView.java (renamed from android/app/slice/views/ShortcutView.java)10
-rw-r--r--android/slice/views/SliceView.java (renamed from android/app/slice/views/SliceView.java)18
-rw-r--r--android/slice/views/SliceViewUtil.java (renamed from android/app/slice/views/SliceViewUtil.java)2
-rw-r--r--android/slice/views/SmallTemplateView.java (renamed from android/app/slice/views/SmallTemplateView.java)24
-rw-r--r--android/support/LibraryVersions.java6
-rw-r--r--android/support/car/drawer/CarDrawerActivity.java152
-rw-r--r--android/support/car/drawer/CarDrawerAdapter.java182
-rw-r--r--android/support/car/drawer/CarDrawerController.java306
-rw-r--r--android/support/car/drawer/DrawerItemViewHolder.java87
-rw-r--r--android/support/car/widget/PagedListView.java57
-rw-r--r--android/support/media/tv/BasePreviewProgram.java100
-rw-r--r--android/support/media/tv/BaseProgram.java23
-rw-r--r--android/support/media/tv/Program.java3
-rw-r--r--android/support/media/tv/TvContractCompat.java82
-rw-r--r--android/support/media/tv/WatchNextProgram.java27
-rw-r--r--android/support/mediacompat/testlib/IntentConstants.java2
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java57
-rw-r--r--android/support/transition/AutoTransition.java6
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java18
-rw-r--r--android/support/v4/content/res/ResourcesCompat.java4
-rw-r--r--android/support/v4/media/RatingCompat.java23
-rw-r--r--android/support/v4/media/RatingCompatKitkat.java67
-rw-r--r--android/support/v4/provider/FontsContractCompat.java3
-rw-r--r--android/support/v7/app/MediaRouteButton.java3
-rw-r--r--android/support/v7/app/MediaRouteChooserDialog.java6
-rw-r--r--android/support/v7/app/MediaRouteControllerDialog.java8
-rw-r--r--android/support/v7/app/MediaRouterThemeHelper.java110
-rw-r--r--android/support/v7/util/DiffUtil.java67
-rw-r--r--android/support/v7/widget/AppCompatTextHelper.java37
-rw-r--r--android/support/v7/widget/TintTypedArray.java5
-rw-r--r--android/telephony/CarrierConfigManager.java34
-rw-r--r--android/telephony/MbmsDownloadSession.java41
-rw-r--r--android/telephony/NetworkScanRequest.java105
-rw-r--r--android/telephony/ServiceState.java9
-rw-r--r--android/telephony/mbms/DownloadRequest.java30
-rw-r--r--android/telephony/mbms/MbmsDownloadReceiver.java11
-rw-r--r--android/telephony/mbms/vendor/MbmsDownloadServiceBase.java18
-rw-r--r--android/telephony/mbms/vendor/MbmsStreamingServiceBase.java8
-rw-r--r--android/text/BoringLayoutCreateDrawPerfTest.java2
-rw-r--r--android/text/BoringLayoutIsBoringPerfTest.java2
-rw-r--r--android/text/DynamicLayout.java6
-rw-r--r--android/text/Hyphenator.java251
-rw-r--r--android/text/Layout.java14
-rw-r--r--android/text/PaintMeasureDrawPerfTest.java2
-rw-r--r--android/text/StaticLayout.java74
-rw-r--r--android/text/StaticLayoutCreateDrawPerfTest.java2
-rw-r--r--android/text/StaticLayout_Delegate.java26
-rw-r--r--android/text/TextLine.java10
-rw-r--r--android/text/TextViewSetTextMeasurePerfTest.java4
-rw-r--r--android/util/Log.java295
-rw-r--r--android/util/LruCache.java25
-rw-r--r--android/util/StatsLog.java76
-rw-r--r--android/util/StatsLogKey.java48
-rw-r--r--android/util/StatsLogTag.java (renamed from android/support/car/drawer/DrawerItemClickListener.java)25
-rw-r--r--android/util/StatsLogValue.java54
-rw-r--r--android/view/SurfaceControl.java538
-rw-r--r--android/view/SurfaceView.java1135
-rw-r--r--android/view/View.java262
-rw-r--r--android/view/ViewDebug.java167
-rw-r--r--android/view/ViewGroup.java98
-rw-r--r--android/view/ViewRootImpl.java758
-rw-r--r--android/view/ViewStructure.java24
-rw-r--r--android/view/WindowManagerInternal.java4
-rw-r--r--android/view/WindowManagerPolicy.java7
-rw-r--r--android/view/accessibility/AccessibilityCache.java2
-rw-r--r--android/view/accessibility/AccessibilityManager.java916
-rw-r--r--android/view/autofill/AutofillManager.java149
-rw-r--r--android/view/textclassifier/TextClassifier.java8
-rw-r--r--android/view/textclassifier/TextClassifierConstants.java90
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java14
-rw-r--r--android/view/textservice/TextServicesManager.java200
-rw-r--r--android/webkit/WebView.java2878
-rw-r--r--android/widget/Editor.java21
-rw-r--r--android/widget/RemoteViews.java6
-rw-r--r--android/widget/SelectionActionModeHelper.java30
-rw-r--r--android/widget/TextView.java41
218 files changed, 9379 insertions, 9650 deletions
diff --git a/android/accessibilityservice/GestureDescription.java b/android/accessibilityservice/GestureDescription.java
index 56f4ae2b..92567d75 100644
--- a/android/accessibilityservice/GestureDescription.java
+++ b/android/accessibilityservice/GestureDescription.java
@@ -428,18 +428,6 @@ 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 85f73bb7..e0ac9113 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 email, a new entry for that email is created as soon as they
+ * a new e-mail, a new entry for that e-mail is created as soon as they
* start entering data, so that if they go to any other activity after
- * that point this email will now appear in the list of drafts.</p>
+ * that point this e-mail 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().onActivityFinished();
+ getAutofillManager().commit();
} 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,8 +6259,6 @@ 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 fc4c8d7f..5e61727f 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -16,8 +16,14 @@
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;
@@ -664,6 +670,138 @@ 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;
+ }
}
/**
@@ -942,14 +1080,11 @@ public class ActivityManager {
ATTR_TASKDESCRIPTION_PREFIX + "color";
private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
- private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
+ private static final String ATTR_TASKDESCRIPTIONICONFILENAME =
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;
@@ -963,27 +1098,9 @@ 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, 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);
+ this(label, icon, null, colorPrimary, 0, 0, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -994,22 +1111,9 @@ 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, 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);
+ this(label, icon, null, 0, 0, 0, 0);
}
/**
@@ -1018,22 +1122,21 @@ public class ActivityManager {
* @param label A label and description of the current state of this activity.
*/
public TaskDescription(String label) {
- this(label, null, 0, null, 0, 0, 0, 0);
+ this(label, null, null, 0, 0, 0, 0);
}
/**
* Creates an empty TaskDescription.
*/
public TaskDescription() {
- this(null, null, 0, null, 0, 0, 0, 0);
+ this(null, null, null, 0, 0, 0, 0);
}
/** @hide */
- public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename,
- int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor) {
+ public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary,
+ int colorBackground, int statusBarColor, int navigationBarColor) {
mLabel = label;
- mIcon = bitmap;
- mIconRes = iconRes;
+ mIcon = icon;
mIconFilename = iconFilename;
mColorPrimary = colorPrimary;
mColorBackground = colorBackground;
@@ -1055,7 +1158,6 @@ 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;
@@ -1071,7 +1173,6 @@ 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) {
@@ -1144,14 +1245,6 @@ 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
@@ -1179,13 +1272,6 @@ public class ActivityManager {
}
/** @hide */
- @TestApi
- public int getIconResource() {
- return mIconRes;
- }
-
- /** @hide */
- @TestApi
public String getIconFilename() {
return mIconFilename;
}
@@ -1251,10 +1337,7 @@ public class ActivityManager {
Integer.toHexString(mColorBackground));
}
if (mIconFilename != null) {
- out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
- }
- if (mIconRes != 0) {
- out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes));
+ out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename);
}
}
@@ -1266,10 +1349,8 @@ 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_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) {
+ } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) {
setIconFilename(attrValue);
- } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) {
- setIcon(Integer.parseInt(attrValue, 10));
}
}
@@ -1292,7 +1373,6 @@ public class ActivityManager {
dest.writeInt(1);
mIcon.writeToParcel(dest, 0);
}
- dest.writeInt(mIconRes);
dest.writeInt(mColorPrimary);
dest.writeInt(mColorBackground);
dest.writeInt(mStatusBarColor);
@@ -1308,7 +1388,6 @@ 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();
@@ -1329,8 +1408,8 @@ public class ActivityManager {
@Override
public String toString() {
return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
- " IconRes: " + mIconRes + " IconFilename: " + mIconFilename +
- " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground +
+ " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary +
+ " colorBackground: " + mColorBackground +
" statusBarColor: " + mColorBackground +
" navigationBarColor: " + mNavigationBarColor;
}
@@ -1492,6 +1571,7 @@ public class ActivityManager {
}
dest.writeInt(stackId);
dest.writeInt(userId);
+ dest.writeLong(firstActiveTime);
dest.writeLong(lastActiveTime);
dest.writeInt(affiliatedTaskId);
dest.writeInt(affiliatedTaskColor);
@@ -1520,6 +1600,7 @@ 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();
@@ -1562,6 +1643,31 @@ 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.
*
@@ -1596,7 +1702,33 @@ public class ActivityManager {
public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
throws SecurityException {
try {
- return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
+ 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();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1889,6 +2021,22 @@ 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 b62e4c2d..a68c3a5c 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,12 +159,6 @@ 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
@@ -285,7 +279,6 @@ 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;
@@ -877,7 +870,6 @@ 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);
@@ -1064,37 +1056,6 @@ 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.
@@ -1287,7 +1248,6 @@ public class ActivityOptions {
mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
break;
}
- mLockTaskMode = otherOptions.mLockTaskMode;
mAnimSpecs = otherOptions.mAnimSpecs;
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
@@ -1362,7 +1322,6 @@ 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 1fe29004..54f74b15 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -387,6 +387,8 @@ 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 c06ad3f3..47063f08 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -32,8 +32,6 @@ 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 a52dc1e4..eb52cb7f 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -934,14 +934,8 @@ 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,
@@ -1141,9 +1135,6 @@ 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 23c4166d..8987bc02 100644
--- a/android/app/StatusBarManager.java
+++ b/android/app/StatusBarManager.java
@@ -73,16 +73,15 @@ 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_GLOBAL_ACTIONS;
+ | DISABLE2_NOTIFICATION_SHADE;
@IntDef(flag = true,
value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS,
- DISABLE2_NOTIFICATION_SHADE, DISABLE2_GLOBAL_ACTIONS})
+ DISABLE2_NOTIFICATION_SHADE})
@Retention(RetentionPolicy.SOURCE)
public @interface Disable2Flags {}
diff --git a/android/app/TaskStackListener.java b/android/app/TaskStackListener.java
index 895d12a7..402e2095 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) throws RemoteException {
+ public void onTaskRemovalStarted(int taskId) {
}
@Override
@@ -91,10 +91,11 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
+ public void onTaskProfileLocked(int taskId, int userId) {
}
@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 081bd814..942cc995 100644
--- a/android/app/WallpaperManager.java
+++ b/android/app/WallpaperManager.java
@@ -388,12 +388,11 @@ public class WallpaperManager {
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
@SetWallpaperFlags int which) {
- return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
- false /* hardware */);
+ return peekWallpaperBitmap(context, returnDefault, which, context.getUserId());
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
- @SetWallpaperFlags int which, int userId, boolean hardware) {
+ @SetWallpaperFlags int which, int userId) {
if (mService != null) {
try {
if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -410,7 +409,7 @@ public class WallpaperManager {
mCachedWallpaper = null;
mCachedWallpaperUserId = 0;
try {
- mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
+ mCachedWallpaper = getCurrentWallpaperLocked(context, userId);
mCachedWallpaperUserId = userId;
} catch (OutOfMemoryError e) {
Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -448,7 +447,7 @@ public class WallpaperManager {
}
}
- private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
+ private Bitmap getCurrentWallpaperLocked(Context context, int userId) {
if (mService == null) {
Log.w(TAG, "WallpaperService not running");
return null;
@@ -461,9 +460,6 @@ 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) {
@@ -818,23 +814,12 @@ public class WallpaperManager {
}
/**
- * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
- *
- * @hide
- */
- public Bitmap getBitmap() {
- 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);
+ public Bitmap getBitmap() {
+ return getBitmapAsUser(mContext.getUserId());
}
/**
@@ -842,8 +827,8 @@ public class WallpaperManager {
*
* @hide
*/
- public Bitmap getBitmapAsUser(int userId, boolean hardware) {
- return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
+ public Bitmap getBitmapAsUser(int userId) {
+ return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
}
/**
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 251863ca..6b405384 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -511,8 +511,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
}
- /** @hide */
- public static String windowingModeToString(@WindowingMode int windowingMode) {
+ private static String windowingModeToString(@WindowingMode int windowingMode) {
switch (windowingMode) {
case WINDOWING_MODE_UNDEFINED: return "undefined";
case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index e491a4f9..d9b7cd7e 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -616,9 +616,6 @@ 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.
@@ -716,9 +713,6 @@ 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();
@@ -882,9 +876,6 @@ 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);
@@ -1453,39 +1444,6 @@ 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;
- }
}
/**
@@ -1818,21 +1776,6 @@ 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 0deb2e13..3868439f 100644
--- a/android/app/job/JobScheduler.java
+++ b/android/app/job/JobScheduler.java
@@ -24,6 +24,7 @@ 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;
@@ -39,18 +40,16 @@ 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 the service component that implements the logic for your job when you
- * construct the JobInfo using
+ * You identify which JobService is meant to execute the logic for your job when you create the
+ * JobInfo with
* {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}.
* </p>
* <p>
- * 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.
+ * 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.
* </p>
* <p>You do not
* instantiate this class directly; instead, retrieve it through
@@ -142,34 +141,30 @@ public abstract class JobScheduler {
int userId, String tag);
/**
- * 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)}.
+ * 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()}.
*/
public abstract void cancel(int jobId);
/**
- * Cancel <em>all</em> jobs that have been scheduled by the calling application.
+ * Cancel all jobs that have been registered with the JobScheduler by this package.
*/
public abstract void cancelAll();
/**
- * Retrieve all jobs that have been scheduled by the calling application.
+ * Retrieve all jobs for this package that are pending in the JobScheduler.
*
- * @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.
+ * @return a list of all the jobs registered by this package that have not
+ * yet been executed.
*/
public abstract @NonNull List<JobInfo> getAllPendingJobs();
/**
- * Look up the description of a scheduled job.
+ * Retrieve a specific job for this package that is pending in the
+ * JobScheduler.
*
- * @return The {@link JobInfo} description of the given scheduled job, or {@code null}
- * if the supplied job ID does not correspond to any job.
+ * @return job registered by this package that has not yet been executed.
*/
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java
index 69afed20..9096b47b 100644
--- a/android/app/job/JobService.java
+++ b/android/app/job/JobService.java
@@ -18,7 +18,16 @@ 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>
@@ -46,7 +55,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 system.
+ * permission, that service will be ignored by the OS.
*/
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
@@ -72,36 +81,14 @@ public abstract class JobService extends Service {
}
/**
- * 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.
+ * 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.
*
- * @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.
+ * @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.
*/
public abstract boolean onStartJob(JobParameters params);
@@ -114,44 +101,37 @@ 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 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>
+ * 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>
*
- * @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.
+ * @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.
*/
public abstract boolean onStopJob(JobParameters params);
/**
- * Call this to inform the JobScheduler that the job has finished its work. When the
- * system receives this message, it releases the wakelock being held for the job.
+ * 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.
* <p>
- * You can request that the job be scheduled again by passing {@code true} as
- * the <code>wantsReschedule</code> parameter. This will apply back-off policy
- * for the job; this policy can be adjusted through the
- * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
- * when the job is originally scheduled. The job's initial
- * requirements are preserved when jobs are rescheduled, regardless of backed-off
- * policy.
- * <p class="note">
- * A job running while the device is dozing will not be rescheduled with the normal back-off
- * policy. Instead, the job will be re-added to the queue and executed again during
- * a future idle maintenance window.
+ * 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.
* </p>
*
- * @param params The parameters identifying this job, as supplied to
- * the job in the {@link #onStartJob(JobParameters)} callback.
- * @param wantsReschedule {@code true} if this job should be rescheduled according
- * to the back-off criteria specified when it was first scheduled; {@code false}
- * otherwise.
+ * @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.
*/
- public final void jobFinished(JobParameters params, boolean wantsReschedule) {
- mEngine.jobFinished(params, wantsReschedule);
+ public final void jobFinished(JobParameters params, boolean needsReschedule) {
+ mEngine.jobFinished(params, needsReschedule);
}
-}
+} \ No newline at end of file
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index fd579fce..051dccbd 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.
- * 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}
+ * <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.
*/
@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}
+ * @return A list of {@link UsageStats} or null if none are available.
*
* @see #INTERVAL_DAILY
* @see #INTERVAL_WEEKLY
@@ -139,7 +139,7 @@ public final class UsageStatsManager {
return slice.getList();
}
} catch (RemoteException e) {
- // fallthrough and return the empty list.
+ // fallthrough and return null.
}
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}
+ * @return A list of {@link ConfigurationStats} or null if none are available.
*/
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 empty result.
+ // fallthrough and return null
}
return sEmptyResults;
}
@@ -197,7 +197,8 @@ 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
+ * @return A {@link java.util.Map} keyed by package name, or null if no stats are
+ * available.
*/
public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
diff --git a/android/arch/lifecycle/ActivityFullLifecycleTest.java b/android/arch/lifecycle/ActivityFullLifecycleTest.java
index 78dd0150..ee4e661a 100644
--- a/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ b/android/arch/lifecycle/ActivityFullLifecycleTest.java
@@ -16,43 +16,48 @@
package android.arch.lifecycle;
-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.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 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.CollectingLifecycleOwner;
-import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingActivity;
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.support.v4.util.Pair;
+import android.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 final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule;
+ public ActivityTestRule activityTestRule =
+ new ActivityTestRule<>(FullLifecycleTestActivity.class);
@Parameterized.Parameters
public static Class[] params() {
- return new Class[]{CollectingSupportActivity.class,
+ return new Class[]{FullLifecycleTestActivity.class,
+ SupportLifecycleRegistryActivity.class,
FrameworkLifecycleRegistryActivity.class};
}
@@ -63,13 +68,28 @@ public class ActivityFullLifecycleTest {
@Test
- public void testFullLifecycle() throws Throwable {
- CollectingLifecycleOwner owner = activityTestRule.getActivity();
- TestUtils.waitTillResumed(owner, activityTestRule);
- activityTestRule.finishActivity();
+ public void testFullLifecycle() throws InterruptedException {
+ Activity activity = activityTestRule.getActivity();
+ List<Pair<TestEvent, Event>> results = ((CollectingActivity) activity)
+ .waitForCollectedEvents();
- TestUtils.waitTillDestroyed(owner, activityTestRule);
- List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents();
- assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
+ 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));
}
}
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 106b2ef0..2c7e1739 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -16,9 +16,7 @@
package android.arch.lifecycle;
-import android.annotation.SuppressLint;
import android.app.Application;
-import android.support.annotation.NonNull;
/**
* Application context aware {@link ViewModel}.
@@ -27,19 +25,16 @@ import android.support.annotation.NonNull;
* <p>
*/
public class AndroidViewModel extends ViewModel {
- @SuppressLint("StaticFieldLeak")
private Application mApplication;
- public AndroidViewModel(@NonNull Application application) {
+ public AndroidViewModel(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 d88e2762..f077daed 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 = getDeclaredMethods(klass);
+ Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation != null) {
@@ -64,18 +64,6 @@ 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) {
@@ -118,7 +106,7 @@ class ClassesInfoCache {
}
}
- Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
+ Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
boolean hasLifecycleMethods = false;
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index 1ddcb1a9..f1352446 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,136 +1,9 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+//ComputableLiveData interface for tests
package android.arch.lifecycle;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A LiveData class that can be invalidated & computed on demand.
- * <p>
- * This is an internal class for now, might be public if we see the necessity.
- *
- * @param <T> The type of the live data
- * @hide internal
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+import android.arch.lifecycle.LiveData;
public abstract class ComputableLiveData<T> {
-
- private final LiveData<T> mLiveData;
-
- private AtomicBoolean mInvalid = new AtomicBoolean(true);
- private AtomicBoolean mComputing = new AtomicBoolean(false);
-
- /**
- * Creates a computable live data which is computed when there are active observers.
- * <p>
- * It can also be invalidated via {@link #invalidate()} which will result in a call to
- * {@link #compute()} if there are active observers (or when they start observing)
- */
- @SuppressWarnings("WeakerAccess")
- public ComputableLiveData() {
- mLiveData = new LiveData<T>() {
- @Override
- protected void onActive() {
- // TODO if we make this class public, we should accept an executor
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- };
- }
-
- /**
- * Returns the LiveData managed by this class.
- *
- * @return A LiveData that is controlled by ComputableLiveData.
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LiveData<T> getLiveData() {
- return mLiveData;
- }
-
- @VisibleForTesting
- final Runnable mRefreshRunnable = new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- boolean computed;
- do {
- computed = false;
- // compute can happen only in 1 thread but no reason to lock others.
- if (mComputing.compareAndSet(false, true)) {
- // as long as it is invalid, keep computing.
- try {
- T value = null;
- while (mInvalid.compareAndSet(true, false)) {
- computed = true;
- value = compute();
- }
- if (computed) {
- mLiveData.postValue(value);
- }
- } finally {
- // release compute lock
- mComputing.set(false);
- }
- }
- // check invalid after releasing compute lock to avoid the following scenario.
- // Thread A runs compute()
- // Thread A checks invalid, it is false
- // Main thread sets invalid to true
- // Thread B runs, fails to acquire compute lock and skips
- // Thread A releases compute lock
- // We've left invalid in set state. The check below recovers.
- } while (computed && mInvalid.get());
- }
- };
-
- // invalidation check always happens on the main thread
- @VisibleForTesting
- final Runnable mInvalidationRunnable = new Runnable() {
- @MainThread
- @Override
- public void run() {
- boolean isActive = mLiveData.hasActiveObservers();
- if (mInvalid.compareAndSet(false, true)) {
- if (isActive) {
- // TODO if we make this class public, we should accept an executor.
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- }
- }
- };
-
- /**
- * Invalidates the LiveData.
- * <p>
- * When there are active observers, this will trigger a call to {@link #compute()}.
- */
- public void invalidate() {
- ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
- }
-
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- protected abstract T compute();
+ public ComputableLiveData(){}
+ abstract protected T compute();
+ public LiveData<T> getLiveData() {return null;}
+ public void invalidate() {}
}
diff --git a/android/arch/lifecycle/DispatcherActivityCallbackTest.java b/android/arch/lifecycle/DispatcherActivityCallbackTest.java
new file mode 100644
index 00000000..86b25b60
--- /dev/null
+++ b/android/arch/lifecycle/DispatcherActivityCallbackTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DispatcherActivityCallbackTest {
+ @Test
+ public void onCreateFrameworkActivity() {
+ LifecycleDispatcher.DispatcherActivityCallback callback =
+ new LifecycleDispatcher.DispatcherActivityCallback();
+ Activity activity = mock(Activity.class);
+ checkReportFragment(callback, activity);
+ }
+
+ @Test
+ public void onCreateFragmentActivity() {
+ LifecycleDispatcher.DispatcherActivityCallback callback =
+ new LifecycleDispatcher.DispatcherActivityCallback();
+ FragmentActivity activity = mock(FragmentActivity.class);
+ FragmentManager fragmentManager = mock(FragmentManager.class);
+ when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
+
+ checkReportFragment(callback, activity);
+
+ verify(activity).getSupportFragmentManager();
+ verify(fragmentManager).registerFragmentLifecycleCallbacks(
+ any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true));
+ }
+
+ @SuppressLint("CommitTransaction")
+ private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback,
+ Activity activity) {
+ android.app.FragmentManager fm = mock(android.app.FragmentManager.class);
+ FragmentTransaction transaction = mock(FragmentTransaction.class);
+ when(activity.getFragmentManager()).thenReturn(fm);
+ when(fm.beginTransaction()).thenReturn(transaction);
+ when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction);
+ callback.onActivityCreated(activity, mock(Bundle.class));
+ verify(activity).getFragmentManager();
+ verify(fm).beginTransaction();
+ verify(transaction).add(any(ReportFragment.class), anyString());
+ verify(transaction).commit();
+ }
+}
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index c0a2090c..02db5ff9 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -17,7 +17,6 @@
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}
@@ -84,7 +83,7 @@ public abstract class Lifecycle {
* @param observer The observer to notify.
*/
@MainThread
- public abstract void addObserver(@NonNull LifecycleObserver observer);
+ public abstract void addObserver(LifecycleObserver observer);
/**
* Removes the given observer from the observers list.
@@ -100,7 +99,7 @@ public abstract class Lifecycle {
* @param observer The observer to be removed.
*/
@MainThread
- public abstract void removeObserver(@NonNull LifecycleObserver observer);
+ public abstract void removeObserver(LifecycleObserver observer);
/**
* Returns the current state of the Lifecycle.
@@ -194,7 +193,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(@NonNull State state) {
+ public boolean isAtLeast(State state) {
return compareTo(state) >= 0;
}
}
diff --git a/android/arch/lifecycle/LifecycleDispatcher.java b/android/arch/lifecycle/LifecycleDispatcher.java
new file mode 100644
index 00000000..9fdec959
--- /dev/null
+++ b/android/arch/lifecycle/LifecycleDispatcher.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.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+
+import android.app.Activity;
+import android.app.Application;
+import android.arch.lifecycle.Lifecycle.State;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * When initialized, it hooks into the Activity callback of the Application and observes
+ * Activities. It is responsible to hook in child-fragments to activities and fragments to report
+ * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle
+ * providers related to an activity as soon it is not safe to run a fragment transaction in this
+ * activity.
+ */
+class LifecycleDispatcher {
+
+ private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ + ".LifecycleDispatcher.report_fragment_tag";
+
+ private static AtomicBoolean sInitialized = new AtomicBoolean(false);
+
+ static void init(Context context) {
+ if (sInitialized.getAndSet(true)) {
+ return;
+ }
+ ((Application) context.getApplicationContext())
+ .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @VisibleForTesting
+ static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
+ private final FragmentCallback mFragmentCallback;
+
+ DispatcherActivityCallback() {
+ mFragmentCallback = new FragmentCallback();
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ if (activity instanceof FragmentActivity) {
+ ((FragmentActivity) activity).getSupportFragmentManager()
+ .registerFragmentLifecycleCallbacks(mFragmentCallback, true);
+ }
+ ReportFragment.injectIfNeededIn(activity);
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ if (activity instanceof FragmentActivity) {
+ markState((FragmentActivity) activity, CREATED);
+ }
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+ if (activity instanceof FragmentActivity) {
+ markState((FragmentActivity) activity, CREATED);
+ }
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public static class DestructionReportFragment extends Fragment {
+ @Override
+ public void onPause() {
+ super.onPause();
+ dispatch(ON_PAUSE);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ dispatch(ON_STOP);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ dispatch(ON_DESTROY);
+ }
+
+ protected void dispatch(Lifecycle.Event event) {
+ dispatchIfLifecycleOwner(getParentFragment(), event);
+ }
+ }
+
+ private static void markState(FragmentManager manager, State state) {
+ Collection<Fragment> fragments = manager.getFragments();
+ if (fragments == null) {
+ return;
+ }
+ for (Fragment fragment : fragments) {
+ if (fragment == null) {
+ continue;
+ }
+ markStateIn(fragment, state);
+ if (fragment.isAdded()) {
+ markState(fragment.getChildFragmentManager(), state);
+ }
+ }
+ }
+
+ private static void markStateIn(Object object, State state) {
+ if (object instanceof LifecycleRegistryOwner) {
+ LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle();
+ registry.markState(state);
+ }
+ }
+
+ private static void markState(FragmentActivity activity, State state) {
+ markStateIn(activity, state);
+ markState(activity.getSupportFragmentManager(), state);
+ }
+
+ private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) {
+ if (fragment instanceof LifecycleRegistryOwner) {
+ ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @VisibleForTesting
+ static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks {
+
+ @Override
+ public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
+ dispatchIfLifecycleOwner(f, ON_CREATE);
+
+ if (!(f instanceof LifecycleRegistryOwner)) {
+ return;
+ }
+
+ if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
+ f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(),
+ REPORT_FRAGMENT_TAG).commit();
+ }
+ }
+
+ @Override
+ public void onFragmentStarted(FragmentManager fm, Fragment f) {
+ dispatchIfLifecycleOwner(f, ON_START);
+ }
+
+ @Override
+ public void onFragmentResumed(FragmentManager fm, Fragment f) {
+ dispatchIfLifecycleOwner(f, ON_RESUME);
+ }
+ }
+}
diff --git a/android/arch/lifecycle/LifecycleOwner.java b/android/arch/lifecycle/LifecycleOwner.java
index 068bac1b..934cf3a2 100644
--- a/android/arch/lifecycle/LifecycleOwner.java
+++ b/android/arch/lifecycle/LifecycleOwner.java
@@ -16,8 +16,6 @@
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.
@@ -31,6 +29,5 @@ 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 bf8aff79..b83e6b8a 100644
--- a/android/arch/lifecycle/LifecycleRegistry.java
+++ b/android/arch/lifecycle/LifecycleRegistry.java
@@ -29,12 +29,9 @@ 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;
@@ -47,8 +44,6 @@ 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.
*
@@ -64,12 +59,8 @@ 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 WeakReference<LifecycleOwner> mLifecycleOwner;
+ private final LifecycleOwner mLifecycleOwner;
private int mAddingObserverCounter = 0;
@@ -95,19 +86,19 @@ public class LifecycleRegistry extends Lifecycle {
* @param provider The owner LifecycleOwner
*/
public LifecycleRegistry(@NonNull LifecycleOwner provider) {
- mLifecycleOwner = new WeakReference<>(provider);
+ mLifecycleOwner = provider;
mState = INITIALIZED;
}
/**
- * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
+ * Only marks the current state as the given value. It doesn't dispatch any event to its
+ * listeners.
*
* @param state new state
*/
@SuppressWarnings("WeakerAccess")
- @MainThread
- public void markState(@NonNull State state) {
- moveToState(state);
+ public void markState(State state) {
+ mState = state;
}
/**
@@ -118,16 +109,8 @@ public class LifecycleRegistry extends Lifecycle {
*
* @param event The event that was received
*/
- public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
- State next = getStateAfter(event);
- moveToState(next);
- }
-
- private void moveToState(State next) {
- if (mState == next) {
- return;
- }
- mState = next;
+ public void handleLifecycleEvent(Lifecycle.Event event) {
+ mState = getStateAfter(event);
if (mHandlingEvent || mAddingObserverCounter != 0) {
mNewEventOccurred = true;
// we will figure out what to do on upper level.
@@ -157,7 +140,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void addObserver(@NonNull LifecycleObserver observer) {
+ public void addObserver(LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
@@ -165,19 +148,15 @@ 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(lifecycleOwner, upEvent(statefulObserver.mState));
+ statefulObserver.dispatchEvent(mLifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
@@ -199,7 +178,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void removeObserver(@NonNull LifecycleObserver observer) {
+ public void removeObserver(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
@@ -279,7 +258,7 @@ public class LifecycleRegistry extends Lifecycle {
throw new IllegalArgumentException("Unexpected state value " + state);
}
- private void forwardPass(LifecycleOwner lifecycleOwner) {
+ private void forwardPass() {
Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
mObserverMap.iteratorWithAdditions();
while (ascendingIterator.hasNext() && !mNewEventOccurred) {
@@ -288,13 +267,13 @@ public class LifecycleRegistry extends Lifecycle {
while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
&& mObserverMap.contains(entry.getKey()))) {
pushParentState(observer.mState);
- observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
+ observer.dispatchEvent(mLifecycleOwner, upEvent(observer.mState));
popParentState();
}
}
}
- private void backwardPass(LifecycleOwner lifecycleOwner) {
+ private void backwardPass() {
Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
mObserverMap.descendingIterator();
while (descendingIterator.hasNext() && !mNewEventOccurred) {
@@ -304,7 +283,7 @@ public class LifecycleRegistry extends Lifecycle {
&& mObserverMap.contains(entry.getKey()))) {
Event event = downEvent(observer.mState);
pushParentState(getStateAfter(event));
- observer.dispatchEvent(lifecycleOwner, event);
+ observer.dispatchEvent(mLifecycleOwner, event);
popParentState();
}
}
@@ -313,22 +292,16 @@ 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(lifecycleOwner);
+ backwardPass();
}
Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
if (!mNewEventOccurred && newest != null
&& mState.compareTo(newest.getValue().mState) > 0) {
- forwardPass(lifecycleOwner);
+ forwardPass();
}
}
mNewEventOccurred = false;
diff --git a/android/arch/lifecycle/LifecycleRegistryOwner.java b/android/arch/lifecycle/LifecycleRegistryOwner.java
index 0c67fefe..38eeb6d3 100644
--- a/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ b/android/arch/lifecycle/LifecycleRegistryOwner.java
@@ -16,8 +16,6 @@
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.
@@ -25,7 +23,6 @@ import android.support.annotation.NonNull;
@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 2a7bbad2..6506454d 100644
--- a/android/arch/lifecycle/LifecycleRegistryTest.java
+++ b/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -566,25 +566,6 @@ 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/ProcessLifecycleOwnerInitializer.java b/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
index 8ba297fe..ac278c0c 100644
--- a/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
+++ b/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
@@ -29,9 +29,10 @@ import android.support.annotation.RestrictTo;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ProcessLifecycleOwnerInitializer extends ContentProvider {
+public class LifecycleRuntimeTrojanProvider extends ContentProvider {
@Override
public boolean onCreate() {
+ LifecycleDispatcher.init(getContext());
ProcessLifecycleOwner.init(getContext());
return true;
}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 5b09c32f..3aea6acb 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,410 +1,4 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+//LiveData interface for tests
package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * LiveData is a data holder class that can be observed within a given lifecycle.
- * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
- * this observer will be notified about modifications of the wrapped data only if the paired
- * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
- * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
- * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
- * about modifications. For those observers, you should manually call
- * {@link #removeObserver(Observer)}.
- *
- * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
- * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
- * activities and fragments where they can safely observe LiveData and not worry about leaks:
- * they will be instantly unsubscribed when they are destroyed.
- *
- * <p>
- * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
- * to get notified when number of active {@link Observer}s change between 0 and 1.
- * This allows LiveData to release any heavy resources when it does not have any Observers that
- * are actively observing.
- * <p>
- * This class is designed to hold individual data fields of {@link ViewModel},
- * but can also be used for sharing data between different modules in your application
- * in a decoupled fashion.
- *
- * @param <T> The type of data held by this instance
- * @see ViewModel
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
-// thread.
-public abstract class LiveData<T> {
- private final Object mDataLock = new Object();
- static final int START_VERSION = -1;
- private static final Object NOT_SET = new Object();
-
- private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
-
- private LifecycleRegistry mRegistry = init();
-
- private LifecycleRegistry init() {
- LifecycleRegistry registry = new LifecycleRegistry(this);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
- return registry;
- }
-
- @Override
- public Lifecycle getLifecycle() {
- return mRegistry;
- }
- };
-
- private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
- new SafeIterableMap<>();
-
- // how many observers are in active state
- private int mActiveCount = 0;
- private volatile Object mData = NOT_SET;
- // when setData is called, we set the pending data and actual data swap happens on the main
- // thread
- private volatile Object mPendingData = NOT_SET;
- private int mVersion = START_VERSION;
-
- private boolean mDispatchingValue;
- @SuppressWarnings("FieldCanBeLocal")
- private boolean mDispatchInvalidated;
- private final Runnable mPostValueRunnable = new Runnable() {
- @Override
- public void run() {
- Object newValue;
- synchronized (mDataLock) {
- newValue = mPendingData;
- mPendingData = NOT_SET;
- }
- //noinspection unchecked
- setValue((T) newValue);
- }
- };
-
- private void considerNotify(LifecycleBoundObserver observer) {
- if (!observer.active) {
- return;
- }
- // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
- //
- // we still first check observer.active to keep it as the entrance for events. So even if
- // the observer moved to an active state, if we've not received that event, we better not
- // notify for a more predictable notification order.
- if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
- observer.activeStateChanged(false);
- return;
- }
- if (observer.lastVersion >= mVersion) {
- return;
- }
- observer.lastVersion = mVersion;
- //noinspection unchecked
- observer.observer.onChanged((T) mData);
- }
-
- private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
- if (mDispatchingValue) {
- mDispatchInvalidated = true;
- return;
- }
- mDispatchingValue = true;
- do {
- mDispatchInvalidated = false;
- if (initiator != null) {
- considerNotify(initiator);
- initiator = null;
- } else {
- for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
- mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
- considerNotify(iterator.next().getValue());
- if (mDispatchInvalidated) {
- break;
- }
- }
- }
- } while (mDispatchInvalidated);
- mDispatchingValue = false;
- }
-
- /**
- * Adds the given observer to the observers list within the lifespan of the given
- * owner. The events are dispatched on the main thread. If LiveData already has data
- * set, it will be delivered to the observer.
- * <p>
- * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
- * or {@link Lifecycle.State#RESUMED} state (active).
- * <p>
- * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
- * automatically be removed.
- * <p>
- * When data changes while the {@code owner} is not active, it will not receive any updates.
- * If it becomes active again, it will receive the last available data automatically.
- * <p>
- * LiveData keeps a strong reference to the observer and the owner as long as the
- * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
- * the observer &amp; the owner.
- * <p>
- * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
- * ignores the call.
- * <p>
- * If the given owner, observer tuple is already in the list, the call is ignored.
- * If the observer is already in the list with another owner, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param owner The LifecycleOwner which controls the observer
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- // ignore
- return;
- }
- LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
- LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
- if (existing != null && existing.owner != wrapper.owner) {
- throw new IllegalArgumentException("Cannot add the same observer"
- + " with different lifecycles");
- }
- if (existing != null) {
- return;
- }
- owner.getLifecycle().addObserver(wrapper);
- }
-
- /**
- * Adds the given observer to the observers list. This call is similar to
- * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
- * is always active. This means that the given observer will receive all events and will never
- * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
- * observing this LiveData.
- * While LiveData has one of such observers, it will be considered
- * as active.
- * <p>
- * If the observer was already added with an owner to this LiveData, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observeForever(@NonNull Observer<T> observer) {
- observe(ALWAYS_ON, observer);
- }
-
- /**
- * Removes the given observer from the observers list.
- *
- * @param observer The Observer to receive events.
- */
- @MainThread
- public void removeObserver(@NonNull final Observer<T> observer) {
- assertMainThread("removeObserver");
- LifecycleBoundObserver removed = mObservers.remove(observer);
- if (removed == null) {
- return;
- }
- removed.owner.getLifecycle().removeObserver(removed);
- removed.activeStateChanged(false);
- }
-
- /**
- * Removes all observers that are tied to the given {@link LifecycleOwner}.
- *
- * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
- */
- @MainThread
- public void removeObservers(@NonNull final LifecycleOwner owner) {
- assertMainThread("removeObservers");
- for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
- if (entry.getValue().owner == owner) {
- removeObserver(entry.getKey());
- }
- }
- }
-
- /**
- * Posts a task to a main thread to set the given value. So if you have a following code
- * executed in the main thread:
- * <pre class="prettyprint">
- * liveData.postValue("a");
- * liveData.setValue("b");
- * </pre>
- * The value "b" would be set at first and later the main thread would override it with
- * the value "a".
- * <p>
- * If you called this method multiple times before a main thread executed a posted task, only
- * the last value would be dispatched.
- *
- * @param value The new value
- */
- protected void postValue(T value) {
- boolean postTask;
- synchronized (mDataLock) {
- postTask = mPendingData == NOT_SET;
- mPendingData = value;
- }
- if (!postTask) {
- return;
- }
- ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
- }
-
- /**
- * Sets the value. If there are active observers, the value will be dispatched to them.
- * <p>
- * This method must be called from the main thread. If you need set a value from a background
- * thread, you can use {@link #postValue(Object)}
- *
- * @param value The new value
- */
- @MainThread
- protected void setValue(T value) {
- assertMainThread("setValue");
- mVersion++;
- mData = value;
- dispatchingValue(null);
- }
-
- /**
- * Returns the current value.
- * Note that calling this method on a background thread does not guarantee that the latest
- * value set will be received.
- *
- * @return the current value
- */
- @Nullable
- public T getValue() {
- Object data = mData;
- if (data != NOT_SET) {
- //noinspection unchecked
- return (T) data;
- }
- return null;
- }
-
- int getVersion() {
- return mVersion;
- }
-
- /**
- * Called when the number of active observers change to 1 from 0.
- * <p>
- * This callback can be used to know that this LiveData is being used thus should be kept
- * up to date.
- */
- protected void onActive() {
-
- }
-
- /**
- * Called when the number of active observers change from 1 to 0.
- * <p>
- * This does not mean that there are no observers left, there may still be observers but their
- * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
- * (like an Activity in the back stack).
- * <p>
- * You can check if there are observers via {@link #hasObservers()}.
- */
- protected void onInactive() {
-
- }
-
- /**
- * Returns true if this LiveData has observers.
- *
- * @return true if this LiveData has observers
- */
- public boolean hasObservers() {
- return mObservers.size() > 0;
- }
-
- /**
- * Returns true if this LiveData has active observers.
- *
- * @return true if this LiveData has active observers
- */
- public boolean hasActiveObservers() {
- return mActiveCount > 0;
- }
-
- class LifecycleBoundObserver implements GenericLifecycleObserver {
- public final LifecycleOwner owner;
- public final Observer<T> observer;
- public boolean active;
- public int lastVersion = START_VERSION;
-
- LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
- this.owner = owner;
- this.observer = observer;
- }
-
- @Override
- public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- removeObserver(observer);
- return;
- }
- // immediately set active state, so we'd never dispatch anything to inactive
- // owner
- activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
- }
-
- void activeStateChanged(boolean newActive) {
- if (newActive == active) {
- return;
- }
- active = newActive;
- boolean wasInactive = LiveData.this.mActiveCount == 0;
- LiveData.this.mActiveCount += active ? 1 : -1;
- if (wasInactive && active) {
- onActive();
- }
- if (LiveData.this.mActiveCount == 0 && !active) {
- onInactive();
- }
- if (active) {
- dispatchingValue(this);
- }
- }
- }
-
- static boolean isActiveState(State state) {
- return state.isAtLeast(STARTED);
- }
-
- private void assertMainThread(String methodName) {
- if (!ArchTaskExecutor.getInstance().isMainThread()) {
- throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
- + " thread");
- }
- }
+public class LiveData<T> {
}
diff --git a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
deleted file mode 100644
index 836cfff0..00000000
--- a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.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 647d5d7a..9f0b4257 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -53,29 +53,18 @@ 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);
-
- mOwner2 = mock(LifecycleOwner.class);
-
- mRegistry2 = new LifecycleRegistry(mOwner2);
- when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
-
+ mActiveObserversChanged = mock(MethodExec.class);
+ mLiveData.activeObserversChanged = mActiveObserversChanged;
mInObserver = false;
}
@@ -170,11 +159,14 @@ 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(mOwner2, observer);
+ mLiveData.observe(owner2, observer);
} catch (Throwable t) {
throwable = t;
}
@@ -464,210 +456,6 @@ 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 58647394..672b3a3b 100644
--- a/android/arch/lifecycle/MediatorLiveData.java
+++ b/android/arch/lifecycle/MediatorLiveData.java
@@ -19,49 +19,16 @@ 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 observe other {@code LiveData} objects and react on
+ * {@link LiveData} subclass which may observer 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
*/
@@ -82,7 +49,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> The type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
+ public <S> void addSource(LiveData<S> source, Observer<S> onChanged) {
Source<S> e = new Source<>(source, onChanged);
Source<?> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
@@ -104,7 +71,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> the type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void removeSource(@NonNull LiveData<S> toRemote) {
+ public <S> void removeSource(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
deleted file mode 100644
index 81a07564..00000000
--- a/android/arch/lifecycle/MissingClassTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import 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
deleted file mode 100644
index 07a9dc5a..00000000
--- a/android/arch/lifecycle/PartiallyCoveredActivityTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_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 179e2c47..e2a12563 100644
--- a/android/arch/lifecycle/ProcessLifecycleOwner.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwner.java
@@ -22,7 +22,6 @@ 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;
/**
@@ -157,8 +156,7 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- ReportFragment.injectIfNeededIn(activity);
- ReportFragment.get(activity).setProcessListener(mInitializationListener);
+ ReportFragment .get(activity).setProcessListener(mInitializationListener);
}
@Override
@@ -173,7 +171,6 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
});
}
- @NonNull
@Override
public Lifecycle getLifecycle() {
return mRegistry;
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index 77baf94c..37bdcdb4 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -31,7 +31,6 @@ 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;
@@ -96,22 +95,6 @@ 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);
@@ -181,11 +164,4 @@ 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 16a89ce8..3e4ece82 100644
--- a/android/arch/lifecycle/ReportFragment.java
+++ b/android/arch/lifecycle/ReportFragment.java
@@ -28,6 +28,7 @@ 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 f7f9bbe5..f0214bfb 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -16,35 +16,16 @@
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.util.Pair;
+import android.support.v4.app.FragmentActivity;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
class TestUtils {
@@ -80,88 +61,23 @@ class TestUtils {
return result;
}
- 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)
+ static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
throws Throwable {
final CountDownLatch latch = new CountDownLatch(1);
activityRule.runOnUiThread(() -> {
- Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
- if (currentState == state) {
+ Lifecycle.State currentState = a.getLifecycle().getCurrentState();
+ if (currentState == RESUMED) {
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);
+ }
+ });
});
- 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
- });
+ latch.await();
}
- @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 c735f8ba..9ce9cbb7 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -18,7 +18,6 @@ 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;
/**
@@ -61,8 +60,7 @@ public class Transformations {
* @return a LiveData which emits resulting values
*/
@MainThread
- public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
- @NonNull final Function<X, Y> func) {
+ public static <X, Y> LiveData<Y> map(LiveData<X> source, final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
@@ -122,8 +120,8 @@ public class Transformations {
* @param <Y> a type of resulting LiveData
*/
@MainThread
- public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
- @NonNull final Function<X, LiveData<Y>> func) {
+ public static <X, Y> LiveData<Y> switchMap(LiveData<X> trigger,
+ 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 29cbab8e..7ef591f3 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -43,8 +43,7 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
- @NonNull
- <T extends ViewModel> T create(@NonNull Class<T> modelClass);
+ <T extends ViewModel> T create(Class<T> modelClass);
}
private final Factory mFactory;
@@ -71,7 +70,7 @@ public class ViewModelProvider {
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
- public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
+ public ViewModelProvider(ViewModelStore store, Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
@@ -89,8 +88,7 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
- @NonNull
- public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
+ public <T extends ViewModel> T get(Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
@@ -138,9 +136,8 @@ public class ViewModelProvider {
*/
public static class NewInstanceFactory implements Factory {
- @NonNull
@Override
- public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+ public <T extends ViewModel> T create(Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index b4b20aa4..746162a9 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -139,9 +139,8 @@ public class ViewModelProviders {
mApplication = application;
}
- @NonNull
@Override
- public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+ public <T extends ViewModel> T create(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 e26fa325..50583056 100644
--- a/android/arch/lifecycle/ViewModelStoreOwner.java
+++ b/android/arch/lifecycle/ViewModelStoreOwner.java
@@ -16,8 +16,6 @@
package android.arch.lifecycle;
-import android.support.annotation.NonNull;
-
/**
* A scope that owns {@link ViewModelStore}.
* <p>
@@ -32,6 +30,5 @@ 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 d7d769d6..8c17dd98 100644
--- a/android/arch/lifecycle/ViewModelStores.java
+++ b/android/arch/lifecycle/ViewModelStores.java
@@ -19,7 +19,6 @@ 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;
@@ -39,7 +38,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(@NonNull FragmentActivity activity) {
+ public static ViewModelStore of(FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
@@ -50,7 +49,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(@NonNull Fragment fragment) {
+ public static ViewModelStore of(Fragment fragment) {
return holderFragmentFor(fragment).getViewModelStore();
}
}
diff --git a/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java b/android/arch/lifecycle/testapp/CollectingActivity.java
index 4213cab9..6e243b6c 100644
--- a/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
+++ b/android/arch/lifecycle/testapp/CollectingActivity.java
@@ -17,20 +17,21 @@
package android.arch.lifecycle.testapp;
import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.support.v4.util.Pair;
+import android.util.Pair;
import java.util.List;
/**
* For activities that collect their events.
*/
-public interface CollectingLifecycleOwner extends LifecycleOwner {
+public interface CollectingActivity {
+ long TIMEOUT = 5;
+
/**
- * Return a copy of currently collected events
+ * Return collected events
*
* @return The list of collected events.
* @throws InterruptedException
*/
- List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents();
+ List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() throws InterruptedException;
}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
deleted file mode 100644
index f38d4224..00000000
--- a/android/arch/lifecycle/testapp/CollectingSupportActivity.java
+++ /dev/null
@@ -1,113 +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.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
deleted file mode 100644
index 9bbbe165..00000000
--- a/android/arch/lifecycle/testapp/CollectingSupportFragment.java
+++ /dev/null
@@ -1,104 +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.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 cdf577c1..d8f4fb39 100644
--- a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -16,29 +16,27 @@
package android.arch.lifecycle.testapp;
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_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.support.annotation.NonNull;
-import android.support.v4.util.Pair;
+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 framework activity.
*/
-@SuppressWarnings("deprecation")
public class FrameworkLifecycleRegistryActivity extends Activity implements
- LifecycleRegistryOwner, CollectingLifecycleOwner {
+ LifecycleRegistryOwner, CollectingActivity {
private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
- @NonNull
@Override
public LifecycleRegistry getLifecycle() {
return mLifecycleRegistry;
@@ -51,43 +49,49 @@ public class FrameworkLifecycleRegistryActivity extends Activity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
getLifecycle().addObserver(mTestObserver);
}
@Override
protected void onStart() {
super.onStart();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
}
@Override
protected void onResume() {
super.onResume();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
+ finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
mLatch.countDown();
}
@Override
protected void onStop() {
super.onStop();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
}
@Override
protected void onPause() {
super.onPause();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
}
+ /**
+ * awaits for all events and returns them.
+ */
@Override
- public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
- return new ArrayList<>(mCollectedEvents);
+ public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
+ throws InterruptedException {
+ mLatch.await(TIMEOUT, TimeUnit.SECONDS);
+ return mCollectedEvents;
}
}
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
new file mode 100644
index 00000000..5f33c282
--- /dev/null
+++ b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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
new file mode 100644
index 00000000..b9d59142
--- /dev/null
+++ b/android/arch/lifecycle/testapp/MainActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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 7d53528f..0ae94033 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -22,10 +22,4 @@ 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
deleted file mode 100644
index 835d846a..00000000
--- a/android/arch/lifecycle/testapp/NonSupportActivity.java
+++ /dev/null
@@ -1,85 +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 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
new file mode 100644
index 00000000..c46c6d3e
--- /dev/null
+++ b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.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 788045a2..0929f84a 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 {
- OWNER_CALLBACK,
- LIFECYCLE_EVENT,
+ ACTIVITY_CALLBACK,
+ LIFECYCLE_EVENT
}
diff --git a/android/arch/lifecycle/testapp/TestObserver.java b/android/arch/lifecycle/testapp/TestObserver.java
index 00b8e16d..c6112396 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.support.v4.util.Pair;
+import android.util.Pair;
import java.util.List;
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 06564907..664ab16c 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -21,6 +21,7 @@ import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -74,6 +75,7 @@ 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 be9da200..afcc208c 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -26,65 +26,21 @@ 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
@@ -92,7 +48,21 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
public abstract NullPaddedList<Value> loadInitial(
Key key, int initialLoadSize, boolean enablePlaceholders);
- /** @hide */
+ /**
+ * 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
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -108,7 +78,24 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
return list;
}
- /** @hide */
+ @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
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -124,4 +111,15 @@ 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/PagedStorageDiffHelper.java b/android/arch/paging/ContiguousDiffHelper.java
index 6fc70390..7dd194b2 100644
--- a/android/arch/paging/PagedStorageDiffHelper.java
+++ b/android/arch/paging/ContiguousDiffHelper.java
@@ -16,31 +16,36 @@
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;
-class PagedStorageDiffHelper {
- private PagedStorageDiffHelper() {
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class ContiguousDiffHelper {
+ private ContiguousDiffHelper() {
}
+ @NonNull
static <T> DiffUtil.DiffResult computeDiff(
- 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();
+ final NullPaddedList<T> oldList, final NullPaddedList<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 + oldOffset);
- T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
+ T oldItem = oldList.mList.get(oldItemPosition);
+ T newItem = newList.mList.get(newItemPosition);
if (oldItem == null || newItem == null) {
return null;
}
@@ -49,22 +54,21 @@ class PagedStorageDiffHelper {
@Override
public int getOldListSize() {
- return oldSize;
+ return oldList.mList.size();
}
@Override
public int getNewListSize() {
- return newSize;
+ return newList.mList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition + oldOffset);
- T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
+ T oldItem = oldList.mList.get(oldItemPosition);
+ T newItem = newList.mList.get(newItemPosition);
if (oldItem == newItem) {
return true;
}
- //noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}
@@ -73,19 +77,18 @@ class PagedStorageDiffHelper {
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition + oldOffset);
- T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
+ T oldItem = oldList.mList.get(oldItemPosition);
+ T newItem = newList.mList.get(newItemPosition);
if (oldItem == newItem) {
return true;
}
- //noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}
return diffCallback.areContentsTheSame(oldItem, newItem);
}
- }, true);
+ }, detectMoves);
}
private static class OffsettingListUpdateCallback implements ListUpdateCallback {
@@ -131,25 +134,21 @@ class PagedStorageDiffHelper {
* immediately after dispatching this diff.
*/
static <T> void dispatchDiff(ListUpdateCallback callback,
- final PagedStorage<?, T> oldList,
- final PagedStorage<?, T> newList,
+ final NullPaddedList<T> oldList, final NullPaddedList<T> newList,
final DiffUtil.DiffResult diffResult) {
- 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) {
+ if (oldList.getLeadingNullCount() == 0
+ && oldList.getTrailingNullCount() == 0
+ && newList.getLeadingNullCount() == 0
+ && newList.getTrailingNullCount() == 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);
@@ -158,6 +157,8 @@ class PagedStorageDiffHelper {
}
// 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/PagedStorageDiffHelperTest.java b/android/arch/paging/ContiguousDiffHelperTest.java
index 8cb92246..4f221b34 100644
--- a/android/arch/paging/PagedStorageDiffHelperTest.java
+++ b/android/arch/paging/ContiguousDiffHelperTest.java
@@ -16,9 +16,6 @@
package android.arch.paging;
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -32,12 +29,11 @@ import android.support.v7.util.ListUpdateCallback;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
+import org.mockito.Mockito;
@SmallTest
@RunWith(JUnit4.class)
-public class PagedStorageDiffHelperTest {
+public class ContiguousDiffHelperTest {
private interface CallbackValidator {
void validate(ListUpdateCallback callback);
}
@@ -55,18 +51,13 @@ public class PagedStorageDiffHelperTest {
}
};
- public static Page<Integer, String> createPage(String... items) {
- return new Page<>(Arrays.asList(items));
- }
-
- private static void validateTwoListDiff(PagedStorage<?, String> oldList,
- PagedStorage<?, String> newList,
+ private void validateTwoListDiff(StringPagedList oldList, StringPagedList newList,
CallbackValidator callbackValidator) {
- DiffUtil.DiffResult diffResult = PagedStorageDiffHelper.computeDiff(
- oldList, newList, DIFF_CALLBACK);
+ DiffUtil.DiffResult diffResult = ContiguousDiffHelper.computeDiff(oldList, newList,
+ DIFF_CALLBACK, false);
- ListUpdateCallback listUpdateCallback = mock(ListUpdateCallback.class);
- PagedStorageDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult);
+ ListUpdateCallback listUpdateCallback = Mockito.mock(ListUpdateCallback.class);
+ ContiguousDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult);
callbackValidator.validate(listUpdateCallback);
}
@@ -74,35 +65,8 @@ public class PagedStorageDiffHelperTest {
@Test
public void sameListNoUpdates() {
validateTwoListDiff(
- new PagedStorage<>(5, createPage("a", "b", "c"), 5),
- new PagedStorage<>(5, createPage("a", "b", "c"), 5),
- new CallbackValidator() {
- @Override
- public void validate(ListUpdateCallback callback) {
- verifyZeroInteractions(callback);
- }
- }
- );
- }
-
- @Test
- public void sameListNoUpdatesPlaceholder() {
- PagedStorage<Integer, String> storageNoPlaceholder =
- new PagedStorage<>(0, createPage("a", "b", "c"), 10);
-
- PagedStorage<Integer, String> storageWithPlaceholder =
- new PagedStorage<>(0, createPage("a", "b", "c"), 10);
- storageWithPlaceholder.allocatePlaceholders(3, 0, 3,
- /* ignored */ mock(PagedStorage.Callback.class));
-
- // even though one has placeholders, and null counts are different...
- assertEquals(10, storageNoPlaceholder.getTrailingNullCount());
- assertEquals(7, storageWithPlaceholder.getTrailingNullCount());
-
- // ... should be no interactions, since content still same
- validateTwoListDiff(
- storageNoPlaceholder,
- storageWithPlaceholder,
+ new StringPagedList(5, 5, "a", "b", "c"),
+ new StringPagedList(5, 5, "a", "b", "c"),
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -115,8 +79,8 @@ public class PagedStorageDiffHelperTest {
@Test
public void appendFill() {
validateTwoListDiff(
- new PagedStorage<>(5, createPage("a", "b"), 5),
- new PagedStorage<>(5, createPage("a", "b", "c"), 4),
+ new StringPagedList(5, 5, "a", "b"),
+ new StringPagedList(5, 4, "a", "b", "c"),
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -132,8 +96,8 @@ public class PagedStorageDiffHelperTest {
@Test
public void prependFill() {
validateTwoListDiff(
- new PagedStorage<>(5, createPage("b", "c"), 5),
- new PagedStorage<>(4, createPage("a", "b", "c"), 5),
+ new StringPagedList(5, 5, "b", "c"),
+ new StringPagedList(4, 5, "a", "b", "c"),
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -149,8 +113,8 @@ public class PagedStorageDiffHelperTest {
@Test
public void change() {
validateTwoListDiff(
- new PagedStorage<>(5, createPage("a1", "b1", "c1"), 5),
- new PagedStorage<>(5, createPage("a2", "b1", "c2"), 5),
+ new StringPagedList(5, 5, "a1", "b1", "c1"),
+ new StringPagedList(5, 5, "a2", "b1", "c2"),
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -161,5 +125,4 @@ public class PagedStorageDiffHelperTest {
}
);
}
-
}
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index 7835dbe3..d8907c3b 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -16,136 +16,101 @@
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;
- @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 int mLastLoad = 0;
+ private T mLastItem = null;
- @MainThread
- @Override
- public void onPageResult(@NonNull PageResult<K, V> 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<>();
- 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,
+ @WorkerThread
+ <K> ContiguousPagedList(@NonNull ContiguousDataSource<K, T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- @NonNull Config config,
- final @Nullable K key) {
- super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config);
- mDataSource = dataSource;
-
- // blocking init just triggers the initial load on the construction thread -
- // Could still be posted with callback, if desired.
- mDataSource.loadInitial(key,
- mConfig.mInitialLoadSizeHint,
- mConfig.mEnablePlaceholders,
- mReceiver);
- }
-
- @MainThread
- @Override
- 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;
+ Config config,
+ @Nullable K key) {
+ super();
- final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
+ 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 (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
+
+ 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;
}
- 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);
- }
+ @Override
+ public T get(int index) {
+ T item = super.get(index);
+ if (item != null) {
+ mLastItem = item;
}
+ return item;
}
- @MainThread
@Override
- protected void loadAroundInternal(int index) {
- int prependItems = mConfig.mPrefetchDistance - (index - mStorage.getLeadingNullCount());
- int appendItems = index + mConfig.mPrefetchDistance
- - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
+ public void loadAround(int index) {
+ mLastLoad = index + mPositionOffset;
+
+ int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount);
+ int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
@@ -158,6 +123,21 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
}
+ @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) {
@@ -165,17 +145,29 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
mPrependWorkerRunning = true;
- 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();
+ final int position = mLeadingNullCount + mPositionOffset;
+ final T item = mList.get(0);
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (isDetached()) {
+ if (mDetached.get()) {
return;
}
- mDataSource.loadBefore(position, item, mConfig.mPageSize, mReceiver);
+
+ 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();
+ }
}
});
}
@@ -187,44 +179,56 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
mAppendWorkerRunning = true;
- 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();
+ final int position = mLeadingNullCount + mList.size() - 1 + mPositionOffset;
+ final T item = mList.get(mList.size() - 1);
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (isDetached()) {
+ if (mDetached.get()) {
return;
}
- mDataSource.loadAfter(position, item, mConfig.mPageSize, mReceiver);
+
+ 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();
+ }
}
});
}
- @Override
- boolean isContiguous() {
- return true;
- }
+ @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;
+ }
- @Nullable
- @Override
- public Object getLastKey() {
- return mDataSource.getKey(mLastLoad, mLastItem);
- }
+ Collections.reverse(before);
+ mList.addAll(0, before);
- @MainThread
- @Override
- public void onInitialized(int count) {
- notifyInserted(0, count);
- }
+ final int changedCount = Math.min(mLeadingNullCount, count);
+ final int addedCount = count - changedCount;
- @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;
+ if (changedCount != 0) {
+ mLeadingNullCount -= changedCount;
+ }
+ mPositionOffset -= addedCount;
+ mNumberPrepended += count;
+
+
+ // only try to post more work after fully prepended (with offsets / null counts updated)
+ mPrependItemsRequested -= count;
mPrependWorkerRunning = false;
if (mPrependItemsRequested > 0) {
// not done prepending, keep going
@@ -232,16 +236,39 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
// finally dispatch callbacks, after prepend may have already been scheduled
- notifyChanged(leadingNulls, changedCount);
- notifyInserted(0, addedCount);
+ 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);
+ }
+ }
+ }
}
@MainThread
- @Override
- public void onPageAppended(int endPosition, int changedCount, int addedCount) {
- // consider whether to post more work, now that a page is fully appended
+ 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);
- mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
+ final int changedCount = Math.min(mTrailingNullCount, count);
+ final int addedCount = count - changedCount;
+
+ if (changedCount != 0) {
+ mTrailingNullCount -= changedCount;
+ }
+ mNumberAppended += count;
+
+ // only try to post more work after fully appended (with null counts updated)
+ mAppendItemsRequested -= count;
mAppendWorkerRunning = false;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
@@ -249,19 +276,100 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
}
// finally dispatch callbacks, after append may have already been scheduled
- notifyChanged(endPosition, changedCount);
- notifyInserted(endPosition + changedCount, addedCount);
+ 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);
+ }
+ }
+ }
}
- @MainThread
@Override
- public void onPagePlaceholderInserted(int pageIndex) {
- throw new IllegalStateException("Tiled callback on ContiguousPagedList");
+ 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();
}
- @MainThread
@Override
- public void onPageInserted(int start, int count) {
- throw new IllegalStateException("Tiled callback on ContiguousPagedList");
+ 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);
+ }
+ }
+ }
+
+ @Override
+ public boolean isDetached() {
+ return mDetached.get();
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void detach() {
+ mDetached.set(true);
+ }
+
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return mDataSource.getKey(mLastLoad, mLastItem);
}
}
diff --git a/android/arch/paging/ContiguousPagedListTest.java b/android/arch/paging/ContiguousPagedListTest.java
index 43f556a8..ee7ea6a4 100644
--- a/android/arch/paging/ContiguousPagedListTest.java
+++ b/android/arch/paging/ContiguousPagedListTest.java
@@ -16,7 +16,6 @@
package android.arch.paging;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
@@ -110,7 +109,6 @@ public class ContiguousPagedListTest {
private void verifyRange(int start, int count, NullPaddedList<Item> actual) {
if (mCounted) {
- //noinspection UnnecessaryLocalVariable
int expectedLeading = start;
int expectedTrailing = ITEMS.size() - start - count;
assertEquals(ITEMS.size(), actual.size());
@@ -134,38 +132,6 @@ public class ContiguousPagedListTest {
}
}
- @SuppressWarnings("SuspiciousSystemArraycopy")
- private void verifyRange(int start, int count, PagedStorage<?, Item> actual) {
- if (mCounted) {
- Item[] expected = new Item[ITEMS.size()];
- System.arraycopy(ITEMS.toArray(), start, expected, start, count);
- assertArrayEquals(expected, actual.toArray());
-
- //noinspection UnnecessaryLocalVariable
- int expectedLeading = start;
- int expectedTrailing = ITEMS.size() - start - count;
- assertEquals(ITEMS.size(), actual.size());
- assertEquals(ITEMS.size() - expectedLeading - expectedTrailing,
- actual.getStorageCount());
- assertEquals(expectedLeading, actual.getLeadingNullCount());
- assertEquals(expectedTrailing, actual.getTrailingNullCount());
-
- } else {
- Item[] expected = new Item[count];
- System.arraycopy(ITEMS.toArray(), start, expected, 0, count);
- assertArrayEquals(expected, actual.toArray());
-
- assertEquals(count, actual.size());
- assertEquals(actual.size(), actual.getStorageCount());
- assertEquals(0, actual.getLeadingNullCount());
- assertEquals(0, actual.getTrailingNullCount());
- }
- }
-
- private void verifyRange(int start, int count, PagedList<Item> actual) {
- verifyRange(start, count, actual.mStorage);
- }
-
private void verifyCallback(PagedList.Callback callback, int countedPosition,
int uncountedPosition) {
if (mCounted) {
@@ -188,7 +154,7 @@ public class ContiguousPagedListTest {
}
- private ContiguousPagedList<Integer, Item> createCountedPagedList(
+ private ContiguousPagedList<Item> createCountedPagedList(
PagedList.Config config, int initialPosition) {
TestSource source = new TestSource();
return new ContiguousPagedList<>(
@@ -197,7 +163,7 @@ public class ContiguousPagedListTest {
initialPosition);
}
- private ContiguousPagedList<Integer, Item> createCountedPagedList(int initialPosition) {
+ private ContiguousPagedList<Item> createCountedPagedList(int initialPosition) {
return createCountedPagedList(
new PagedList.Config.Builder()
.setInitialLoadSizeHint(40)
@@ -208,14 +174,8 @@ public class ContiguousPagedListTest {
}
@Test
- public void construct() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
- verifyRange(0, 40, pagedList);
- }
-
- @Test
public void append() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
+ ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(0, 40, pagedList);
@@ -232,7 +192,7 @@ public class ContiguousPagedListTest {
@Test
public void prepend() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(80);
+ ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(60, 40, pagedList);
@@ -248,7 +208,7 @@ public class ContiguousPagedListTest {
@Test
public void outwards() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(50);
+ ContiguousPagedList<Item> pagedList = createCountedPagedList(50);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(30, 40, pagedList);
@@ -271,7 +231,7 @@ public class ContiguousPagedListTest {
@Test
public void multiAppend() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
+ ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(0, 40, pagedList);
@@ -288,7 +248,7 @@ public class ContiguousPagedListTest {
@Test
public void distantPrefetch() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(
+ ContiguousPagedList<Item> pagedList = createCountedPagedList(
new PagedList.Config.Builder()
.setInitialLoadSizeHint(10)
.setPageSize(10)
@@ -314,7 +274,7 @@ public class ContiguousPagedListTest {
@Test
public void appendCallbackAddedLate() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
+ ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
verifyRange(0, 40, pagedList);
pagedList.loadAround(35);
@@ -322,7 +282,7 @@ public class ContiguousPagedListTest {
verifyRange(0, 60, pagedList);
// snapshot at 60 items
- PagedList<Item> snapshot = (PagedList<Item>) pagedList.snapshot();
+ NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
verifyRange(0, 60, snapshot);
@@ -340,7 +300,7 @@ public class ContiguousPagedListTest {
@Test
public void prependCallbackAddedLate() {
- ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(80);
+ ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
verifyRange(60, 40, pagedList);
pagedList.loadAround(mCounted ? 65 : 5);
@@ -348,7 +308,7 @@ public class ContiguousPagedListTest {
verifyRange(40, 60, pagedList);
// snapshot at 60 items
- PagedList<Item> snapshot = (PagedList<Item>) pagedList.snapshot();
+ NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
verifyRange(40, 60, snapshot);
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 524e570a..48fbec5f 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -17,7 +17,6 @@
package android.arch.paging;
import android.support.annotation.AnyThread;
-import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -61,6 +60,15 @@ 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.
*/
@@ -103,7 +111,7 @@ public abstract class DataSource<Key, Value> {
*/
@AnyThread
@SuppressWarnings("WeakerAccess")
- public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+ public void addInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
mOnInvalidatedCallbacks.add(onInvalidatedCallback);
}
@@ -114,7 +122,7 @@ public abstract class DataSource<Key, Value> {
*/
@AnyThread
@SuppressWarnings("WeakerAccess")
- public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+ public void removeInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
}
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 0d452946..8cf6829c 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -103,6 +103,10 @@ 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
@@ -114,14 +118,7 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
@Override
List<Value> loadBeforeImpl(
int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize) {
- List<Value> list = loadBefore(getKey(currentBeginItem), pageSize);
-
- if (list != null && list.size() > 1) {
- // TODO: move out of keyed entirely, into the DB DataSource.
- list = new ArrayList<>(list);
- Collections.reverse(list);
- }
- return list;
+ return loadBefore(getKey(currentBeginItem), pageSize);
}
@Nullable
@@ -194,8 +191,6 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @WorkerThread
- @Override
public NullPaddedList<Value> loadInitial(
@Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
if (isInvalid()) {
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index 07dd84bf..b7c68dd6 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,133 +16,5 @@
package android.arch.paging;
-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();
- }
-}
+abstract public class LivePagedListProvider<K, T> {
+} \ No newline at end of file
diff --git a/android/arch/paging/NullPaddedList.java b/android/arch/paging/NullPaddedList.java
index c7b0b231..43000302 100644
--- a/android/arch/paging/NullPaddedList.java
+++ b/android/arch/paging/NullPaddedList.java
@@ -16,9 +16,11 @@
package android.arch.paging;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
-import java.util.AbstractList;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -29,11 +31,18 @@ import java.util.List;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class NullPaddedList<Type> extends AbstractList<Type> {
+public class NullPaddedList<Type> extends PagedList<Type> {
List<Type> mList;
- private int mTrailingNullCount;
- private int mLeadingNullCount;
- private int mPositionOffset;
+ int mTrailingNullCount;
+ int mLeadingNullCount;
+ int mPositionOffset;
+
+ // track the items prepended/appended since the PagedList was initialized
+ int mNumberPrepended;
+ int mNumberAppended;
+
+ NullPaddedList() {
+ }
@Override
public String toString() {
@@ -82,6 +91,20 @@ public class NullPaddedList<Type> extends AbstractList<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
@@ -101,12 +124,46 @@ public class NullPaddedList<Type> extends AbstractList<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;
}
@@ -137,4 +194,12 @@ public class NullPaddedList<Type> extends AbstractList<Type> {
public int getTrailingNullCount() {
return mTrailingNullCount;
}
+
+ int getNumberPrepended() {
+ return mNumberPrepended;
+ }
+
+ int getNumberAppended() {
+ return mNumberAppended;
+ }
}
diff --git a/android/arch/paging/Page.java b/android/arch/paging/Page.java
deleted file mode 100644
index e9890ed4..00000000
--- a/android/arch/paging/Page.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.List;
-
-/**
- * Immutable class representing a page of data loaded from a DataSource.
- * <p>
- * Optionally stores before/after keys for cases where they cannot be computed, but the DataSource
- * can provide them as part of loading a page.
- * <p>
- * A page's list must never be modified.
- */
-class Page<K, V> {
- @SuppressWarnings("WeakerAccess")
- @Nullable
- public final K beforeKey;
- @NonNull
- public final List<V> items;
- @SuppressWarnings("WeakerAccess")
- @Nullable
- public K afterKey;
-
- Page(@NonNull List<V> items) {
- this(null, items, null);
- }
-
- Page(@Nullable K beforeKey, @NonNull List<V> items, @Nullable K afterKey) {
- this.beforeKey = beforeKey;
- this.items = items;
- this.afterKey = afterKey;
- }
-}
diff --git a/android/arch/paging/PageArrayList.java b/android/arch/paging/PageArrayList.java
new file mode 100644
index 00000000..b90d055a
--- /dev/null
+++ b/android/arch/paging/PageArrayList.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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
new file mode 100644
index 00000000..135e640d
--- /dev/null
+++ b/android/arch/paging/PageArrayListTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.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
deleted file mode 100644
index a4090f61..00000000
--- a/android/arch/paging/PageResult.java
+++ /dev/null
@@ -1,56 +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.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 1f07bfab..6a31b689 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -20,12 +20,9 @@ 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}.
@@ -93,28 +90,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @param <T> The type of the entries in the list.
*/
public abstract class PagedList<T> extends AbstractList<T> {
- final Executor mMainThreadExecutor;
- final Executor mBackgroundThreadExecutor;
- final Config mConfig;
-
- @NonNull
- final PagedStorage<?, T> mStorage;
-
- int mLastLoad = 0;
- T mLastItem = null;
-
- private final AtomicBoolean mDetached = new AtomicBoolean(false);
-
- protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
-
- PagedList(@NonNull PagedStorage<?, T> storage,
- @NonNull Executor mainThreadExecutor,
- @NonNull Executor backgroundThreadExecutor,
- @NonNull Config config) {
- mStorage = storage;
- mMainThreadExecutor = mainThreadExecutor;
- mBackgroundThreadExecutor = backgroundThreadExecutor;
- mConfig = config;
+ // Since we currently rely on implementation details of two implementations,
+ // prevent external subclassing
+ PagedList() {
}
/**
@@ -302,13 +280,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
*/
@Override
@Nullable
- public T get(int index) {
- T item = mStorage.get(index);
- if (item != null) {
- mLastItem = item;
- }
- return item;
- }
+ public abstract T get(int index);
/**
@@ -316,10 +288,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @param index Index at which to load.
*/
- public void loadAround(int index) {
- mLastLoad = index + getPositionOffset();
- loadAroundInternal(index);
- }
+ public abstract void loadAround(int index);
/**
@@ -328,9 +297,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @return Current total size of the list.
*/
@Override
- public int size() {
- return mStorage.size();
- }
+ public abstract int size();
/**
* Returns whether the list is immutable. Immutable lists may not become mutable again, and may
@@ -338,25 +305,15 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @return True if the PagedList is immutable.
*/
- @SuppressWarnings("WeakerAccess")
- public boolean isImmutable() {
- return isDetached();
- }
+ public abstract boolean isImmutable();
/**
* Returns an immutable snapshot of the PagedList. If this PagedList is already
* immutable, it will be returned.
*
- * @return Immutable snapshot of PagedList data.
+ * @return Immutable snapshot of PagedList, which may be the PagedList itself.
*/
- @NonNull
- public List<T> snapshot() {
- if (isImmutable()) {
- return this;
- }
-
- return new SnapshotPagedList<>(this);
- }
+ public abstract List<T> snapshot();
abstract boolean isContiguous();
@@ -371,7 +328,9 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @return Key of position most recently passed to {@link #loadAround(int)}.
*/
@Nullable
- public abstract Object getLastKey();
+ public Object getLastKey() {
+ return null;
+ }
/**
* True if the PagedList has detached the DataSource it was loading from, and will no longer
@@ -379,9 +338,8 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @return True if the data source is detached.
*/
- @SuppressWarnings("WeakerAccess")
public boolean isDetached() {
- return mDetached.get();
+ return true;
}
/**
@@ -391,9 +349,7 @@ 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);
}
/**
@@ -405,7 +361,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 mStorage.getPositionOffset();
+ return 0;
}
/**
@@ -429,68 +385,16 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @param callback Callback to dispatch to.
* @see #removeWeakCallback(Callback)
*/
- @SuppressWarnings("WeakerAccess")
- public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
- if (previousSnapshot != null && previousSnapshot != this) {
- PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
- //noinspection unchecked
- dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
- }
-
- // 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);
- }
- }
+ public abstract void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
+ @NonNull Callback callback);
- // then add the new one
- mCallbacks.add(new WeakReference<>(callback));
- }
/**
* Removes a previously added callback.
*
* @param callback Callback, previously added.
- * @see #addWeakCallback(List, Callback)
+ * @see #addWeakCallback(PagedList, 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);
+ public abstract void removeWeakCallback(Callback callback);
/**
* Callback signaling when content is loaded into the list.
@@ -641,15 +545,10 @@ 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>
- * 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)}.
+ * 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.
* <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 abcff415..c7b61d9f 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -25,6 +25,8 @@ 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}.
@@ -118,15 +120,15 @@ import android.support.v7.widget.RecyclerView;
* @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> mPagedList;
- private PagedList<T> mSnapshot;
+ private PagedList<T> mList;
// Max generation of currently scheduled runnable
private int mMaxScheduledGeneration;
@@ -180,17 +182,12 @@ public class PagedListAdapterHelper<T> {
@SuppressWarnings("WeakerAccess")
@Nullable
public T getItem(int index) {
- if (mPagedList == null) {
- if (mSnapshot == null) {
- throw new IndexOutOfBoundsException(
- "Item count is zero, getItem() call is invalid");
- } else {
- return mSnapshot.get(index);
- }
+ if (mList == null) {
+ throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid");
}
- mPagedList.loadAround(index);
- return mPagedList.get(index);
+ mList.loadAround(index);
+ return mList.get(index);
}
/**
@@ -201,11 +198,7 @@ public class PagedListAdapterHelper<T> {
*/
@SuppressWarnings("WeakerAccess")
public int getItemCount() {
- if (mPagedList != null) {
- return mPagedList.size();
- }
-
- return mSnapshot == null ? 0 : mSnapshot.size();
+ return mList == null ? 0 : mList.size();
}
/**
@@ -219,7 +212,7 @@ public class PagedListAdapterHelper<T> {
*/
public void setList(final PagedList<T> pagedList) {
if (pagedList != null) {
- if (mPagedList == null && mSnapshot == null) {
+ if (mList == null) {
mIsContiguous = pagedList.isContiguous();
} else {
if (pagedList.isContiguous() != mIsContiguous) {
@@ -229,7 +222,7 @@ public class PagedListAdapterHelper<T> {
}
}
- if (pagedList == mPagedList) {
+ if (pagedList == mList) {
// nothing to do
return;
}
@@ -238,55 +231,49 @@ public class PagedListAdapterHelper<T> {
final int runGeneration = ++mMaxScheduledGeneration;
if (pagedList == 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);
+ mUpdateCallback.onRemoved(0, mList.size());
+ mList.removeWeakCallback(mPagedListCallback);
+ mList = null;
return;
}
- if (mPagedList == null && mSnapshot == null) {
+ if (mList == null) {
// fast simple first insert
- mPagedList = pagedList;
- pagedList.addWeakCallback(null, mPagedListCallback);
-
- // dispatch update callback after updating mPagedList/mSnapshot
mUpdateCallback.onInserted(0, pagedList.size());
+ mList = pagedList;
+ pagedList.addWeakCallback(null, mPagedListCallback);
return;
}
- if (mPagedList != null) {
+ if (!mList.isImmutable()) {
// 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
- 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");
+ mList.removeWeakCallback(mPagedListCallback);
+ mList = (PagedList<T>) mList.snapshot();
}
- final PagedList<T> oldSnapshot = mSnapshot;
- final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
+ final PagedList<T> oldSnapshot = mList;
+ final List<T> newSnapshot = pagedList.snapshot();
+ mUpdateScheduled = true;
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
- result = PagedStorageDiffHelper.computeDiff(
- oldSnapshot.mStorage,
- newSnapshot.mStorage,
- mConfig.getDiffCallback());
+ 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);
+ }
mConfig.getMainThreadExecutor().execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
+ mUpdateScheduled = false;
latchPagedList(pagedList, newSnapshot, result);
}
}
@@ -296,21 +283,16 @@ public class PagedListAdapterHelper<T> {
}
private void latchPagedList(
- PagedList<T> newList, PagedList<T> diffSnapshot,
+ PagedList<T> newList, List<T> diffSnapshot,
DiffUtil.DiffResult diffResult) {
- if (mSnapshot == null || mPagedList != null) {
- throw new IllegalStateException("must be in snapshot state to apply diff");
+ if (mIsContiguous) {
+ ContiguousDiffHelper.dispatchDiff(mUpdateCallback,
+ (NullPaddedList<T>) mList, (ContiguousPagedList<T>) newList, diffResult);
+ } else {
+ SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult);
}
-
- 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);
+ mList = newList;
+ newList.addWeakCallback((PagedList<T>) diffSnapshot, mPagedListCallback);
}
/**
@@ -325,9 +307,6 @@ public class PagedListAdapterHelper<T> {
@SuppressWarnings("WeakerAccess")
@Nullable
public PagedList<T> getCurrentList() {
- if (mSnapshot != null) {
- return mSnapshot;
- }
- return mPagedList;
+ return mList;
}
}
diff --git a/android/arch/paging/PagedListAdapterHelperTest.java b/android/arch/paging/PagedListAdapterHelperTest.java
index 963d0479..3518540c 100644
--- a/android/arch/paging/PagedListAdapterHelperTest.java
+++ b/android/arch/paging/PagedListAdapterHelperTest.java
@@ -21,7 +21,6 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -290,64 +289,6 @@ public class PagedListAdapterHelperTest {
assertFalse(helper.getCurrentList().isImmutable());
}
- @Test
- public void itemCountUpdatedBeforeListUpdateCallbacks() {
- // verify that itemCount is updated in the helper before dispatching ListUpdateCallbacks
-
- final int[] expectedCount = new int[] { 0 };
- // provides access to helper, which must be constructed after callback
- final PagedListAdapterHelper[] helperAccessor = new PagedListAdapterHelper[] { null };
-
- ListUpdateCallback callback = new ListUpdateCallback() {
- @Override
- public void onInserted(int position, int count) {
- assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
- }
-
- @Override
- public void onRemoved(int position, int count) {
- assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
- }
-
- @Override
- public void onMoved(int fromPosition, int toPosition) {
- fail("not expected");
- }
-
- @Override
- public void onChanged(int position, int count, Object payload) {
- fail("not expected");
- }
- };
-
- PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
- helperAccessor[0] = helper;
-
- PagedList.Config config = new PagedList.Config.Builder()
- .setPageSize(20)
- .build();
-
-
- // in the fast-add case...
- expectedCount[0] = 5;
- assertEquals(0, helper.getItemCount());
- helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 5), 0));
- assertEquals(5, helper.getItemCount());
-
- // in the slow, diff on BG thread case...
- expectedCount[0] = 10;
- assertEquals(5, helper.getItemCount());
- helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 10), 0));
- drain();
- assertEquals(10, helper.getItemCount());
-
- // and in the fast-remove case
- expectedCount[0] = 0;
- assertEquals(10, helper.getItemCount());
- helper.setList(null);
- assertEquals(0, helper.getItemCount());
- }
-
private void drainExceptDiffThread() {
boolean executed;
do {
diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java
deleted file mode 100644
index 7f91290d..00000000
--- a/android/arch/paging/PagedStorage.java
+++ /dev/null
@@ -1,433 +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.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/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java
index c538cb60..deb51e94 100644
--- a/android/arch/paging/PositionalDataSource.java
+++ b/android/arch/paging/PositionalDataSource.java
@@ -42,17 +42,6 @@ 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) {
@@ -66,7 +55,16 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I
return loadBefore(currentBeginIndex - 1, pageSize);
}
- /** @hide */
+
+ /**
+ * 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
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -120,9 +118,6 @@ 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
deleted file mode 100644
index 7e965a0f..00000000
--- a/android/arch/paging/SnapshotPagedList.java
+++ /dev/null
@@ -1,64 +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;
-
-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
new file mode 100644
index 00000000..fe478973
--- /dev/null
+++ b/android/arch/paging/SparseDiffHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.recyclerview.extensions.DiffCallback;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.util.ListUpdateCallback;
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class SparseDiffHelper {
+ private SparseDiffHelper() {
+ }
+
+ @NonNull
+ static <T> DiffUtil.DiffResult computeDiff(
+ final PageArrayList<T> oldList, final PageArrayList<T> newList,
+ final DiffCallback<T> diffCallback, boolean detectMoves) {
+
+ if (!oldList.isImmutable()) {
+ throw new IllegalArgumentException("list must be immutable to safely perform diff");
+ }
+ if (!newList.isImmutable()) {
+ throw new IllegalArgumentException("list must be immutable to safely perform diff");
+ }
+ return DiffUtil.calculateDiff(new DiffUtil.Callback() {
+ @Nullable
+ @Override
+ public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+ T oldItem = oldList.get(oldItemPosition);
+ T newItem = newList.get(newItemPosition);
+ if (oldItem == null || newItem == null) {
+ return null;
+ }
+ return diffCallback.getChangePayload(oldItem, newItem);
+ }
+
+ @Override
+ public int getOldListSize() {
+ return oldList.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return newList.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ T oldItem = oldList.get(oldItemPosition);
+ T newItem = newList.get(newItemPosition);
+ if (oldItem == newItem) {
+ return true;
+ }
+ if (oldItem == null || newItem == null) {
+ return false;
+ }
+ return diffCallback.areItemsTheSame(oldItem, newItem);
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ T oldItem = oldList.get(oldItemPosition);
+ T newItem = newList.get(newItemPosition);
+ if (oldItem == newItem) {
+ return true;
+ }
+ if (oldItem == null || newItem == null) {
+ return false;
+ }
+
+ return diffCallback.areContentsTheSame(oldItem, newItem);
+ }
+ }, detectMoves);
+ }
+
+ static <T> void dispatchDiff(ListUpdateCallback callback,
+ final DiffUtil.DiffResult diffResult) {
+ // Simple case, dispatch & return
+ diffResult.dispatchUpdatesTo(callback);
+ }
+}
diff --git a/android/arch/paging/StringPagedList.java b/android/arch/paging/StringPagedList.java
index 880d5e9b..5318d38c 100644
--- a/android/arch/paging/StringPagedList.java
+++ b/android/arch/paging/StringPagedList.java
@@ -16,60 +16,10 @@
package android.arch.paging;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
import java.util.Arrays;
-public class StringPagedList extends PagedList<String> implements PagedStorage.Callback {
- StringPagedList(int leadingNulls, int trailingNulls, String... items) {
- super(new PagedStorage<Integer, String>(),
- null, null, null);
- PagedStorage<Integer, String> keyedStorage = (PagedStorage<Integer, String>) mStorage;
- keyedStorage.init(leadingNulls,
- new Page<Integer, String>(null, Arrays.asList(items), null),
- trailingNulls,
- 0,
- this);
- }
-
- @Override
- boolean isContiguous() {
- return true;
- }
-
- @Nullable
- @Override
- public Object getLastKey() {
- return null;
- }
-
- @Override
- protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<String> storageSnapshot,
- @NonNull Callback callback) {
- }
-
- @Override
- protected void loadAroundInternal(int index) {
- }
-
- @Override
- public void onInitialized(int count) {
- }
-
- @Override
- public void onPagePrepended(int leadingNulls, int changed, int added) {
- }
-
- @Override
- public void onPageAppended(int endPosition, int changed, int added) {
- }
-
- @Override
- public void onPagePlaceholderInserted(int pageIndex) {
- }
-
- @Override
- public void onPageInserted(int start, int count) {
+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
index 976f7df5..30809c3e 100644
--- a/android/arch/paging/TestExecutor.java
+++ b/android/arch/paging/TestExecutor.java
@@ -30,7 +30,7 @@ public class TestExecutor implements Executor {
mTasks.add(command);
}
- public boolean executeAll() {
+ boolean executeAll() {
boolean consumed = !mTasks.isEmpty();
Runnable task;
while ((task = mTasks.poll()) != null) {
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 61dead3a..36be423d 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -19,7 +19,6 @@ package android.arch.paging;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
-import java.util.Collections;
import java.util.List;
/**
@@ -93,6 +92,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
* @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
*/
@WorkerThread
+ @Override
public abstract int countItems();
@Override
@@ -118,61 +118,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
@WorkerThread
public abstract List<Type> loadRange(int startPosition, int count);
- /**
- * blocking, and splits pages
- */
- void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
- PageResult.Receiver<Integer, Type> receiver) {
-
- 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) {
+ final 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 c45d029d..a2fc064b 100644
--- a/android/arch/paging/TiledPagedList.java
+++ b/android/arch/paging/TiledPagedList.java
@@ -16,173 +16,219 @@
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;
-class TiledPagedList<T> extends PagedList<T>
- implements PagedStorage.Callback {
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class TiledPagedList<T> extends PageArrayList<T> {
private final TiledDataSource<T> mDataSource;
+ private final Executor mMainThreadExecutor;
+ private final Executor mBackgroundThreadExecutor;
+ private final Config mConfig;
- @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
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ private final List<T> mLoadingPlaceholder = new AbstractList<T>() {
@Override
- public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) {
- // NOTE: if we're already on main thread, this can delay page receive by a frame
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- onPageResult(pageResult);
- }
- });
+ public T get(int i) {
+ return null;
}
- @MainThread
@Override
- public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
- if (pageResult.page == null) {
- detach();
- return;
- }
-
- if (isDetached()) {
- // No op, have detached
- return;
- }
-
- 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);
- }
+ public int size() {
+ return 0;
}
};
+ private int mLastLoad = -1;
+
+ private AtomicBoolean mDetached = new AtomicBoolean(false);
+
+ private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+
@WorkerThread
TiledPagedList(@NonNull TiledDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- @NonNull Config config,
+ Config config,
int position) {
- super(new PagedStorage<Integer, T>(),
- mainThreadExecutor, backgroundThreadExecutor, config);
- mDataSource = dataSource;
-
- final int pageSize = mConfig.mPageSize;
+ super(config.mPageSize, dataSource.countItems());
- final int itemCount = mDataSource.countItems();
- final int firstLoadSize = Math.min(itemCount,
- (Math.max(mConfig.mInitialLoadSizeHint / pageSize, 2)) * pageSize);
- final int firstLoadPosition = computeFirstLoadPosition(
- position, firstLoadSize, pageSize, itemCount);
+ 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;
+ }
- mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize,
- itemCount, mReceiver);
+ 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();
}
- static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
- int idealStart = position - firstLoadSize / 2;
+ @Override
+ public void loadAround(int index) {
+ mLastLoad = index;
- int roundedPageStart = Math.round(idealStart / pageSize) * pageSize;
+ int minimumPage = Math.max((index - mConfig.mPrefetchDistance) / mPageSize, 0);
+ int maximumPage = Math.min((index + mConfig.mPrefetchDistance) / mPageSize,
+ mMaxPageCount - 1);
- // minimum start position is 0
- roundedPageStart = Math.max(0, roundedPageStart);
+ 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);
+ }
+ }
- // 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 scheduleLoadPage(final int pageIndex) {
+ final int localPageIndex = pageIndex - mPageIndexOffset;
- return roundedPageStart;
- }
+ if (mPages.get(localPageIndex) != null) {
+ // page is present in list, and non-null - don't need to load
+ return;
+ }
+ mPages.set(localPageIndex, mLoadingPlaceholder);
- @Override
- boolean isContiguous() {
- return false;
- }
+ 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();
+ }
+ }
+ });
- @Nullable
- @Override
- public Object getLastKey() {
- return mLastLoad;
}
- @Override
- protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
- @NonNull Callback callback) {
- //noinspection UnnecessaryLocalVariable
- final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage;
-
- // loop through each page and signal the callback for any pages that are present now,
- // but not in the snapshot.
- final int pageSize = mConfig.mPageSize;
- final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
- final int pageCount = mStorage.getPageCount();
- for (int i = 0; i < pageCount; i++) {
- int pageIndex = i + leadingNullPages;
- int updatedPages = 0;
- // count number of consecutive pages that were added since the snapshot...
- while (updatedPages < mStorage.getPageCount()
- && mStorage.hasPage(pageSize, pageIndex + updatedPages)
- && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
- updatedPages++;
- }
- // and signal them all at once to the callback
- if (updatedPages > 0) {
- callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
- i += updatedPages - 1;
+ private void loadPageImpl(int pageIndex, List<T> data) {
+ int localPageIndex = pageIndex - mPageIndexOffset;
+
+ 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
- protected void loadAroundInternal(int index) {
- mStorage.allocatePlaceholders(index, mConfig.mPrefetchDistance, mConfig.mPageSize, this);
+ 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();
}
@Override
- public void onInitialized(int count) {
- notifyInserted(0, count);
+ public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
+ @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;
+ }
+ }
+ }
+ mCallbacks.add(new WeakReference<>(callback));
}
@Override
- public void onPagePrepended(int leadingNulls, int changed, int added) {
- throw new IllegalStateException("Contiguous callback on TiledPagedList");
+ 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);
+ }
+ }
}
@Override
- public void onPageAppended(int endPosition, int changed, int added) {
- throw new IllegalStateException("Contiguous callback on TiledPagedList");
+ public boolean isDetached() {
+ return mDetached.get();
}
@Override
- public void onPagePlaceholderInserted(final int pageIndex) {
- // placeholder means initialize a load
- mBackgroundThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (isDetached()) {
- return;
- }
- final int pageSize = mConfig.mPageSize;
- mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver);
- }
- });
+ public void detach() {
+ mDetached.set(true);
}
+ @Nullable
@Override
- public void onPageInserted(int start, int count) {
- notifyChanged(start, count);
+ public Object getLastKey() {
+ return mLastLoad;
}
}
diff --git a/android/arch/paging/TiledPagedListTest.java b/android/arch/paging/TiledPagedListTest.java
index 22bfd1ff..4ad02e12 100644
--- a/android/arch/paging/TiledPagedListTest.java
+++ b/android/arch/paging/TiledPagedListTest.java
@@ -78,107 +78,75 @@ public class TiledPagedListTest {
}
}
- private void verifyRange(List<Item> list, Integer... loadedPages) {
+ 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("Index " + i, ITEMS.get(i), list.get(i));
+ assertSame(ITEMS.get(i), list.get(i));
} else {
- assertEquals("Index " + i, null, list.get(i));
+ assertEquals(null, list.get(i));
}
}
}
- private TiledPagedList<Item> createTiledPagedList(int loadPosition, int initPages) {
- return createTiledPagedList(loadPosition, initPages, PAGE_SIZE);
+ private TiledPagedList<Item> createTiledPagedList(int loadPosition) {
+ return createTiledPagedList(loadPosition, PAGE_SIZE);
}
- private TiledPagedList<Item> createTiledPagedList(int loadPosition, int initPages,
- int prefetchDistance) {
+ 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)
- .setInitialLoadSizeHint(PAGE_SIZE * initPages)
.setPrefetchDistance(prefetchDistance)
.build(),
loadPosition);
}
@Test
- public void computeFirstLoadPosition_zero() {
- assertEquals(0, TiledPagedList.computeFirstLoadPosition(0, 30, 10, 100));
- }
-
- @Test
- public void computeFirstLoadPosition_requestedPositionIncluded() {
- assertEquals(0, TiledPagedList.computeFirstLoadPosition(10, 10, 10, 100));
- }
-
- @Test
- public void computeFirstLoadPosition_endAdjusted() {
- assertEquals(70, TiledPagedList.computeFirstLoadPosition(99, 30, 10, 100));
- }
-
- @Test
- public void initialLoad_onePage() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
- verifyRange(pagedList, 0, 1);
- }
-
- @Test
- public void initialLoad_onePageOffset() {
- TiledPagedList<Item> pagedList = createTiledPagedList(10, 1);
- verifyRange(pagedList, 0, 1);
- }
-
- @Test
- public void initialLoad_full() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 100);
- verifyRange(pagedList, 0, 1, 2, 3, 4);
+ public void initialLoad() {
+ TiledPagedList<Item> pagedList = createTiledPagedList(0);
+ verifyRange(pagedList, 0);
}
@Test
public void initialLoad_end() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44, 2);
+ TiledPagedList<Item> pagedList = createTiledPagedList(44);
verifyRange(pagedList, 3, 4);
}
@Test
public void initialLoad_multiple() {
- TiledPagedList<Item> pagedList = createTiledPagedList(9, 2);
+ TiledPagedList<Item> pagedList = createTiledPagedList(9);
verifyRange(pagedList, 0, 1);
}
@Test
public void initialLoad_offset() {
- TiledPagedList<Item> pagedList = createTiledPagedList(41, 2);
+ TiledPagedList<Item> pagedList = createTiledPagedList(41);
verifyRange(pagedList, 3, 4);
}
@Test
public void append() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
+ TiledPagedList<Item> pagedList = createTiledPagedList(0);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0, 1);
+ verifyRange(pagedList, 0);
verifyZeroInteractions(callback);
- pagedList.loadAround(15);
-
- verifyRange(pagedList, 0, 1);
-
+ pagedList.loadAround(5);
drain();
- verifyRange(pagedList, 0, 1, 2);
- verify(callback).onChanged(20, 10);
+ verifyRange(pagedList, 0, 1);
+ verify(callback).onChanged(10, 10);
verifyNoMoreInteractions(callback);
}
@Test
public void prepend() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44, 2);
+ TiledPagedList<Item> pagedList = createTiledPagedList(44);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(pagedList, 3, 4);
@@ -194,16 +162,16 @@ public class TiledPagedListTest {
@Test
public void loadWithGap() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
+ TiledPagedList<Item> pagedList = createTiledPagedList(0);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0, 1);
+ verifyRange(pagedList, 0);
verifyZeroInteractions(callback);
pagedList.loadAround(44);
drain();
- verifyRange(pagedList, 0, 1, 3, 4);
+ verifyRange(pagedList, 0, 3, 4);
verify(callback).onChanged(30, 10);
verify(callback).onChanged(40, 5);
verifyNoMoreInteractions(callback);
@@ -211,56 +179,58 @@ public class TiledPagedListTest {
@Test
public void tinyPrefetchTest() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 1, 1);
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0, 1);
+ verifyRange(pagedList, 0); // just 4 loaded
verifyZeroInteractions(callback);
- pagedList.loadAround(33);
+ pagedList.loadAround(23);
drain();
- verifyRange(pagedList, 0, 1, 3);
- verify(callback).onChanged(30, 10);
+ verifyRange(pagedList, 0, 2);
+ verify(callback).onChanged(20, 10);
verifyNoMoreInteractions(callback);
pagedList.loadAround(44);
drain();
- verifyRange(pagedList, 0, 1, 3, 4);
+ verifyRange(pagedList, 0, 2, 4);
verify(callback).onChanged(40, 5);
verifyNoMoreInteractions(callback);
}
@Test
public void appendCallbackAddedLate() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 1, 0);
- verifyRange(pagedList, 0, 1);
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 0);
+ verifyRange(pagedList, 0);
- pagedList.loadAround(25);
+ pagedList.loadAround(15);
drain();
- verifyRange(pagedList, 0, 1, 2);
+ verifyRange(pagedList, 0, 1);
+
+ // snapshot at 20 items
+ PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
+ verifyRange(snapshot, 0, 1);
- // snapshot at 30 items
- List<Item> snapshot = pagedList.snapshot();
- verifyRange(snapshot, 0, 1, 2);
+ pagedList.loadAround(25);
pagedList.loadAround(35);
- pagedList.loadAround(44);
drain();
- verifyRange(pagedList, 0, 1, 2, 3, 4);
- verifyRange(snapshot, 0, 1, 2);
+ verifyRange(pagedList, 0, 1, 2, 3);
+ verifyRange(snapshot, 0, 1);
PagedList.Callback callback = mock(
PagedList.Callback.class);
pagedList.addWeakCallback(snapshot, callback);
- verify(callback).onChanged(30, 20);
+ verify(callback).onChanged(20, 20);
verifyNoMoreInteractions(callback);
}
+
@Test
public void prependCallbackAddedLate() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44, 2, 0);
+ TiledPagedList<Item> pagedList = createTiledPagedList(44, 0);
verifyRange(pagedList, 3, 4);
pagedList.loadAround(25);
@@ -268,9 +238,10 @@ public class TiledPagedListTest {
verifyRange(pagedList, 2, 3, 4);
// snapshot at 30 items
- List<Item> snapshot = pagedList.snapshot();
+ PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
verifyRange(snapshot, 2, 3, 4);
+
pagedList.loadAround(15);
pagedList.loadAround(5);
drain();
@@ -301,11 +272,10 @@ public class TiledPagedListTest {
assertTrue(pagedList.isContiguous());
- ContiguousPagedList<Integer, Item> contiguousPagedList =
- (ContiguousPagedList<Integer, Item>) pagedList;
- assertEquals(0, contiguousPagedList.mStorage.getLeadingNullCount());
- assertEquals(PAGE_SIZE, contiguousPagedList.mStorage.getStorageCount());
- assertEquals(0, contiguousPagedList.mStorage.getTrailingNullCount());
+ ContiguousPagedList<Item> contiguousPagedList = (ContiguousPagedList<Item>) pagedList;
+ assertEquals(0, contiguousPagedList.getLeadingNullCount());
+ assertEquals(PAGE_SIZE, contiguousPagedList.mList.size());
+ assertEquals(0, contiguousPagedList.getTrailingNullCount());
}
private void drain() {
diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java
index b31dc13a..45ec0289 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(@NonNull Observer observer) {
+ public void addObserver(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(@NonNull final Observer observer) {
+ public void removeObserver(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 d55bbfe8..72066992 100644
--- a/android/arch/persistence/room/Relation.java
+++ b/android/arch/persistence/room/Relation.java
@@ -28,8 +28,6 @@ import java.lang.annotation.Target;
* <pre>
* {@literal @}Entity
* public class Pet {
- * {@literal @} PrimaryKey
- * int id;
* int userId;
* String name;
* // other fields
@@ -43,8 +41,8 @@ import java.lang.annotation.Target;
*
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT id, name from User")
- * public List&lt;UserNameAndAllPets&gt; loadUserAndPets();
+ * {@literal @}Query("SELECT id, name from User WHERE age &gt; :minAge")
+ * public List&lt;UserNameAndAllPets&gt; loadUserAndPets(int minAge);
* }
* </pre>
* <p>
@@ -65,16 +63,16 @@ import java.lang.annotation.Target;
* {@literal @}Embedded
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
- * public List&lt;PetNameAndId&gt; pets;
+ * public List<PetNameAndId> pets;
* }
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT * from User")
- * public List&lt;UserAllPets&gt; loadUserAndPets();
+ * {@literal @}Query("SELECT * from User WHERE age &gt; :minAge")
+ * public List&lt;UserAllPets&gt; loadUserAndPets(int minAge);
* }
* </pre>
* <p>
- * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched
+ * In the example above, {@code PetNameAndId} is a regular 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.
@@ -87,7 +85,7 @@ import java.lang.annotation.Target;
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class,
* projection = {"name"})
- * public List&lt;String&gt; petNames;
+ * public List<String> petNames;
* }
* </pre>
* <p>
@@ -95,7 +93,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 2850b55e..8ce4be0c 100644
--- a/android/arch/persistence/room/Room.java
+++ b/android/arch/persistence/room/Room.java
@@ -43,7 +43,6 @@ 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
@@ -66,7 +65,6 @@ 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 8c940246..cdad868d 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,9 +153,7 @@ public abstract class RoomDatabase {
*
* @hide
*/
- @SuppressWarnings("WeakerAccess")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- // used in generated code
public void assertNotMainThread() {
if (mAllowMainThreadQueries) {
return;
@@ -300,7 +298,6 @@ 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();
}
@@ -310,6 +307,7 @@ 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;
@@ -339,8 +337,7 @@ public abstract class RoomDatabase {
* @param factory The factory to use to access the database.
* @return this
*/
- @NonNull
- public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
+ public Builder<T> openHelperFactory(SupportSQLiteOpenHelper.Factory factory) {
mFactory = factory;
return this;
}
@@ -364,7 +361,6 @@ public abstract class RoomDatabase {
* changes.
* @return this
*/
- @NonNull
public Builder<T> addMigrations(Migration... migrations) {
mMigrationContainer.addMigrations(migrations);
return this;
@@ -382,7 +378,6 @@ public abstract class RoomDatabase {
*
* @return this
*/
- @NonNull
public Builder<T> allowMainThreadQueries() {
mAllowMainThreadQueries = true;
return this;
@@ -405,7 +400,6 @@ public abstract class RoomDatabase {
*
* @return this
*/
- @NonNull
public Builder<T> fallbackToDestructiveMigration() {
mRequireMigration = false;
return this;
@@ -417,7 +411,6 @@ public abstract class RoomDatabase {
* @param callback The callback.
* @return this
*/
- @NonNull
public Builder<T> addCallback(@NonNull Callback callback) {
if (mCallbacks == null) {
mCallbacks = new ArrayList<>();
@@ -434,7 +427,6 @@ public abstract class RoomDatabase {
*
* @return A new database instance.
*/
- @NonNull
public T build() {
//noinspection ConstantConditions
if (mContext == null) {
@@ -501,7 +493,6 @@ 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 f05e6be2..c64be967 100644
--- a/android/arch/persistence/room/RoomWarnings.java
+++ b/android/arch/persistence/room/RoomWarnings.java
@@ -125,12 +125,4 @@ 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 3b6ede9c..914e4f41 100644
--- a/android/arch/persistence/room/Transaction.java
+++ b/android/arch/persistence/room/Transaction.java
@@ -22,10 +22,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Marks a method in a {@link Dao} class as a transaction method.
+ * Marks a method in an abstract {@link Dao} class as a transaction method.
* <p>
- * 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.
+ * 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>
@@ -45,38 +44,6 @@ 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 cdd464e4..818c46b4 100644
--- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -86,7 +86,6 @@ 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 b5df914a..9d402370 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 DESC LIMIT :limit")
+ @Query("SELECT * from customer ORDER BY mLastName ASC 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
new file mode 100644
index 00000000..3cbffc8b
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 33f40183..84f20ec5 100644
--- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -17,17 +17,20 @@
package android.arch.persistence.room.integration.testapp.test;
import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.TaskExecutor;
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;
@@ -35,13 +38,17 @@ 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.
@@ -49,97 +56,138 @@ import java.util.concurrent.TimeoutException;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class InvalidationTest {
- @Rule
- public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule();
private UserDao mUserDao;
private TestDatabase mDb;
@Before
- public void createDb() throws TimeoutException, InterruptedException {
+ public void createDb() {
Context context = InstrumentationRegistry.getTargetContext();
mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
mUserDao = mDb.getUserDao();
- drain();
+ }
+
+ @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();
+ }
+ });
}
@After
- public void closeDb() throws TimeoutException, InterruptedException {
- mDb.close();
- drain();
+ public void clearExecutor() {
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
- private void drain() throws TimeoutException, InterruptedException {
- executorRule.drainTasks(1, TimeUnit.MINUTES);
+ 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);
+ }
}
@Test
- public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException {
+ public void testInvalidationOnUpdate() throws InterruptedException {
User user = TestUtil.createUser(3);
mUserDao.insert(user);
- LoggingObserver observer = new LoggingObserver("User");
+ LatchObserver observer = new LatchObserver(1, "User");
mDb.getInvalidationTracker().addObserver(observer);
- drain();
mUserDao.updateById(3, "foo2");
- drain();
+ waitUntilIOThreadIsIdle();
+ assertThat(observer.await(), is(true));
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testInvalidationOnDelete() throws InterruptedException, TimeoutException {
+ public void testInvalidationOnDelete() throws InterruptedException {
User user = TestUtil.createUser(3);
mUserDao.insert(user);
- LoggingObserver observer = new LoggingObserver("User");
+ LatchObserver observer = new LatchObserver(1, "User");
mDb.getInvalidationTracker().addObserver(observer);
- drain();
mUserDao.delete(user);
- drain();
+ waitUntilIOThreadIsIdle();
+ assertThat(observer.await(), is(true));
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testInvalidationOnInsert() throws InterruptedException, TimeoutException {
- LoggingObserver observer = new LoggingObserver("User");
+ public void testInvalidationOnInsert() throws InterruptedException {
+ LatchObserver observer = new LatchObserver(1, "User");
mDb.getInvalidationTracker().addObserver(observer);
- drain();
mUserDao.insert(TestUtil.createUser(3));
- drain();
+ waitUntilIOThreadIsIdle();
+ assertThat(observer.await(), is(true));
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException {
- LoggingObserver observer = new LoggingObserver("User");
+ public void testDontInvalidateOnLateInsert() throws InterruptedException {
+ LatchObserver observer = new LatchObserver(1, "User");
mUserDao.insert(TestUtil.createUser(3));
- drain();
+ waitUntilIOThreadIsIdle();
mDb.getInvalidationTracker().addObserver(observer);
- drain();
- assertThat(observer.getInvalidatedTables(), nullValue());
+ waitUntilIOThreadIsIdle();
+ assertThat(observer.await(), is(false));
}
@Test
- public void testMultipleTables() throws InterruptedException, TimeoutException {
- LoggingObserver observer = new LoggingObserver("User", "Pet");
+ public void testMultipleTables() throws InterruptedException {
+ LatchObserver observer = new LatchObserver(1, "User", "Pet");
mDb.getInvalidationTracker().addObserver(observer);
- drain();
mUserDao.insert(TestUtil.createUser(3));
- drain();
+ waitUntilIOThreadIsIdle();
+ assertThat(observer.await(), is(true));
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
- private static class LoggingObserver extends InvalidationTracker.Observer {
+ private static class LatchObserver extends InvalidationTracker.Observer {
+ CountDownLatch mLatch;
+
private Set<String> mInvalidatedTables;
- LoggingObserver(String... tables) {
+ LatchObserver(int permits, 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 2735c05a..e11117e4 100644
--- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
@@ -166,13 +166,17 @@ public class QueryDataSourceTest extends TestDatabaseTest {
p = dataSource.loadBefore(15, list.get(0), 10);
assertNotNull(p);
- list.addAll(0, p);
+ for (User u : p) {
+ list.add(0, u);
+ }
assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray());
p = dataSource.loadBefore(5, list.get(0), 10);
assertNotNull(p);
- list.addAll(0, p);
+ for (User u : p) {
+ list.add(0, u);
+ }
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
deleted file mode 100644
index 854c8627..00000000
--- a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
+++ /dev/null
@@ -1,471 +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.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 d69ea0dc..907e624b 100644
--- a/android/arch/persistence/room/migration/Migration.java
+++ b/android/arch/persistence/room/migration/Migration.java
@@ -17,7 +17,6 @@
package android.arch.persistence.room.migration;
import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.support.annotation.NonNull;
/**
* Base class for a database migration.
@@ -59,5 +58,5 @@ public abstract class Migration {
*
* @param database The database instance
*/
- public abstract void migrate(@NonNull SupportSQLiteDatabase database);
+ public abstract void migrate(SupportSQLiteDatabase database);
}
diff --git a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
index d72cf8cb..1467a4f0 100644
--- a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
+++ b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
@@ -16,18 +16,13 @@
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 2f9a8882..800514cc 100644
--- a/android/arch/persistence/room/paging/LimitOffsetDataSource.java
+++ b/android/arch/persistence/room/paging/LimitOffsetDataSource.java
@@ -49,13 +49,10 @@ 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,
- boolean inTransaction, String... tables) {
+ protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, 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) {
@@ -101,30 +98,13 @@ public abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> {
sqLiteQuery.copyArgumentsFrom(mSourceQuery);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
- 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();
- }
+ Cursor cursor = mDb.query(sqLiteQuery);
+
+ 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 d01e3c53..bee05ddd 100644
--- a/android/arch/persistence/room/util/StringUtil.java
+++ b/android/arch/persistence/room/util/StringUtil.java
@@ -17,7 +17,6 @@
package android.arch.persistence.room.util;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.util.Log;
import java.util.ArrayList;
@@ -25,14 +24,10 @@ import java.util.List;
import java.util.StringTokenizer;
/**
- * @hide
- *
* String utilities for Room
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressWarnings("WeakerAccess")
public class StringUtil {
-
- @SuppressWarnings("unused")
public static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* Returns a new StringBuilder to be used while producing SQL queries.
diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java
index cdeaea3e..5b2bf456 100644
--- a/android/content/ContentProvider.java
+++ b/android/content/ContentProvider.java
@@ -2099,7 +2099,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public static Uri maybeAddUserId(Uri uri, int userId) {
if (uri == null) return null;
if (userId != UserHandle.USER_CURRENT
- && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ && (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+ || ContentResolver.SCHEME_SLICE.equals(uri.getScheme()))) {
if (!uriHasUserId(uri)) {
//We don't add the user Id if there's already one
Uri.Builder builder = uri.buildUpon();
diff --git a/android/content/ContentResolver.java b/android/content/ContentResolver.java
index 9ccc552f..02e70f55 100644
--- a/android/content/ContentResolver.java
+++ b/android/content/ContentResolver.java
@@ -47,6 +47,8 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.slice.Slice;
+import android.slice.SliceProvider;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -178,6 +180,8 @@ public abstract class ContentResolver {
public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED =
new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
+ /** @hide */
+ public static final String SCHEME_SLICE = "slice";
public static final String SCHEME_CONTENT = "content";
public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
public static final String SCHEME_FILE = "file";
@@ -1718,6 +1722,36 @@ public abstract class ContentResolver {
}
/**
+ * Turns a slice Uri into slice content.
+ *
+ * @param uri The URI to a slice provider
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ * @hide
+ */
+ public final @Nullable Slice bindSlice(@NonNull Uri uri) {
+ Preconditions.checkNotNull(uri, "uri");
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+ final Bundle res = provider.call(mPackageName, SliceProvider.METHOD_SLICE, null,
+ extras);
+ Bundle.setDefusable(res, true);
+ return res.getParcelable(SliceProvider.EXTRA_SLICE);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
* Returns the content provider for the given content URI.
*
* @param uri The URI to a content provider
@@ -1725,7 +1759,7 @@ public abstract class ContentResolver {
* @hide
*/
public final IContentProvider acquireProvider(Uri uri) {
- if (!SCHEME_CONTENT.equals(uri.getScheme())) {
+ if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) {
return null;
}
final String auth = uri.getAuthority();
diff --git a/android/content/Intent.java b/android/content/Intent.java
index e47de752..c9ad9519 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -53,7 +53,6 @@ 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;
@@ -9372,57 +9371,6 @@ 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 a957aed8..c9bce530 100644
--- a/android/content/IntentFilter.java
+++ b/android/content/IntentFilter.java
@@ -26,7 +26,6 @@ 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;
@@ -919,15 +918,6 @@ 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;
}
@@ -1749,59 +1739,6 @@ 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 ff9fd8ec..9ee6fa24 100644
--- a/android/content/pm/FeatureInfo.java
+++ b/android/content/pm/FeatureInfo.java
@@ -18,7 +18,6 @@ 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
@@ -114,18 +113,6 @@ 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 b94a410b..aa9562ff 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.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
+import android.annotation.SdkConstant.SdkConstantType;
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,27 +282,12 @@ public class LauncherApps {
public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
/**
- * @hide include all pinned shortcuts by any launchers, not just by the caller,
- * 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
+ * Does not retrieve CHOOSER only shortcuts.
+ * TODO: Add another flag for MATCH_ALL_PINNED
* @hide
*/
public static final int FLAG_MATCH_ALL_KINDS =
- 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;
+ FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
/** @hide kept for unit tests */
@Deprecated
@@ -334,7 +319,6 @@ public class LauncherApps {
FLAG_MATCH_PINNED,
FLAG_MATCH_MANIFEST,
FLAG_GET_KEY_FIELDS_ONLY,
- FLAG_MATCH_MANIFEST,
})
@Retention(RetentionPolicy.SOURCE)
public @interface QueryFlags {}
@@ -694,21 +678,6 @@ 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}.
*
@@ -729,16 +698,10 @@ public class LauncherApps {
@NonNull UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
- // 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(),
+ return 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 143c51da..be7f921e 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -467,7 +467,6 @@ 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);
- /** Returns a PermissionGroup. */
- public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
- @NonNull String groupName);
+ /** temporary until mPermissionTrees is moved to PermissionManager */
+ public abstract Object enforcePermissionTreeTEMP(@NonNull String permName, int callingUid);
}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index ad36139a..6c7c8a07 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -3711,15 +3711,17 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_IS_GAME;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
- false)) {
- ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
+ if (false) {
+ 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.equals(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 != ai.packageName) {
+ outError[0] = "cantSaveState applications can not use custom processes";
+ }
}
}
}
@@ -6847,11 +6849,6 @@ 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 5dd7aeda..b45c26ce 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -353,11 +353,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
return size;
}
- /** @hide */
- public boolean isAppOp() {
- return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
- }
-
public static final Creator<PermissionInfo> CREATOR =
new Creator<PermissionInfo>() {
@Override
diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java
index 9ff07757..6b9c7537 100644
--- a/android/content/pm/ShortcutInfo.java
+++ b/android/content/pm/ShortcutInfo.java
@@ -18,7 +18,6 @@ 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;
@@ -101,13 +100,6 @@ 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 = {
@@ -166,124 +158,6 @@ 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";
@@ -366,11 +240,6 @@ 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();
@@ -483,7 +352,6 @@ 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;
@@ -747,23 +615,13 @@ public final class ShortcutInfo implements Parcelable {
/**
* @hide
- *
- * @isUpdating set true if it's "update", as opposed to "replace".
*/
- public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) {
- if (isUpdating) {
- Preconditions.checkState(isVisibleToPublisher(),
- "[Framework BUG] Invisible shortcuts can't be updated");
- }
+ public void ensureUpdatableWith(ShortcutInfo source) {
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");
-
- if (isVisibleToPublisher()) {
- // Don't do this check for restore-blocked shortcuts.
- Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
- }
+ Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
}
/**
@@ -780,7 +638,7 @@ public final class ShortcutInfo implements Parcelable {
* @hide
*/
public void copyNonNullFieldsFrom(ShortcutInfo source) {
- ensureUpdatableWith(source, /*isUpdating=*/ true);
+ ensureUpdatableWith(source);
if (source.mActivity != null) {
mActivity = source.mActivity;
@@ -1311,19 +1169,6 @@ 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.
*
@@ -1558,21 +1403,6 @@ 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.
@@ -1661,18 +1491,6 @@ 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>
@@ -1850,7 +1668,6 @@ 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.
@@ -1894,7 +1711,6 @@ public final class ShortcutInfo implements Parcelable {
dest.writeInt(mFlags);
dest.writeInt(mIconResId);
dest.writeLong(mLastChangedTimestamp);
- dest.writeInt(mDisabledReason);
if (hasKeyFieldsOnly()) {
dest.writeInt(0);
@@ -1992,11 +1808,6 @@ 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");
}
@@ -2037,9 +1848,7 @@ public final class ShortcutInfo implements Parcelable {
sb.append("packageName=");
sb.append(mPackageName);
- addIndentOrComma(sb, indent);
-
- sb.append("activity=");
+ sb.append(", activity=");
sb.append(mActivity);
addIndentOrComma(sb, indent);
@@ -2074,11 +1883,6 @@ 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);
@@ -2149,7 +1953,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 disabledReason) {
+ int flags, int iconResId, String iconResName, String bitmapPath) {
mUserId = userId;
mId = id;
mPackageName = packageName;
@@ -2174,6 +1978,5 @@ 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 7fc25d82..7b7d8ae4 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 callingPid, int callingUid);
+ int userId);
public abstract boolean
isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
@@ -58,8 +58,7 @@ public abstract class ShortcutServiceInternal {
public abstract Intent[] createShortcutIntents(
int launcherUserId, @NonNull String callingPackage,
- @NonNull String packageName, @NonNull String shortcutId, int userId,
- int callingPid, int callingUid);
+ @NonNull String packageName, @NonNull String shortcutId, int userId);
public abstract void addListener(@NonNull ShortcutChangeListener listener);
@@ -71,7 +70,7 @@ public abstract class ShortcutServiceInternal {
@NonNull String packageName, @NonNull String shortcutId, int userId);
public abstract boolean hasShortcutHostPermission(int launcherUserId,
- @NonNull String callingPackage, int callingPid, int callingUid);
+ @NonNull String callingPackage);
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
index 386239cf..a8b8c4b5 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 | StackOverflowError e) {
+ } catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
diff --git a/android/database/SQLiteDatabaseIoPerfTest.java b/android/database/SQLiteDatabaseIoPerfTest.java
deleted file mode 100644
index 7c5316d2..00000000
--- a/android/database/SQLiteDatabaseIoPerfTest.java
+++ /dev/null
@@ -1,176 +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.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
deleted file mode 100644
index 7a32c0cc..00000000
--- a/android/database/SQLiteDatabasePerfTest.java
+++ /dev/null
@@ -1,223 +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.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 0072012f..57c75490 100644
--- a/android/graphics/Bitmap.java
+++ b/android/graphics/Bitmap.java
@@ -21,7 +21,6 @@ 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;
@@ -1234,7 +1233,6 @@ 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 f5bf754a..ffb39e33 100644
--- a/android/graphics/BitmapFactory.java
+++ b/android/graphics/BitmapFactory.java
@@ -354,7 +354,6 @@ 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;
/**
@@ -413,7 +412,6 @@ public class BitmapFactory {
* can check, inbetween the bounds decode and the image decode, to see
* if the operation is canceled.
*/
- @Deprecated
public boolean mCancel;
/**
@@ -428,7 +426,6 @@ public class BitmapFactory {
* or if inJustDecodeBounds is true, will set outWidth/outHeight
* to -1
*/
- @Deprecated
public void requestCancelDecode() {
mCancel = true;
}
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 20405d3b..26ead3d1 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -202,22 +202,6 @@ 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
@@ -237,13 +221,6 @@ 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 760cc49b..4ea4e381 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/MediaSource.h!
+ * in include/media/stagefright/MediaSource.h!
*/
/**
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve
diff --git a/android/media/MediaRecorder.java b/android/media/MediaRecorder.java
index 76784904..59a124fa 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, "rw");
+ RandomAccessFile f = new RandomAccessFile(file, "rws");
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, "rw");
+ RandomAccessFile file = new RandomAccessFile(mPath, "rws");
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, "rw");
+ RandomAccessFile file = new RandomAccessFile(mFile, "rws");
try {
_setOutputFile(file.getFD());
} finally {
diff --git a/android/media/tv/TvInputManager.java b/android/media/tv/TvInputManager.java
index fd1f2cf6..d7a9edef 100644
--- a/android/media/tv/TvInputManager.java
+++ b/android/media/tv/TvInputManager.java
@@ -2590,9 +2590,12 @@ public final class TvInputManager {
}
}
- /** @removed */
public boolean dispatchKeyEventToHdmi(KeyEvent event) {
- return false;
+ try {
+ return mInterface.dispatchKeyEventToHdmi(event);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
}
public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java
index 4e474c8e..2c9fb23e 100644
--- a/android/net/LinkProperties.java
+++ b/android/net/LinkProperties.java
@@ -683,9 +683,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4Address() {
for (LinkAddress address : mLinkAddresses) {
- if (address.getAddress() instanceof Inet4Address) {
- return true;
- }
+ if (address.getAddress() instanceof Inet4Address) {
+ return true;
+ }
}
return false;
}
@@ -725,9 +725,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4DefaultRoute() {
for (RouteInfo r : mRoutes) {
- if (r.isIPv4Default()) {
- return true;
- }
+ if (r.isIPv4Default()) {
+ return true;
+ }
}
return false;
}
@@ -740,9 +740,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv6DefaultRoute() {
for (RouteInfo r : mRoutes) {
- if (r.isIPv6Default()) {
- return true;
- }
+ if (r.isIPv6Default()) {
+ return true;
+ }
}
return false;
}
@@ -755,9 +755,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4DnsServer() {
for (InetAddress ia : mDnses) {
- if (ia instanceof Inet4Address) {
- return true;
- }
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
}
return false;
}
@@ -770,9 +770,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv6DnsServer() {
for (InetAddress ia : mDnses) {
- if (ia instanceof Inet6Address) {
- return true;
- }
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
}
return false;
}
diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java
index 1925c39e..0230f36b 100644
--- a/android/net/ip/ConnectivityPacketTracker.java
+++ b/android/net/ip/ConnectivityPacketTracker.java
@@ -25,7 +25,6 @@ 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;
@@ -60,14 +59,11 @@ 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;
@@ -89,16 +85,14 @@ public class ConnectivityPacketTracker {
mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
}
- public void start(String displayName) {
+ public void start() {
mRunning = true;
- mDisplayName = displayName;
mPacketListener.start();
}
public void stop() {
mPacketListener.stop();
mRunning = false;
- mDisplayName = null;
}
private final class PacketListener extends BlockingSocketReader {
@@ -139,19 +133,16 @@ public class ConnectivityPacketTracker {
@Override
protected void onStart() {
- final String msg = TextUtils.isEmpty(mDisplayName)
- ? MARK_START
- : String.format(MARK_NAMED_START, mDisplayName);
- mLog.log(msg);
+ mLog.log(MARK_START);
}
@Override
protected void onStop() {
- String msg = TextUtils.isEmpty(mDisplayName)
- ? MARK_STOP
- : String.format(MARK_NAMED_STOP, mDisplayName);
- if (!mRunning) msg += " (packet listener stopped unexpectedly)";
- mLog.log(msg);
+ if (mRunning) {
+ mLog.log(MARK_STOP);
+ } else {
+ mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)");
+ }
}
@Override
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index e33f6c99..bc07b810 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -26,7 +26,6 @@ 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;
@@ -349,16 +348,6 @@ public class IpManager extends StateMachine {
return this;
}
- public Builder withNetwork(Network network) {
- mConfig.mNetwork = network;
- return this;
- }
-
- public Builder withDisplayName(String displayName) {
- mConfig.mDisplayName = displayName;
- return this;
- }
-
public ProvisioningConfiguration build() {
return new ProvisioningConfiguration(mConfig);
}
@@ -373,8 +362,6 @@ public class IpManager extends StateMachine {
/* package */ ApfCapabilities mApfCapabilities;
/* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
/* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
- /* package */ Network mNetwork = null;
- /* package */ String mDisplayName = null;
public ProvisioningConfiguration() {} // used by Builder
@@ -387,9 +374,6 @@ public class IpManager extends StateMachine {
mStaticIpConfig = other.mStaticIpConfig;
mApfCapabilities = other.mApfCapabilities;
mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
- mIPv6AddrGenMode = other.mIPv6AddrGenMode;
- mNetwork = other.mNetwork;
- mDisplayName = other.mDisplayName;
}
@Override
@@ -404,8 +388,6 @@ public class IpManager extends StateMachine {
.add("mApfCapabilities: " + mApfCapabilities)
.add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
.add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
- .add("mNetwork: " + mNetwork)
- .add("mDisplayName: " + mDisplayName)
.toString();
}
@@ -1459,10 +1441,10 @@ public class IpManager extends StateMachine {
@Override
public void enter() {
// Get the Configuration for ApfFilter from Context
- final boolean filter802_3Frames =
+ boolean filter802_3Frames =
mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
- final int[] ethTypeBlackList = mContext.getResources().getIntArray(
+ int[] ethTypeBlackList = mContext.getResources().getIntArray(
R.array.config_apfEthTypeBlackList);
mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
@@ -1474,7 +1456,7 @@ public class IpManager extends StateMachine {
}
mPacketTracker = createPacketTracker();
- if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+ if (mPacketTracker != null) mPacketTracker.start();
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
@@ -1488,7 +1470,7 @@ public class IpManager extends StateMachine {
return;
}
- final InitialConfiguration config = mConfiguration.mInitialConfig;
+ InitialConfiguration config = mConfiguration.mInitialConfig;
if ((config != null) && !applyInitialConfig(config)) {
// TODO introduce a new IpManagerEvent constant to distinguish this error case.
doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
diff --git a/android/net/util/SharedLog.java b/android/net/util/SharedLog.java
index bbd3d13e..343d237f 100644
--- a/android/net/util/SharedLog.java
+++ b/android/net/util/SharedLog.java
@@ -106,10 +106,6 @@ public class SharedLog {
record(Category.NONE, msg);
}
- public void logf(String fmt, Object... args) {
- log(String.format(fmt, args));
- }
-
public void mark(String msg) {
record(Category.MARK, msg);
}
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 59956964..98819279 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -1911,13 +1911,6 @@ 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.
*
@@ -2026,14 +2019,6 @@ 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}
@@ -2041,12 +2026,6 @@ 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).
*
@@ -2129,11 +2108,6 @@ 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;
@@ -2293,13 +2267,6 @@ 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.
*
@@ -2315,13 +2282,6 @@ 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;
/**
@@ -2341,13 +2301,6 @@ 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.
*
@@ -2534,13 +2487,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getDischargeAmountScreenOffSinceCharge();
/**
- * Get the amount the battery has discharged while the screen was dozing,
+ * Get the amount the battery has discharged while the screen was doze,
* since the last time power was unplugged.
*/
public abstract int getDischargeAmountScreenDoze();
/**
- * Get the amount the battery has discharged while the screen was dozing,
+ * Get the amount the battery has discharged while the screen was doze,
* since the last time the device was charged.
*/
public abstract int getDischargeAmountScreenDozeSinceCharge();
@@ -2673,20 +2626,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 getUahDischargeScreenOff(int which);
+ public abstract long getMahDischargeScreenOff(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 getUahDischargeScreenDoze(int which);
+ public abstract long getMahDischargeScreenDoze(int which);
/**
* Return the amount of battery discharge measured in micro-Ampere-hours. This will be
* non-zero only if the device's battery has a coulomb counter.
*/
- public abstract long getUahDischarge(int which);
+ public abstract long getMahDischarge(int which);
/**
* Returns the estimated real battery capacity, which may be less than the capacity
@@ -3031,7 +2984,7 @@ public abstract class BatteryStats implements Parcelable {
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
/ 1000;
final int count = timer.getCountLocked(which);
- if (totalTime != 0 || count != 0) {
+ if (totalTime != 0) {
dumpLine(pw, uid, category, type, totalTime, count);
}
}
@@ -3047,12 +3000,12 @@ public abstract class BatteryStats implements Parcelable {
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
*/
private static void dumpTimer(ProtoOutputStream proto, long fieldId,
- Timer timer, long rawRealtimeUs, int which) {
+ Timer timer, long rawRealtime, int which) {
if (timer == null) {
return;
}
// Convert from microseconds to milliseconds with rounding
- final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtimeUs, which) + 500) / 1000;
+ final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
final int count = timer.getCountLocked(which);
if (totalTimeMs != 0 || count != 0) {
final long token = proto.start(fieldId);
@@ -3161,104 +3114,71 @@ 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);
- 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());
+ final long totalTimeMs = idleTimeMs + rxTimeMs + totalTxTimeMs;
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, totalControllerActivityTimeMs));
+ sb.append(formatRatioLocked(idleTimeMs, totalTimeMs));
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, totalControllerActivityTimeMs));
+ sb.append(formatRatioLocked(rxTimeMs, totalTimeMs));
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());
- 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);
+ final int numTxLvls = counter.getTxTimeCounters().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(powerLevel[lvl]);
- sb.append(" ");
+ sb.append(" [");
+ sb.append(lvl);
+ sb.append("] ");
formatTimeMs(sb, txLvlTimeMs);
sb.append("(");
- sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
+ sb.append(formatRatioLocked(txLvlTimeMs, totalTxTimeMs));
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());
}
- if (powerDrainMaMs > 0) {
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" ");
- sb.append(controllerName);
- sb.append(" Battery drain: ").append(
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(controllerName);
+ sb.append(" Power drain: ").append(
BatteryStatsHelper.makemAh(powerDrainMaMs / (double) (1000*60*60)));
- sb.append("mAh");
- pw.println(sb.toString());
- }
+ sb.append("mAh");
+ pw.println(sb.toString());
}
/**
@@ -3271,13 +3191,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 microseconds, unless specified otherwise.
+ * NOTE: all times are expressed in 'ms'.
*/
public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid,
boolean wifiOnly) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
- final long rawRealtimeMs = SystemClock.elapsedRealtime();
- final long rawRealtime = rawRealtimeMs * 1000;
+ final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
@@ -3300,9 +3220,9 @@ public abstract class BatteryStats implements Parcelable {
rawRealtime, which);
final int connChanges = getNumConnectivityChange(which);
final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
- final long dischargeCount = getUahDischarge(which);
- final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
- final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+ final long dischargeCount = getMahDischarge(which);
+ final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
+ final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
final StringBuilder sb = new StringBuilder(128);
@@ -3540,9 +3460,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:
@@ -3583,9 +3503,6 @@ public abstract class BatteryStats implements Parcelable {
case CAMERA:
label = "camera";
break;
- case MEMORY:
- label = "memory";
- break;
default:
label = "???";
}
@@ -3606,7 +3523,6 @@ 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) {
@@ -4104,7 +4020,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeCount = getUahDischarge(which);
+ final long dischargeCount = getMahDischarge(which);
if (dischargeCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4114,7 +4030,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
+ final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
if (dischargeScreenOffCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4124,7 +4040,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+ final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
if (dischargeScreenDozeCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4330,116 +4246,126 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- pw.println("");
- pw.print(prefix);
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" CONNECTIVITY POWER SUMMARY START");
- pw.println(sb.toString());
-
- pw.print(prefix);
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Logging duration for connectivity statistics: ");
- formatTimeMs(sb, whichBatteryRealtime / 1000);
- pw.println(sb.toString());
-
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Cellular Statistics:");
- pw.println(sb.toString());
-
pw.print(prefix);
+ 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(" 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:");
+ sb.append(" Phone signal levels:");
didOne = false;
- for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- final long time = getPhoneDataConnectionTime(i, rawRealtime, which);
+ 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("\n ");
sb.append(prefix);
didOne = true;
- sb.append(DATA_CONNECTION_NAMES[i]);
+ 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)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- 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): "};
+ sb.append(" Signal scanning time: ");
+ formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000);
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Radio types:");
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);
+ 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(cellularRxSignalStrengthDescription[i]);
+ sb.append(DATA_CONNECTION_NAMES[i]);
sb.append(" ");
formatTimeMs(sb, time/1000);
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());
- printControllerActivity(pw, sb, prefix, "Cellular",
- getModemControllerActivity(), which);
+ 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(prefix);
+ sb.append(" Mobile radio active adjusted time: ");
+ formatTimeMs(sb, mobileActiveAdjustedTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(mobileActiveAdjustedTime, whichBatteryRealtime));
+ sb.append(")");
+ pw.println(sb.toString());
+ }
+
+ printControllerActivity(pw, sb, prefix, "Radio", 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 Statistics:");
+ 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(")");
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(" ");
@@ -4447,20 +4373,22 @@ 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(" ");
@@ -4468,23 +4396,17 @@ 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 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): "};
+ sb.append(" Wifi signal levels:");
didOne = false;
- final int numWifiRxBins = Math.min(NUM_WIFI_SIGNAL_STRENGTH_BINS,
- wifiRxSignalStrengthDescription.length);
- for (int i=0; i<numWifiRxBins; i++) {
+ for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
final long time = getWifiSignalStrengthTime(i, rawRealtime, which);
if (time == 0) {
continue;
@@ -4492,12 +4414,15 @@ public abstract class BatteryStats implements Parcelable {
sb.append("\n ");
sb.append(prefix);
didOne = true;
- sb.append(" ");
- sb.append(wifiRxSignalStrengthDescription[i]);
+ sb.append("level(");
+ sb.append(i);
+ sb.append(") ");
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());
@@ -4505,13 +4430,6 @@ 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));
@@ -6120,61 +6038,6 @@ public abstract class BatteryStats implements Parcelable {
return true;
}
- private static void dumpDurationSteps(ProtoOutputStream proto, long fieldId,
- LevelStepTracker steps) {
- if (steps == null) {
- return;
- }
- int count = steps.mNumStepDurations;
- long token;
- for (int i = 0; i < count; ++i) {
- token = proto.start(fieldId);
- proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
- proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
-
- final long initMode = steps.getInitModeAt(i);
- final long modMode = steps.getModModeAt(i);
-
- int ds = SystemProto.BatteryLevelStep.DS_MIXED;
- if ((modMode & STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
- switch ((int) (initMode & STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
- case Display.STATE_OFF:
- ds = SystemProto.BatteryLevelStep.DS_OFF;
- break;
- case Display.STATE_ON:
- ds = SystemProto.BatteryLevelStep.DS_ON;
- break;
- case Display.STATE_DOZE:
- ds = SystemProto.BatteryLevelStep.DS_DOZE;
- break;
- case Display.STATE_DOZE_SUSPEND:
- ds = SystemProto.BatteryLevelStep.DS_DOZE_SUSPEND;
- break;
- default:
- ds = SystemProto.BatteryLevelStep.DS_ERROR;
- break;
- }
- }
- proto.write(SystemProto.BatteryLevelStep.DISPLAY_STATE, ds);
-
- int psm = SystemProto.BatteryLevelStep.PSM_MIXED;
- if ((modMode & STEP_LEVEL_MODE_POWER_SAVE) == 0) {
- psm = (initMode & STEP_LEVEL_MODE_POWER_SAVE) != 0
- ? SystemProto.BatteryLevelStep.PSM_ON : SystemProto.BatteryLevelStep.PSM_OFF;
- }
- proto.write(SystemProto.BatteryLevelStep.POWER_SAVE_MODE, psm);
-
- int im = SystemProto.BatteryLevelStep.IM_MIXED;
- if ((modMode & STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
- im = (initMode & STEP_LEVEL_MODE_DEVICE_IDLE) != 0
- ? SystemProto.BatteryLevelStep.IM_ON : SystemProto.BatteryLevelStep.IM_OFF;
- }
- proto.write(SystemProto.BatteryLevelStep.IDLE_MODE, im);
-
- proto.end(token);
- }
- }
-
public static final int DUMP_CHARGED_ONLY = 1<<1;
public static final int DUMP_DAILY_ONLY = 1<<2;
public static final int DUMP_HISTORY_ONLY = 1<<3;
@@ -6600,7 +6463,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- /** Dump #STATS_SINCE_CHARGED batterystats data to a proto. @hide */
+ /** Dump batterystats data to a proto. @hide */
public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
int flags, long historyStart) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
@@ -6622,376 +6485,10 @@ public abstract class BatteryStats implements Parcelable {
if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
// TODO: implement dumpProtoAppsLocked(proto, apps);
- dumpProtoSystemLocked(context, proto, (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+ // TODO: implement dumpProtoSystemLocked(proto);
}
proto.end(bToken);
proto.flush();
}
-
- private void dumpProtoSystemLocked(Context context, ProtoOutputStream proto, boolean wifiOnly) {
- final long sToken = proto.start(BatteryStatsProto.SYSTEM);
- final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
- final long rawRealtimeMs = SystemClock.elapsedRealtime();
- final long rawRealtimeUs = rawRealtimeMs * 1000;
- final int which = STATS_SINCE_CHARGED;
-
- // Battery data (BATTERY_DATA)
- long token = proto.start(SystemProto.BATTERY);
- proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
- proto.write(SystemProto.Battery.START_COUNT, getStartCount());
- proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
- computeRealtime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Battery.TOTAL_UPTIME_MS,
- computeUptime(rawUptimeUs, which) / 1000);
- proto.write(SystemProto.Battery.BATTERY_REALTIME_MS,
- computeBatteryRealtime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Battery.BATTERY_UPTIME_MS,
- computeBatteryUptime(rawUptimeUs, which) / 1000);
- proto.write(SystemProto.Battery.SCREEN_OFF_REALTIME_MS,
- computeBatteryScreenOffRealtime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Battery.SCREEN_OFF_UPTIME_MS,
- computeBatteryScreenOffUptime(rawUptimeUs, which) / 1000);
- proto.write(SystemProto.Battery.SCREEN_DOZE_DURATION_MS,
- getScreenDozeTime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Battery.ESTIMATED_BATTERY_CAPACITY_MAH,
- getEstimatedBatteryCapacity());
- proto.write(SystemProto.Battery.MIN_LEARNED_BATTERY_CAPACITY_UAH,
- getMinLearnedBatteryCapacity());
- proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
- getMaxLearnedBatteryCapacity());
- proto.end(token);
-
- // Battery discharge (BATTERY_DISCHARGE_DATA)
- token = proto.start(SystemProto.BATTERY_DISCHARGE);
- proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
- getLowDischargeAmountSinceCharge());
- proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
- getHighDischargeAmountSinceCharge());
- proto.write(SystemProto.BatteryDischarge.SCREEN_ON_SINCE_CHARGE,
- getDischargeAmountScreenOnSinceCharge());
- proto.write(SystemProto.BatteryDischarge.SCREEN_OFF_SINCE_CHARGE,
- getDischargeAmountScreenOffSinceCharge());
- proto.write(SystemProto.BatteryDischarge.SCREEN_DOZE_SINCE_CHARGE,
- getDischargeAmountScreenDozeSinceCharge());
- proto.write(SystemProto.BatteryDischarge.TOTAL_MAH,
- getUahDischarge(which) / 1000);
- proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_OFF,
- getUahDischargeScreenOff(which) / 1000);
- proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
- getUahDischargeScreenDoze(which) / 1000);
- proto.end(token);
-
- // Time remaining
- long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
- if (timeRemainingUs >= 0) {
- // Charge time remaining (CHARGE_TIME_REMAIN_DATA)
- proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
- } else {
- timeRemainingUs = computeBatteryTimeRemaining(rawRealtimeUs);
- // Discharge time remaining (DISCHARGE_TIME_REMAIN_DATA)
- if (timeRemainingUs >= 0) {
- proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
- } else {
- proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, -1);
- }
- }
-
- // Charge step (CHARGE_STEP_DATA)
- dumpDurationSteps(proto, SystemProto.CHARGE_STEP, getChargeLevelStepTracker());
-
- // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
- for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
- token = proto.start(SystemProto.DATA_CONNECTION);
- proto.write(SystemProto.DataConnection.NAME, i);
- dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
- rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Discharge step (DISCHARGE_STEP_DATA)
- dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());
-
- // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
- final long[] cpuFreqs = getCpuFreqs();
- if (cpuFreqs != null) {
- for (long i : cpuFreqs) {
- proto.write(SystemProto.CPU_FREQUENCY, i);
- }
- }
-
- // Bluetooth controller (GLOBAL_BLUETOOTH_CONTROLLER_DATA)
- dumpControllerActivityProto(proto, SystemProto.GLOBAL_BLUETOOTH_CONTROLLER,
- getBluetoothControllerActivity(), which);
-
- // Modem controller (GLOBAL_MODEM_CONTROLLER_DATA)
- dumpControllerActivityProto(proto, SystemProto.GLOBAL_MODEM_CONTROLLER,
- getModemControllerActivity(), which);
-
- // Global network data (GLOBAL_NETWORK_DATA)
- token = proto.start(SystemProto.GLOBAL_NETWORK);
- proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
- getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
- getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_RX,
- getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_TX,
- getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_RX,
- getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_TX,
- getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_RX,
- getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_TX,
- getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.BT_BYTES_RX,
- getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
- proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
- getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
- proto.end(token);
-
- // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
- dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
- getWifiControllerActivity(), which);
-
-
- // Global wifi (GLOBAL_WIFI_DATA)
- token = proto.start(SystemProto.GLOBAL_WIFI);
- proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
- getWifiOnTime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
- getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
- proto.end(token);
-
- // Kernel wakelock (KERNEL_WAKELOCK_DATA)
- final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
- for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
- token = proto.start(SystemProto.KERNEL_WAKELOCK);
- proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
- dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
- rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Misc (MISC_DATA)
- // Calculate wakelock times across all uids.
- long fullWakeLockTimeTotalUs = 0;
- long partialWakeLockTimeTotalUs = 0;
-
- final SparseArray<? extends Uid> uidStats = getUidStats();
- for (int iu = 0; iu < uidStats.size(); iu++) {
- final Uid u = uidStats.valueAt(iu);
-
- final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks =
- u.getWakelockStats();
- for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
- final Uid.Wakelock wl = wakelocks.valueAt(iw);
-
- final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
- if (fullWakeTimer != null) {
- fullWakeLockTimeTotalUs += fullWakeTimer.getTotalTimeLocked(rawRealtimeUs,
- which);
- }
-
- final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
- if (partialWakeTimer != null) {
- partialWakeLockTimeTotalUs += partialWakeTimer.getTotalTimeLocked(
- rawRealtimeUs, which);
- }
- }
- }
- token = proto.start(SystemProto.MISC);
- proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
- getScreenOnTime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
- getPhoneOnTime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.FULL_WAKELOCK_TOTAL_DURATION_MS,
- fullWakeLockTimeTotalUs / 1000);
- proto.write(SystemProto.Misc.PARTIAL_WAKELOCK_TOTAL_DURATION_MS,
- partialWakeLockTimeTotalUs / 1000);
- proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_DURATION_MS,
- getMobileRadioActiveTime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_ADJUSTED_TIME_MS,
- getMobileRadioActiveAdjustedTime(which) / 1000);
- proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_COUNT,
- getMobileRadioActiveCount(which));
- proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_UNKNOWN_DURATION_MS,
- getMobileRadioActiveUnknownTime(which) / 1000);
- proto.write(SystemProto.Misc.INTERACTIVE_DURATION_MS,
- getInteractiveTime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.BATTERY_SAVER_MODE_ENABLED_DURATION_MS,
- getPowerSaveModeEnabledTime(rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.NUM_CONNECTIVITY_CHANGES,
- getNumConnectivityChange(which));
- proto.write(SystemProto.Misc.DEEP_DOZE_ENABLED_DURATION_MS,
- getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.DEEP_DOZE_COUNT,
- getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which));
- proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_DURATION_MS,
- getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_COUNT,
- getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which));
- proto.write(SystemProto.Misc.LONGEST_DEEP_DOZE_DURATION_MS,
- getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
- proto.write(SystemProto.Misc.LIGHT_DOZE_ENABLED_DURATION_MS,
- getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.LIGHT_DOZE_COUNT,
- getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
- proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_DURATION_MS,
- getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
- proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_COUNT,
- getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
- proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
- getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
- proto.end(token);
-
- final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
- helper.create(this);
- helper.refreshStats(which, UserHandle.USER_ALL);
-
- // Power use item (POWER_USE_ITEM_DATA)
- final List<BatterySipper> sippers = helper.getUsageList();
- if (sippers != null) {
- for (int i = 0; i < sippers.size(); ++i) {
- final BatterySipper bs = sippers.get(i);
- int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
- int uid = 0;
- switch (bs.drainType) {
- case IDLE:
- n = SystemProto.PowerUseItem.IDLE;
- break;
- case CELL:
- n = SystemProto.PowerUseItem.CELL;
- break;
- case PHONE:
- n = SystemProto.PowerUseItem.PHONE;
- break;
- case WIFI:
- n = SystemProto.PowerUseItem.WIFI;
- break;
- case BLUETOOTH:
- n = SystemProto.PowerUseItem.BLUETOOTH;
- break;
- case SCREEN:
- n = SystemProto.PowerUseItem.SCREEN;
- break;
- case FLASHLIGHT:
- n = SystemProto.PowerUseItem.FLASHLIGHT;
- break;
- case APP:
- // dumpProtoAppLocked will handle this.
- continue;
- case USER:
- n = SystemProto.PowerUseItem.USER;
- uid = UserHandle.getUid(bs.userId, 0);
- break;
- case UNACCOUNTED:
- n = SystemProto.PowerUseItem.UNACCOUNTED;
- break;
- case OVERCOUNTED:
- n = SystemProto.PowerUseItem.OVERCOUNTED;
- break;
- case CAMERA:
- n = SystemProto.PowerUseItem.CAMERA;
- break;
- case MEMORY:
- n = SystemProto.PowerUseItem.MEMORY;
- break;
- }
- token = proto.start(SystemProto.POWER_USE_ITEM);
- proto.write(SystemProto.PowerUseItem.NAME, n);
- proto.write(SystemProto.PowerUseItem.UID, uid);
- proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
- proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
- proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
- proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
- bs.proportionalSmearMah);
- proto.end(token);
- }
- }
-
- // Power use summary (POWER_USE_SUMMARY_DATA)
- token = proto.start(SystemProto.POWER_USE_SUMMARY);
- proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
- helper.getPowerProfile().getBatteryCapacity());
- proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
- proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
- proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
- proto.end(token);
-
- // RPM stats (RESOURCE_POWER_MANAGER_DATA)
- final Map<String, ? extends Timer> rpmStats = getRpmStats();
- final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
- for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
- token = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
- proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
- dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
- ent.getValue(), rawRealtimeUs, which);
- dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
- screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Screen brightness (SCREEN_BRIGHTNESS_DATA)
- for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
- token = proto.start(SystemProto.SCREEN_BRIGHTNESS);
- proto.write(SystemProto.ScreenBrightness.NAME, i);
- dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
- rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
- dumpTimer(proto, SystemProto.SIGNAL_SCANNING, getPhoneSignalScanningTimer(), rawRealtimeUs,
- which);
-
- // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
- for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
- token = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
- proto.write(SystemProto.PhoneSignalStrength.NAME, i);
- dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
- rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Wakeup reasons (WAKEUP_REASON_DATA)
- final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
- for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
- token = proto.start(SystemProto.WAKEUP_REASON);
- proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
- dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
- for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
- token = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
- proto.write(SystemProto.WifiSignalStrength.NAME, i);
- dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
- rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
- for (int i = 0; i < NUM_WIFI_STATES; ++i) {
- token = proto.start(SystemProto.WIFI_STATE);
- proto.write(SystemProto.WifiState.NAME, i);
- dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
- rawRealtimeUs, which);
- proto.end(token);
- }
-
- // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
- for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
- token = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
- proto.write(SystemProto.WifiSupplicantState.NAME, i);
- dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
- rawRealtimeUs, which);
- proto.end(token);
- }
-
- proto.end(sToken);
- }
}
diff --git a/android/os/Debug.java b/android/os/Debug.java
index 017c2134..b46c6b16 100644
--- a/android/os/Debug.java
+++ b/android/os/Debug.java
@@ -1748,26 +1748,22 @@ 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 = 8;
+ public static final int MEMINFO_SWAP_TOTAL = 6;
/** @hide */
- public static final int MEMINFO_SWAP_FREE = 9;
+ public static final int MEMINFO_SWAP_FREE = 7;
/** @hide */
- public static final int MEMINFO_ZRAM_TOTAL = 10;
+ public static final int MEMINFO_ZRAM_TOTAL = 8;
/** @hide */
- public static final int MEMINFO_MAPPED = 11;
+ public static final int MEMINFO_MAPPED = 9;
/** @hide */
- public static final int MEMINFO_VM_ALLOC_USED = 12;
+ public static final int MEMINFO_VM_ALLOC_USED = 10;
/** @hide */
- public static final int MEMINFO_PAGE_TABLES = 13;
+ public static final int MEMINFO_PAGE_TABLES = 11;
/** @hide */
- public static final int MEMINFO_KERNEL_STACK = 14;
+ public static final int MEMINFO_KERNEL_STACK = 12;
/** @hide */
- public static final int MEMINFO_COUNT = 15;
+ public static final int MEMINFO_COUNT = 13;
/**
* Retrieves /proc/meminfo. outSizes is filled with fields
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
index 7f588adb..c091420a 100644
--- a/android/os/ParcelFileDescriptor.java
+++ b/android/os/ParcelFileDescriptor.java
@@ -737,9 +737,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
private void closeWithStatus(int status, String msg) {
if (mClosed) return;
mClosed = true;
- if (mGuard != null) {
- mGuard.close();
- }
+ 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 76b21426..1f3a1e68 100644
--- a/android/os/PatternMatcher.java
+++ b/android/os/PatternMatcher.java
@@ -16,7 +16,7 @@
package android.os;
-import android.util.proto.ProtoOutputStream;
+import android.util.Log;
import java.util.Arrays;
@@ -131,17 +131,7 @@ 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;
}
@@ -151,7 +141,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 34c78455..f41848fa 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,29 @@
package android.os;
+import android.util.Log;
+
+import com.android.internal.os.BinderInternal;
+
+import java.util.HashMap;
import java.util.Map;
+/** @hide */
public final class ServiceManager {
+ private static final String TAG = "ServiceManager";
+ private static IServiceManager sServiceManager;
+ private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
+
+ private static IServiceManager getIServiceManager() {
+ if (sServiceManager != null) {
+ return sServiceManager;
+ }
+
+ // Find the service manager
+ sServiceManager = ServiceManagerNative
+ .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
+ return sServiceManager;
+ }
/**
* Returns a reference to a service with the given name.
@@ -27,14 +47,32 @@ public final class ServiceManager {
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
public static IBinder getService(String name) {
+ try {
+ IBinder service = sCache.get(name);
+ if (service != null) {
+ return service;
+ } else {
+ return Binder.allowBlocking(getIServiceManager().getService(name));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in getService", e);
+ }
return null;
}
/**
- * Is not supposed to return null, but that is fine for layoutlib.
+ * Returns a reference to a service with the given name, or throws
+ * {@link NullPointerException} if none is found.
+ *
+ * @hide
*/
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
- throw new ServiceNotFoundException(name);
+ final IBinder binder = getService(name);
+ if (binder != null) {
+ return binder;
+ } else {
+ throw new ServiceNotFoundException(name);
+ }
}
/**
@@ -45,7 +83,39 @@ public final class ServiceManager {
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- // pass
+ addService(name, service, false, IServiceManager.DUMP_PRIORITY_NORMAL);
+ }
+
+ /**
+ * Place a new @a service called @a name into the service
+ * manager.
+ *
+ * @param name the name of the new service
+ * @param service the service object
+ * @param allowIsolated set to true to allow isolated sandboxed processes
+ * to access this service
+ */
+ public static void addService(String name, IBinder service, boolean allowIsolated) {
+ addService(name, service, allowIsolated, IServiceManager.DUMP_PRIORITY_NORMAL);
+ }
+
+ /**
+ * Place a new @a service called @a name into the service
+ * manager.
+ *
+ * @param name the name of the new service
+ * @param service the service object
+ * @param allowIsolated set to true to allow isolated sandboxed processes
+ * @param dumpPriority supported dump priority levels as a bitmask
+ * to access this service
+ */
+ public static void addService(String name, IBinder service, boolean allowIsolated,
+ int dumpPriority) {
+ try {
+ getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in addService", e);
+ }
}
/**
@@ -53,7 +123,17 @@ public final class ServiceManager {
* service manager. Non-blocking.
*/
public static IBinder checkService(String name) {
- return null;
+ try {
+ IBinder service = sCache.get(name);
+ if (service != null) {
+ return service;
+ } else {
+ return Binder.allowBlocking(getIServiceManager().checkService(name));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in checkService", e);
+ return null;
+ }
}
/**
@@ -62,9 +142,12 @@ public final class ServiceManager {
* case of an exception
*/
public static String[] listServices() {
- // actual implementation returns null sometimes, so it's ok
- // to return null instead of an empty list.
- return null;
+ try {
+ return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in listServices", e);
+ return null;
+ }
}
/**
@@ -76,7 +159,10 @@ public final class ServiceManager {
* @hide
*/
public static void initServiceCache(Map<String, IBinder> cache) {
- // pass
+ if (sCache.size() != 0) {
+ throw new IllegalStateException("setServiceCache may only be called once");
+ }
+ sCache.putAll(cache);
}
/**
@@ -87,7 +173,6 @@ public final class ServiceManager {
* @hide
*/
public static class ServiceNotFoundException extends Exception {
- // identical to the original implementation
public ServiceNotFoundException(String name) {
super("No service published for: " + name);
}
diff --git a/android/os/SystemProperties.java b/android/os/SystemProperties.java
index 84111fbf..560b4b31 100644
--- a/android/os/SystemProperties.java
+++ b/android/os/SystemProperties.java
@@ -84,6 +84,9 @@ 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
*/
@@ -96,6 +99,9 @@ public class SystemProperties {
/**
* Get the String value for the given {@code key}.
*
+ * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
+ * method will crash in native code.
+ *
* @param key the key to lookup
* @param def the default value in case the property is not set or empty
* @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index 8c688713..430a5e3e 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -574,25 +574,6 @@ 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 3d2e1d1f..ee8eed19 100644
--- a/android/preference/SeekBarVolumizer.java
+++ b/android/preference/SeekBarVolumizer.java
@@ -206,7 +206,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
try {
mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
.getAudioAttributes())
- .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
+ .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
+ AudioAttributes.FLAG_BYPASS_MUTE)
.build());
mRingtone.play();
} catch (Throwable e) {
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index b4350746..a062db43 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -5708,7 +5708,6 @@ public final class Settings {
*
* @hide
*/
- @TestApi
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
"accessibility_display_magnification_enabled";
@@ -6793,6 +6792,14 @@ 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 ':'.
@@ -9568,22 +9575,6 @@ public final class Settings {
public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants";
/**
- * TextClassifier specific settings.
- * This is encoded as a key=value list, separated by commas. Ex:
- *
- * <pre>
- * smart_selection_dark_launch (boolean)
- * smart_selection_enabled_for_edit_text (boolean)
- * </pre>
- *
- * <p>
- * Type: string
- * @hide
- * see also android.view.textclassifier.TextClassifierConstants
- */
- public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
-
- /**
* Get the key that retrieves a bluetooth headset's priority.
* @hide
*/
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
index 953501c7..2e59f6c5 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 autofill UI with the options sent by the service.
+ * <li>The Android System displays an UI affordance with the options sent by the service.
* <li>The user picks an option.
* <li>The proper views are autofilled.
* </ol>
@@ -365,81 +365,6 @@ import com.android.internal.os.SomeArgs;
* <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
* verifications above, as long as the service can verify the authenticity of the browser app by
* checking its signing certificate.
- *
- * <a name="MultipleStepsSave"></a>
- * <h3>Saving when data is split in multiple screens</h3>
- *
- * Apps often split the user data in multiple screens in the same activity, specially in
- * activities used to create a new user account. For example, the first screen asks for a username,
- * and if the username is available, it moves to a second screen, which asks for a password.
- *
- * <p>It's tricky to handle save for autofill in these situations, because the autofill service must
- * wait until the user enters both fields before the autofill save UI can be shown. But it can be
- * done by following the steps below:
- *
- * <ol>
- * <li>In the first
- * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
- * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in
- * the response, containing the autofill ids of the partial fields present in the screen.
- * <li>In the second
- * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
- * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids
- * set in the previous request from the client state, and adds these ids and the
- * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second
- * response.
- * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the
- * proper {@link FillContext fill contexts} to get the value of each field (there is one fill
- * context per fill request).
- * </ol>
- *
- * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow
- * would be:
- * <pre class="prettyprint">
- * // On first fill request
- * AutofillId usernameId = // parse from AssistStructure;
- * Bundle clientState = new Bundle();
- * clientState.putParcelable("usernameId", usernameId);
- * fillCallback.onSuccess(
- * new FillResponse.Builder()
- * .setClientState(clientState)
- * .setSaveInfo(new SaveInfo
- * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId})
- * .build())
- * .build());
- *
- * // On second fill request
- * Bundle clientState = fillRequest.getClientState();
- * AutofillId usernameId = clientState.getParcelable("usernameId");
- * AutofillId passwordId = // parse from AssistStructure
- * clientState.putParcelable("passwordId", passwordId);
- * fillCallback.onSuccess(
- * new FillResponse.Builder()
- * .setClientState(clientState)
- * .setSaveInfo(new SaveInfo
- * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
- * new AutofillId[] {usernameId, passwordId})
- * .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
- * .build())
- * .build());
- *
- * // On save request
- * Bundle clientState = saveRequest.getClientState();
- * AutofillId usernameId = clientState.getParcelable("usernameId");
- * AutofillId passwordId = clientState.getParcelable("passwordId");
- * List<FillContext> fillContexts = saveRequest.getFillContexts();
- *
- * FillContext usernameContext = fillContexts.get(0);
- * ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId);
- * AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString();
- *
- * FillContext passwordContext = fillContexts.get(1);
- * ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId);
- * AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
- *
- * save(username, password);
- *
- * </pre>
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index fde2416f..1b9240cc 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 autofill save UI.
+ * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
* 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,17 +103,13 @@ 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 calls {@link AutofillManager#commit()}.
- * <li>All required views become invisible (if the {@link SaveInfo} was created with the
+ * <li>The app explicitly called {@link AutofillManager#commit()}.
+ * <li>All required views became 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:
@@ -127,13 +123,10 @@ 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 autofill save UI asking to save data for autofill.
+ * <li>The user explicitly tapped the UI affordance asking to save data for autofill.
* </ul>
*
- * <a name="CustomizingSaveUI"></a>
- * <h3>Customizing the autofill save UI</h3>
- *
- * <p>The service can also customize some aspects of the autofill save UI:
+ * <p>The service can also customize some aspects of the save UI affordance:
* <ul>
* <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
* <li>Add a customized subtitle by calling
@@ -219,25 +212,16 @@ public final class SaveInfo implements Parcelable {
@interface SaveDataType{}
/**
- * 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.
+ * 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.
*/
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, FLAG_DONT_SAVE_ON_FINISH})
+ value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
@Retention(RetentionPolicy.SOURCE)
@interface SaveInfoFlags{}
@@ -252,7 +236,6 @@ 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;
@@ -276,7 +259,6 @@ public final class SaveInfo implements Parcelable {
mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
}
}
- mTriggerId = builder.mTriggerId;
}
/** @hide */
@@ -338,12 +320,6 @@ public final class SaveInfo implements Parcelable {
return mSanitizerValues;
}
- /** @hide */
- @Nullable
- public AutofillId getTriggerId() {
- return mTriggerId;
- }
-
/**
* A builder for {@link SaveInfo} objects.
*/
@@ -362,7 +338,6 @@ 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.
@@ -419,15 +394,13 @@ public final class SaveInfo implements Parcelable {
/**
* Sets flags changing the save behavior.
*
- * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
- * {@link #FLAG_DONT_SAVE_ON_FINISH}, or {@code 0}.
+ * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
* @return This builder.
*/
public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
throwIfDestroyed();
- mFlags = Preconditions.checkFlagsArgument(flags,
- FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH);
+ mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
return this;
}
@@ -520,8 +493,8 @@ public final class SaveInfo implements Parcelable {
}
/**
- * Sets an object used to validate the user input - if the input is not valid, the
- * autofill save UI is not shown.
+ * Sets an object used to validate the user input - if the input is not valid, the Save UI
+ * affordance is not shown.
*
* <p>Typically used to validate credit card numbers. Examples:
*
@@ -547,7 +520,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">
@@ -642,27 +615,6 @@ 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.
*
@@ -700,14 +652,13 @@ public final class SaveInfo implements Parcelable {
.append(", description=").append(mDescription)
.append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
mNegativeButtonStyle))
- .append(", flags=").append(mFlags)
- .append(", customDescription=").append(mCustomDescription)
- .append(", validator=").append(mValidator)
+ .append(", mFlags=").append(mFlags)
+ .append(", mCustomDescription=").append(mCustomDescription)
+ .append(", validation=").append(mValidator)
.append(", sanitizerKeys=")
.append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
.append(", sanitizerValues=")
.append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
- .append(", triggerId=").append(mTriggerId)
.append("]").toString();
}
@@ -736,7 +687,6 @@ public final class SaveInfo implements Parcelable {
parcel.writeParcelableArray(mSanitizerValues[i], flags);
}
}
- parcel.writeParcelable(mTriggerId, flags);
parcel.writeInt(mFlags);
}
@@ -777,10 +727,6 @@ 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 f53967bd..65fdb5c4 100644
--- a/android/service/autofill/SaveRequest.java
+++ b/android/service/autofill/SaveRequest.java
@@ -19,6 +19,7 @@ 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;
@@ -59,14 +60,9 @@ public final class SaveRequest implements Parcelable {
}
/**
- * 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).
+ * Gets the extra client state returned from the last {@link
+ * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}
+ * fill request}.
*
* @return The client state.
*/
diff --git a/android/service/notification/ZenModeConfig.java b/android/service/notification/ZenModeConfig.java
index c5615ae6..7bec898a 100644
--- a/android/service/notification/ZenModeConfig.java
+++ b/android/service/notification/ZenModeConfig.java
@@ -76,13 +76,10 @@ public class ZenModeConfig implements Parcelable {
private static final int DAY_MINUTES = 24 * 60;
private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
- // 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_CALLS = true;
private static final boolean DEFAULT_ALLOW_MESSAGES = false;
- private static final boolean DEFAULT_ALLOW_REMINDERS = false;
- private static final boolean DEFAULT_ALLOW_EVENTS = false;
+ private static final boolean DEFAULT_ALLOW_REMINDERS = true;
+ private static final boolean DEFAULT_ALLOW_EVENTS = true;
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;
@@ -92,8 +89,6 @@ public class ZenModeConfig implements Parcelable {
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
private static final String ALLOW_TAG = "allow";
- private static final String ALLOW_ATT_ALARMS = "alarms";
- private static final String ALLOW_ATT_MEDIA = "media_system_other";
private static final String ALLOW_ATT_CALLS = "calls";
private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
private static final String ALLOW_ATT_MESSAGES = "messages";
@@ -105,6 +100,8 @@ 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";
@@ -126,8 +123,6 @@ 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;
@@ -166,8 +161,6 @@ public class ZenModeConfig implements Parcelable {
}
allowWhenScreenOff = source.readInt() == 1;
allowWhenScreenOn = source.readInt() == 1;
- allowAlarms = source.readInt() == 1;
- allowMediaSystemOther = source.readInt() == 1;
}
@Override
@@ -197,23 +190,19 @@ 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)
@@ -229,21 +218,9 @@ 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);
}
@@ -256,6 +233,12 @@ 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);
}
@@ -352,9 +335,7 @@ public class ZenModeConfig implements Parcelable {
if (!(o instanceof ZenModeConfig)) return false;
if (o == this) return true;
final ZenModeConfig other = (ZenModeConfig) o;
- return other.allowAlarms == allowAlarms
- && other.allowMediaSystemOther == allowMediaSystemOther
- && other.allowCalls == allowCalls
+ return other.allowCalls == allowCalls
&& other.allowRepeatCallers == allowRepeatCallers
&& other.allowMessages == allowMessages
&& other.allowCallsFrom == allowCallsFrom
@@ -370,10 +351,10 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(allowAlarms, allowMediaSystemOther, allowCalls,
- allowRepeatCallers, allowMessages,
- allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
- allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule);
+ return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
+ allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff,
+ allowWhenScreenOn,
+ user, automaticRules, manualRule);
}
private static String toDayList(int[] days) {
@@ -432,12 +413,10 @@ public class ZenModeConfig implements Parcelable {
}
if (type == XmlPullParser.START_TAG) {
if (ALLOW_TAG.equals(tag)) {
- rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
- DEFAULT_ALLOW_CALLS);
+ rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
DEFAULT_ALLOW_REPEAT_CALLERS);
- rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES,
- DEFAULT_ALLOW_MESSAGES);
+ rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
DEFAULT_ALLOW_REMINDERS);
rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
@@ -459,9 +438,6 @@ public class ZenModeConfig implements Parcelable {
safeBoolean(parser, ALLOW_ATT_SCREEN_OFF, DEFAULT_ALLOW_SCREEN_OFF);
rt.allowWhenScreenOn =
safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON);
- rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS);
- rt.allowMediaSystemOther = safeBoolean(parser, ALLOW_ATT_MEDIA,
- DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
} else if (AUTOMATIC_TAG.equals(tag)) {
@@ -492,8 +468,6 @@ public class ZenModeConfig implements Parcelable {
out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
out.attribute(null, ALLOW_ATT_SCREEN_OFF, Boolean.toString(allowWhenScreenOff));
out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowWhenScreenOn));
- out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
- out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowMediaSystemOther));
out.endTag(null, ALLOW_TAG);
if (manualRule != null) {
@@ -680,12 +654,6 @@ 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,
@@ -712,13 +680,10 @@ public class ZenModeConfig implements Parcelable {
public void applyNotificationPolicy(Policy policy) {
if (policy == null) return;
- 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;
+ allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
+ allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
!= 0;
allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
diff --git a/android/app/slice/Slice.java b/android/slice/Slice.java
index 7f9f74b4..57686548 100644
--- a/android/app/slice/Slice.java
+++ b/android/slice/Slice.java
@@ -14,35 +14,38 @@
* limitations under the License.
*/
-package android.app.slice;
+package android.slice;
+
+import static android.slice.SliceItem.TYPE_ACTION;
+import static android.slice.SliceItem.TYPE_COLOR;
+import static android.slice.SliceItem.TYPE_IMAGE;
+import static android.slice.SliceItem.TYPE_REMOTE_INPUT;
+import static android.slice.SliceItem.TYPE_REMOTE_VIEW;
+import static android.slice.SliceItem.TYPE_SLICE;
+import static android.slice.SliceItem.TYPE_TEXT;
+import static android.slice.SliceItem.TYPE_TIMESTAMP;
import android.annotation.NonNull;
-import android.annotation.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 {
@@ -50,7 +53,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_PARTIAL})
+ HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT})
public @interface SliceHint{ }
/**
@@ -99,12 +102,6 @@ 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.
@@ -112,12 +109,19 @@ 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;
- Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+ /**
+ * @hide
+ */
+ public Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
mHints = hints;
mItems = items.toArray(new SliceItem[items.size()]);
mUri = uri;
@@ -143,15 +147,15 @@ public final class Slice implements Parcelable {
/**
* @return All child {@link SliceItem}s that this Slice contains.
*/
- public List<SliceItem> getItems() {
- return Arrays.asList(mItems);
+ public SliceItem[] getItems() {
+ return mItems;
}
/**
* @return All hints associated with this Slice.
*/
- public @SliceHint List<String> getHints() {
- return Arrays.asList(mHints);
+ public @SliceHint String[] getHints() {
+ return mHints;
}
/**
@@ -159,14 +163,14 @@ public final class Slice implements Parcelable {
*/
public SliceItem getPrimaryIcon() {
for (SliceItem item : getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
+ if (item.getType() == TYPE_IMAGE) {
return item;
}
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ if (!(item.getType() == TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
&& !item.hasHint(Slice.HINT_ACTIONS)
&& !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+ && (item.getType() != TYPE_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, TYPE_IMAGE);
if (icon != null) return icon;
}
}
@@ -231,18 +235,10 @@ 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, SliceItem.TYPE_SLICE, slice.getHints().toArray(
- new String[slice.getHints().size()])));
+ mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints()));
return this;
}
@@ -250,7 +246,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, SliceItem.TYPE_ACTION, new String[0]));
+ mItems.add(new SliceItem(action, s, TYPE_ACTION, new String[0]));
return this;
}
@@ -258,53 +254,31 @@ 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, SliceItem.TYPE_TEXT, hints));
+ mItems.add(new SliceItem(text, 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, SliceItem.TYPE_IMAGE, hints));
+ mItems.add(new SliceItem(icon, 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, SliceItem.TYPE_REMOTE_VIEW, hints));
+ mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints));
return this;
}
/**
* Add remote input to the slice being constructed
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput,
- @SliceHint 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, SliceItem.TYPE_REMOTE_INPUT, hints));
+ mItems.add(new SliceItem(remoteInput, TYPE_REMOTE_INPUT, hints));
return this;
}
@@ -312,33 +286,19 @@ 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, SliceItem.TYPE_COLOR, hints));
+ mItems.add(new SliceItem(color, 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, SliceItem.TYPE_TIMESTAMP, hints));
+ mItems.add(new SliceItem(time, 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() {
@@ -362,18 +322,18 @@ public final class Slice implements Parcelable {
* @hide
* @return A string representation of this slice.
*/
- public String toString() {
- return toString("");
+ public String getString() {
+ return getString("");
}
- private String toString(String indent) {
+ private String getString(String indent) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mItems.length; i++) {
sb.append(indent);
- if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
+ if (mItems[i].getType() == TYPE_SLICE) {
sb.append("slice:\n");
- sb.append(mItems[i].getSlice().toString(indent + " "));
- } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
+ sb.append(mItems[i].getSlice().getString(indent + " "));
+ } else if (mItems[i].getType() == TYPE_TEXT) {
sb.append("text: ");
sb.append(mItems[i].getText());
sb.append("\n");
@@ -384,34 +344,4 @@ 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/app/slice/SliceItem.java b/android/slice/SliceItem.java
index 6e69b051..2827ab9d 100644
--- a/android/app/slice/SliceItem.java
+++ b/android/slice/SliceItem.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice;
+package android.slice;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -23,15 +23,13 @@ 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}.
@@ -49,6 +47,7 @@ import java.util.List;
* 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 {
@@ -98,15 +97,14 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- protected @Slice.SliceHint
- String[] mHints;
+ protected @SliceHint String[] mHints;
private final int mType;
private final Object mObj;
/**
* @hide
*/
- public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
+ public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) {
mHints = hints;
mType = type;
mObj = obj;
@@ -115,7 +113,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
+ public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) {
this(new Pair<>(intent, slice), type, hints);
}
@@ -123,14 +121,14 @@ public final class SliceItem implements Parcelable {
* Gets all hints associated with this SliceItem.
* @return Array of hints.
*/
- public @NonNull @Slice.SliceHint List<String> getHints() {
- return Arrays.asList(mHints);
+ public @NonNull @SliceHint String[] getHints() {
+ return mHints;
}
/**
* @hide
*/
- public void addHint(@Slice.SliceHint String hint) {
+ public void addHint(@SliceHint String hint) {
mHints = ArrayUtils.appendElement(String.class, mHints, hint);
}
@@ -208,7 +206,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(@Slice.SliceHint String hint) {
+ public boolean hasHint(@SliceHint String hint) {
return ArrayUtils.contains(mHints, hint);
}
@@ -236,7 +234,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public boolean hasHints(@Slice.SliceHint String[] hints) {
+ public boolean hasHints(@SliceHint String[] hints) {
if (hints == null) return true;
for (String hint : hints) {
if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
@@ -249,7 +247,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
+ public boolean hasAnyHints(@SliceHint String[] hints) {
if (hints == null) return false;
for (String hint : hints) {
if (ArrayUtils.contains(mHints, hint)) {
diff --git a/android/app/slice/SliceProvider.java b/android/slice/SliceProvider.java
index df87b455..4e21371b 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/slice/SliceProvider.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.app.slice;
+package android.slice;
import android.Manifest.permission;
import android.content.ContentProvider;
@@ -26,8 +26,6 @@ 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;
@@ -53,15 +51,10 @@ 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
@@ -80,18 +73,8 @@ 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
@@ -137,11 +120,11 @@ public abstract class SliceProvider extends ContentProvider {
@Override
public final String getType(Uri uri) {
if (DEBUG) Log.d(TAG, "getType " + uri);
- return SLICE_TYPE;
+ return null;
}
@Override
- public Bundle call(String method, String arg, Bundle extras) {
+ public final Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
getContext().enforceCallingPermission(permission.BIND_SLICE,
"Slice binding requires the permission BIND_SLICE");
@@ -160,17 +143,8 @@ public abstract class SliceProvider extends ContentProvider {
CountDownLatch latch = new CountDownLatch(1);
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {
- ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
- try {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyDeath()
- .build());
- output[0] = onBindSlice(sliceUri);
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
- latch.countDown();
- }
+ output[0] = onBindSlice(sliceUri);
+ latch.countDown();
});
try {
latch.await();
diff --git a/android/app/slice/SliceQuery.java b/android/slice/SliceQuery.java
index d1fe2c90..d99b26a5 100644
--- a/android/app/slice/SliceQuery.java
+++ b/android/slice/SliceQuery.java
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-package android.app.slice;
+package android.slice;
+import static android.slice.SliceItem.TYPE_ACTION;
+import static android.slice.SliceItem.TYPE_SLICE;
+
+import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -110,9 +114,7 @@ public class SliceQuery {
* @hide
*/
public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
- List<String> h = s.getHints();
- return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
- hints, nonHints);
+ return find(new SliceItem(s, TYPE_SLICE, s.getHints()), type, hints, nonHints);
}
/**
@@ -138,9 +140,8 @@ public class SliceQuery {
@Override
public SliceItem next() {
SliceItem item = items.poll();
- if (item.getType() == SliceItem.TYPE_SLICE
- || item.getType() == SliceItem.TYPE_ACTION) {
- items.addAll(item.getSlice().getItems());
+ if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) {
+ items.addAll(Arrays.asList(item.getSlice().getItems()));
}
return item;
}
diff --git a/android/app/slice/views/ActionRow.java b/android/slice/views/ActionRow.java
index c7d99f7f..93e9c035 100644
--- a/android/app/slice/views/ActionRow.java
+++ b/android/slice/views/ActionRow.java
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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/app/slice/views/GridView.java b/android/slice/views/GridView.java
index 6f30c507..18a90f7d 100644
--- a/android/app/slice/views/GridView.java
+++ b/android/slice/views/GridView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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.List;
+import java.util.Arrays;
/**
* @hide
@@ -76,10 +76,10 @@ public class GridView extends LinearLayout implements SliceListView {
removeAllViews();
int total = 1;
if (slice.getType() == SliceItem.TYPE_SLICE) {
- List<SliceItem> items = slice.getSlice().getItems();
- total = items.size();
+ SliceItem[] items = slice.getSlice().getItems();
+ total = items.length;
for (int i = 0; i < total; i++) {
- SliceItem item = items.get(i);
+ SliceItem item = items[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(item.getSlice().getItems());
+ items.addAll(Arrays.asList(item.getSlice().getItems()));
}
items.forEach(i -> {
Context context = getContext();
diff --git a/android/app/slice/views/LargeSliceAdapter.java b/android/slice/views/LargeSliceAdapter.java
index 6794ff98..e77a1b2a 100644
--- a/android/app/slice/views/LargeSliceAdapter.java
+++ b/android/slice/views/LargeSliceAdapter.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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/app/slice/views/LargeTemplateView.java b/android/slice/views/LargeTemplateView.java
index 9e225162..d53e8fcb 100644
--- a/android/app/slice/views/LargeTemplateView.java
+++ b/android/slice/views/LargeTemplateView.java
@@ -14,21 +14,22 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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;
/**
@@ -85,7 +86,7 @@ public class LargeTemplateView extends SliceModeView {
if (slice.hasHint(Slice.HINT_LIST)) {
addList(slice, items);
} else {
- slice.getItems().forEach(item -> {
+ Arrays.asList(slice.getItems()).forEach(item -> {
if (item.hasHint(Slice.HINT_ACTIONS)) {
return;
} else if (item.getType() == SliceItem.TYPE_COLOR) {
@@ -108,7 +109,7 @@ public class LargeTemplateView extends SliceModeView {
}
private void addList(Slice slice, List<SliceItem> items) {
- List<SliceItem> sliceItems = slice.getItems();
+ List<SliceItem> sliceItems = Arrays.asList(slice.getItems());
sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
items.addAll(sliceItems);
}
diff --git a/android/app/slice/views/MessageView.java b/android/slice/views/MessageView.java
index 77252bf2..7b03e0bd 100644
--- a/android/app/slice/views/MessageView.java
+++ b/android/slice/views/MessageView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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/app/slice/views/RemoteInputView.java b/android/slice/views/RemoteInputView.java
index e53cb1ea..a29bb5c0 100644
--- a/android/app/slice/views/RemoteInputView.java
+++ b/android/slice/views/RemoteInputView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.slice.views;
import android.animation.Animator;
import android.app.Notification;
diff --git a/android/app/slice/views/ShortcutView.java b/android/slice/views/ShortcutView.java
index b6790c7d..8fe2f1ac 100644
--- a/android/app/slice/views/ShortcutView.java
+++ b/android/slice/views/ShortcutView.java
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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/app/slice/views/SliceView.java b/android/slice/views/SliceView.java
index 32484fca..f3792481 100644
--- a/android/app/slice/views/SliceView.java
+++ b/android/slice/views/SliceView.java
@@ -14,25 +14,23 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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.
*
@@ -122,7 +120,7 @@ public class SliceView extends LinearLayout {
*/
public void bindSlice(Uri sliceUri) {
validate(sliceUri);
- Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
+ Slice s = mContext.getContentResolver().bindSlice(sliceUri);
bindSlice(s);
}
@@ -203,7 +201,7 @@ public class SliceView extends LinearLayout {
}
// TODO: Smarter mapping here from one state to the next.
SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- List<SliceItem> items = mCurrentSlice.getItems();
+ SliceItem[] items = mCurrentSlice.getItems();
SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
Slice.HINT_ACTIONS,
Slice.HINT_ALT);
@@ -214,7 +212,7 @@ public class SliceView extends LinearLayout {
addView(mCurrentView);
addView(mActions);
}
- if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
+ if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) {
mCurrentView.setVisibility(View.VISIBLE);
mCurrentView.setSlice(mCurrentSlice);
} else {
@@ -241,7 +239,7 @@ public class SliceView extends LinearLayout {
}
private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
+ if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) {
throw new RuntimeException("Invalid uri " + sliceUri);
}
if (sliceUri.getPathSegments().size() == 0) {
diff --git a/android/app/slice/views/SliceViewUtil.java b/android/slice/views/SliceViewUtil.java
index 19e8e7c9..1b5a6d1e 100644
--- a/android/app/slice/views/SliceViewUtil.java
+++ b/android/slice/views/SliceViewUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.slice.views;
import android.annotation.ColorInt;
import android.content.Context;
diff --git a/android/app/slice/views/SmallTemplateView.java b/android/slice/views/SmallTemplateView.java
index 42b2d213..b0b181ed 100644
--- a/android/app/slice/views/SmallTemplateView.java
+++ b/android/slice/views/SmallTemplateView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.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,6 +34,7 @@ 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;
@@ -116,7 +117,7 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
int itemCount = 0;
boolean hasSummary = false;
ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
- slice.getSlice().getItems());
+ Arrays.asList(slice.getSlice().getItems()));
for (int i = 0; i < sliceItems.size(); i++) {
SliceItem item = sliceItems.get(i);
if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
@@ -139,9 +140,9 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
mEndContainer.addView(tv);
itemCount++;
} else if (item.getType() == SliceItem.TYPE_SLICE) {
- List<SliceItem> subItems = item.getSlice().getItems();
- for (int j = 0; j < subItems.size(); j++) {
- sliceItems.add(subItems.get(j));
+ SliceItem[] subItems = item.getSlice().getItems();
+ for (int j = 0; j < subItems.length; j++) {
+ sliceItems.add(subItems[j]);
}
}
}
@@ -150,8 +151,7 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
@Override
public void setSlice(Slice slice) {
- setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
- slice.getHints().toArray(new String[slice.getHints().size()])));
+ setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints()));
}
/**
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index 72f7fb18..a046d95e 100644
--- a/android/support/LibraryVersions.java
+++ b/android/support/LibraryVersions.java
@@ -45,17 +45,15 @@ public class LibraryVersions {
*/
public static final Version PAGING = new Version("1.0.0-alpha3");
- private static final Version LIFECYCLES = new Version("1.0.2");
-
/**
* Version code for Lifecycle libs that are required by the support library
*/
- public static final Version LIFECYCLES_CORE = LIFECYCLES;
+ public static final Version LIFECYCLES_CORE = new Version("1.0.2");
/**
* Version code for Lifecycle runtime libs that are required by the support library
*/
- public static final Version LIFECYCLES_RUNTIME = LIFECYCLES;
+ public static final Version LIFECYCLES_RUNTIME = new Version("1.0.0");
/**
* Version code for shared code of flatfoot
diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java
deleted file mode 100644
index 7100218a..00000000
--- a/android/support/car/drawer/CarDrawerActivity.java
+++ /dev/null
@@ -1,152 +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.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
deleted file mode 100644
index b0fd965d..00000000
--- a/android/support/car/drawer/CarDrawerAdapter.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.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
deleted file mode 100644
index 4d9f4e99..00000000
--- a/android/support/car/drawer/CarDrawerController.java
+++ /dev/null
@@ -1,306 +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.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/support/car/drawer/DrawerItemViewHolder.java b/android/support/car/drawer/DrawerItemViewHolder.java
deleted file mode 100644
index d016b2de..00000000
--- a/android/support/car/drawer/DrawerItemViewHolder.java
+++ /dev/null
@@ -1,87 +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.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 46527001..8527c659 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -27,6 +27,7 @@ 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;
@@ -60,6 +61,7 @@ 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;
@@ -96,11 +98,6 @@ 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.
*/
@@ -142,6 +139,7 @@ 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);
@@ -158,16 +156,6 @@ 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);
@@ -211,20 +199,47 @@ public class PagedListView extends FrameLayout {
}
}
});
-
mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
- // Modify the layout the Scroll Bar is not visible.
- if (!mScrollBarEnabled) {
- MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
- params.setMarginStart(0);
- mRecyclerView.setLayoutParams(params);
+ // Modify the layout if the Gutter or the Scroll Bar are not visible.
+ mRightGutterEnabled = a.getBoolean(R.styleable.PagedListView_rightGutterEnabled, false);
+ if (mRightGutterEnabled || !mScrollBarEnabled) {
+ FrameLayout.LayoutParams maxWidthLayoutLayoutParams =
+ (FrameLayout.LayoutParams) maxWidthLayout.getLayoutParams();
+ if (mRightGutterEnabled) {
+ maxWidthLayoutLayoutParams.rightMargin =
+ getResources().getDimensionPixelSize(R.dimen.car_card_margin);
+ }
+ if (!mScrollBarEnabled) {
+ maxWidthLayoutLayoutParams.setMarginStart(0);
+ }
+ maxWidthLayout.setLayoutParams(maxWidthLayoutLayoutParams);
}
setDayNightStyle(DayNightStyle.AUTO);
a.recycle();
}
+ /**
+ * Sets the starting and ending padding for each view in the list.
+ *
+ * @param start The start padding.
+ * @param end The end padding.
+ */
+ public void setListViewStartEndPadding(@Px int start, @Px int end) {
+ int carCardMargin = getResources().getDimensionPixelSize(R.dimen.car_card_margin);
+ int startGutter = mScrollBarEnabled ? carCardMargin : 0;
+ int startPadding = Math.max(start - startGutter, 0);
+ int endGutter = mRightGutterEnabled ? carCardMargin : 0;
+ int endPadding = Math.max(end - endGutter, 0);
+ mRecyclerView.setPaddingRelative(startPadding, mRecyclerView.getPaddingTop(),
+ endPadding, mRecyclerView.getPaddingBottom());
+
+ // Since we're setting padding we'll need to set the clip to padding to the same
+ // value as clip children to ensure that the cards fly off the screen.
+ mRecyclerView.setClipToPadding(mRecyclerView.getClipChildren());
+ }
+
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
diff --git a/android/support/media/tv/BasePreviewProgram.java b/android/support/media/tv/BasePreviewProgram.java
index 39c30140..1423d9d6 100644
--- a/android/support/media/tv/BasePreviewProgram.java
+++ b/android/support/media/tv/BasePreviewProgram.java
@@ -23,13 +23,14 @@ 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;
@@ -54,89 +55,6 @@ 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);
}
@@ -209,7 +127,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @Type int getType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_TYPE);
- return i == null ? TYPE_UNKNOWN : i;
+ return i == null ? INVALID_INT_VALUE : i;
}
/**
@@ -219,7 +137,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @AspectRatio int getPosterArtAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
- return i == null ? ASPECT_RATIO_UNKNOWN : i;
+ return i == null ? INVALID_INT_VALUE : i;
}
/**
@@ -229,7 +147,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @AspectRatio int getThumbnailAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
- return i == null ? ASPECT_RATIO_UNKNOWN : i;
+ return i == null ? INVALID_INT_VALUE : i;
}
/**
@@ -247,7 +165,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @Availability int getAvailability() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_AVAILABILITY);
- return i == null ? AVAILABILITY_UNKNOWN : i;
+ return i == null ? INVALID_INT_VALUE : i;
}
/**
@@ -298,7 +216,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @InteractionType int getInteractionType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_INTERACTION_TYPE);
- return i == null ? INTERACTION_TYPE_UNKNOWN : i;
+ return i == null ? INVALID_INT_VALUE : i;
}
/**
diff --git a/android/support/media/tv/BaseProgram.java b/android/support/media/tv/BaseProgram.java
index 23b5cf9c..e4ce9d1f 100644
--- a/android/support/media/tv/BaseProgram.java
+++ b/android/support/media/tv/BaseProgram.java
@@ -22,16 +22,13 @@ 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}.
@@ -49,22 +46,6 @@ 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;
@@ -273,7 +254,7 @@ public abstract class BaseProgram {
*/
public @ReviewRatingStyle int getReviewRatingStyle() {
Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE);
- return i == null ? REVIEW_RATING_STYLE_UNKNOWN : i;
+ return i == null ? INVALID_INT_VALUE : i;
}
/**
diff --git a/android/support/media/tv/Program.java b/android/support/media/tv/Program.java
index 233f1bab..4e3bd7ac 100644
--- a/android/support/media/tv/Program.java
+++ b/android/support/media/tv/Program.java
@@ -25,7 +25,6 @@ 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
@@ -283,7 +282,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(@Genre String[] genres) {
+ public Builder setBroadcastGenres(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 de4fd04f..5a46e791 100644
--- a/android/support/media/tv/TvContractCompat.java
+++ b/android/support/media/tv/TvContractCompat.java
@@ -30,6 +30,7 @@ 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;
@@ -605,6 +606,16 @@ 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.
*
@@ -923,6 +934,27 @@ 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.
*
@@ -1014,6 +1046,19 @@ 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.
*
@@ -1062,6 +1107,18 @@ 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".
*
@@ -1098,6 +1155,20 @@ 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".
*
@@ -2824,6 +2895,17 @@ 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 c192745c..f4665846 100644
--- a/android/support/media/tv/WatchNextProgram.java
+++ b/android/support/media/tv/WatchNextProgram.java
@@ -22,15 +22,12 @@ 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 java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import android.support.media.tv.TvContractCompat.WatchNextPrograms.WatchNextType;
/**
* A convenience class to access {@link WatchNextPrograms} entries in the system content
@@ -90,34 +87,16 @@ 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,
- * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown.
+ * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program.
*/
public @WatchNextType int getWatchNextType() {
Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
- return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i;
+ return i == null ? INVALID_INT_VALUE : i;
}
/**
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
index 57db43e7..bc35935e 100644
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ b/android/support/mediacompat/testlib/IntentConstants.java
@@ -22,8 +22,6 @@ package android.support.mediacompat.testlib;
public class IntentConstants {
public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
"android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
- public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
- "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
public static final String KEY_METHOD_ID = "method_id";
public static final String KEY_ARGUMENT = "argument";
}
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
deleted file mode 100644
index 82b5c59a..00000000
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ /dev/null
@@ -1,57 +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.mediacompat.testlib;
-
-/**
- * Constants for testing the media session and controller.
- */
-public class MediaSessionConstants {
-
- // MediaSessionCompat methods.
- public static final int SET_EXTRAS = 101;
- public static final int SET_FLAGS = 102;
- public static final int SET_METADATA = 103;
- public static final int SET_PLAYBACK_STATE = 104;
- public static final int SET_QUEUE = 105;
- public static final int SET_QUEUE_TITLE = 106;
- public static final int SET_SESSION_ACTIVITY = 107;
- public static final int SET_CAPTIONING_ENABLED = 108;
- public static final int SET_REPEAT_MODE = 109;
- public static final int SET_SHUFFLE_MODE = 110;
- public static final int SEND_SESSION_EVENT = 112;
- public static final int SET_ACTIVE = 113;
- public static final int RELEASE = 114;
- public static final int SET_PLAYBACK_TO_LOCAL = 115;
- public static final int SET_PLAYBACK_TO_REMOTE = 116;
- public static final int SET_RATING_TYPE = 117;
-
- public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
- public static final String TEST_KEY = "test-key";
- public static final String TEST_VALUE = "test-val";
- public static final String TEST_SESSION_EVENT = "test-session-event";
- public static final int TEST_FLAGS = 5;
- public static final int TEST_CURRENT_VOLUME = 10;
- public static final int TEST_MAX_VOLUME = 11;
- public static final long TEST_QUEUE_ID_1 = 10L;
- public static final long TEST_QUEUE_ID_2 = 20L;
- public static final String TEST_MEDIA_ID_1 = "media_id_1";
- public static final String TEST_MEDIA_ID_2 = "media_id_2";
- public static final long TEST_ACTION = 55L;
-
- public static final int TEST_ERROR_CODE = 0x3;
- public static final String TEST_ERROR_MSG = "test-error-msg";
-}
diff --git a/android/support/transition/AutoTransition.java b/android/support/transition/AutoTransition.java
index bf39c3c3..02b49e26 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 af37f77a..81431972 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2240,24 +2240,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary);
appendVisibleItems();
prependVisibleItems();
- // 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)
+ removeInvisibleViewsAtFront();
+ removeInvisibleViewsAtEnd();
} 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 15b8ce9a..4c70ce93 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, @Nullable FontCallback fontCallback) throws NotFoundException {
+ int style) throws NotFoundException {
if (context.isRestricted()) {
return null;
}
- return loadFont(context, id, value, style, fontCallback, null /* handler */,
+ return loadFont(context, id, value, style, null /* callback */, null /* handler */,
true /* isXmlRequest */);
}
diff --git a/android/support/v4/media/RatingCompat.java b/android/support/v4/media/RatingCompat.java
index e70243f8..b538cac4 100644
--- a/android/support/v4/media/RatingCompat.java
+++ b/android/support/v4/media/RatingCompat.java
@@ -18,7 +18,6 @@ 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;
@@ -327,25 +326,25 @@ public final class RatingCompat implements Parcelable {
*/
public static RatingCompat fromRating(Object ratingObj) {
if (ratingObj != null && Build.VERSION.SDK_INT >= 19) {
- final int ratingStyle = ((Rating) ratingObj).getRatingStyle();
+ final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj);
final RatingCompat rating;
- if (((Rating) ratingObj).isRated()) {
+ if (RatingCompatKitkat.isRated(ratingObj)) {
switch (ratingStyle) {
case RATING_HEART:
- rating = newHeartRating(((Rating) ratingObj).hasHeart());
+ rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj));
break;
case RATING_THUMB_UP_DOWN:
- rating = newThumbRating(((Rating) ratingObj).isThumbUp());
+ rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj));
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
rating = newStarRating(ratingStyle,
- ((Rating) ratingObj).getStarRating());
+ RatingCompatKitkat.getStarRating(ratingObj));
break;
case RATING_PERCENTAGE:
rating = newPercentageRating(
- ((Rating) ratingObj).getPercentRating());
+ RatingCompatKitkat.getPercentRating(ratingObj));
break;
default:
return null;
@@ -373,25 +372,25 @@ public final class RatingCompat implements Parcelable {
if (isRated()) {
switch (mRatingStyle) {
case RATING_HEART:
- mRatingObj = Rating.newHeartRating(hasHeart());
+ mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- mRatingObj = Rating.newThumbRating(isThumbUp());
+ mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
- mRatingObj = Rating.newStarRating(mRatingStyle,
+ mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle,
getStarRating());
break;
case RATING_PERCENTAGE:
- mRatingObj = Rating.newPercentageRating(getPercentRating());
+ mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating());
break;
default:
return null;
}
} else {
- mRatingObj = Rating.newUnratedRating(mRatingStyle);
+ mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle);
}
}
return mRatingObj;
diff --git a/android/support/v4/media/RatingCompatKitkat.java b/android/support/v4/media/RatingCompatKitkat.java
new file mode 100644
index 00000000..1d3fa505
--- /dev/null
+++ b/android/support/v4/media/RatingCompatKitkat.java
@@ -0,0 +1,67 @@
+/*
+ * 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 09261869..9ef1b0b0 100644
--- a/android/support/v4/provider/FontsContractCompat.java
+++ b/android/support/v4/provider/FontsContractCompat.java
@@ -303,9 +303,6 @@ 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 fdbcf9ad..d3f7020b 100644
--- a/android/support/v7/app/MediaRouteButton.java
+++ b/android/support/v7/app/MediaRouteButton.java
@@ -121,7 +121,8 @@ public class MediaRouteButton extends View {
}
public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
+ super(MediaRouterThemeHelper.createThemedContext(context, defStyleAttr), 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 17364efb..0ab2eb11 100644
--- a/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/android/support/v7/app/MediaRouteChooserDialog.java
@@ -92,8 +92,10 @@ public class MediaRouteChooserDialog extends AppCompatDialog {
}
public MediaRouteChooserDialog(Context context, int theme) {
- super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
- MediaRouterThemeHelper.createThemedDialogStyle(context));
+ // 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);
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/android/support/v7/app/MediaRouteControllerDialog.java b/android/support/v7/app/MediaRouteControllerDialog.java
index d89bf21e..4b9a17a3 100644
--- a/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/android/support/v7/app/MediaRouteControllerDialog.java
@@ -201,8 +201,12 @@ public class MediaRouteControllerDialog extends AlertDialog {
}
public MediaRouteControllerDialog(Context context, int theme) {
- super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
- MediaRouterThemeHelper.createThemedDialogStyle(context));
+ // 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);
mContext = getContext();
mControllerCallback = new MediaControllerCallback();
diff --git a/android/support/v7/app/MediaRouterThemeHelper.java b/android/support/v7/app/MediaRouterThemeHelper.java
index 69e40ac7..9ef218e0 100644
--- a/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/android/support/v7/app/MediaRouterThemeHelper.java
@@ -42,76 +42,47 @@ final class MediaRouterThemeHelper {
private MediaRouterThemeHelper() {
}
- 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));
+ /**
+ * 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}.
*
- * 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
+ * @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.
*/
- 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;
+ 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);
}
- // 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);
- }
- return theme;
+ /**
+ * 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);
}
- // END. Previous two methods should be used in conjunction.
- static int getThemeResource(Context context, int attr) {
+ public static int getThemeResource(Context context, int attr) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
}
- static float getDisabledAlpha(Context context) {
+ public static float getDisabledAlpha(Context context) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
? value.getFloat() : 0.5f;
}
- static @ControllerColorType int getControllerColor(Context context, int style) {
+ public 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)
@@ -121,7 +92,7 @@ final class MediaRouterThemeHelper {
return COLOR_DARK_ON_LIGHT_BACKGROUND;
}
- static int getButtonTextColor(Context context) {
+ public 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);
@@ -133,7 +104,7 @@ final class MediaRouterThemeHelper {
return primaryColor;
}
- static void setMediaControlsBackgroundColor(
+ public static void setMediaControlsBackgroundColor(
Context context, View mainControls, View groupControls, boolean hasGroup) {
int primaryColor = getThemeColor(context, 0,
android.support.v7.appcompat.R.attr.colorPrimary);
@@ -153,7 +124,7 @@ final class MediaRouterThemeHelper {
groupControls.setTag(primaryDarkColor);
}
- static void setVolumeSliderColor(
+ public static void setVolumeSliderColor(
Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
int controllerColor = getControllerColor(context, 0);
if (Color.alpha(controllerColor) != 0xFF) {
@@ -165,10 +136,23 @@ 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) {
@@ -189,16 +173,16 @@ final class MediaRouterThemeHelper {
return value.data;
}
- private static int getRouterThemeId(Context context) {
+ private static int getStyledRouterThemeId(Context context, int style) {
int themeId;
if (isLightTheme(context)) {
- if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_Light;
} else {
themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
}
} else {
- if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_LightControlPanel;
} else {
themeId = R.style.Theme_MediaRouter;
diff --git a/android/support/v7/util/DiffUtil.java b/android/support/v7/util/DiffUtil.java
index ebc33f31..6302666f 100644
--- a/android/support/v7/util/DiffUtil.java
+++ b/android/support/v7/util/DiffUtil.java
@@ -16,7 +16,6 @@
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;
@@ -349,72 +348,6 @@ 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 fa6196f5..51510aa2 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -29,7 +29,6 @@ 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;
@@ -37,8 +36,6 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
-import java.lang.ref.WeakReference;
-
@RequiresApi(9)
class AppCompatTextHelper {
@@ -66,7 +63,6 @@ class AppCompatTextHelper {
private int mStyle = Typeface.NORMAL;
private Typeface mFontTypeface;
- private boolean mAsyncFontPending;
AppCompatTextHelper(TextView view) {
mView = view;
@@ -217,23 +213,8 @@ 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 {
- // 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;
+ mFontTypeface = a.getFont(fontFamilyId, mStyle);
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
// Expected if it is not a font resource.
}
@@ -241,16 +222,12 @@ class AppCompatTextHelper {
if (mFontTypeface == null) {
// Try with String. This is done by TextView JB+, but fails in ICS
String fontFamilyName = a.getString(fontFamilyId);
- if (fontFamilyName != null) {
- mFontTypeface = Typeface.create(fontFamilyName, mStyle);
- }
+ 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:
@@ -268,16 +245,6 @@ class AppCompatTextHelper {
}
}
- private void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, Typeface typeface) {
- if (mAsyncFontPending) {
- mFontTypeface = typeface;
- final TextView textView = textViewWeak.get();
- if (textView != null) {
- textView.setTypeface(typeface, mStyle);
- }
- }
- }
-
void onSetTextAppearance(Context context, int resId) {
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
resId, R.styleable.TextAppearance);
diff --git a/android/support/v7/widget/TintTypedArray.java b/android/support/v7/widget/TintTypedArray.java
index 384c4615..22709551 100644
--- a/android/support/v7/widget/TintTypedArray.java
+++ b/android/support/v7/widget/TintTypedArray.java
@@ -106,8 +106,7 @@ public class TintTypedArray {
* not a font resource.
*/
@Nullable
- public Typeface getFont(@StyleableRes int index, int style,
- @Nullable ResourcesCompat.FontCallback fontCallback) {
+ public Typeface getFont(@StyleableRes int index, int style) {
final int resourceId = mWrapped.getResourceId(index, 0);
if (resourceId == 0) {
return null;
@@ -115,7 +114,7 @@ public class TintTypedArray {
if (mTypedValue == null) {
mTypedValue = new TypedValue();
}
- return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, fontCallback);
+ return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
}
public int length() {
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index de980b2f..689ce954 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -763,18 +763,6 @@ 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}.
*/
@@ -1585,25 +1573,6 @@ 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;
@@ -1734,7 +1703,6 @@ 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);
@@ -1758,7 +1726,6 @@ 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);
@@ -1873,7 +1840,6 @@ 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 9a9877a8..764b7b22 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -77,9 +77,8 @@ 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},
- * {@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}.
+ * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
+ * {@link #RESULT_IO_ERROR}.
*
* This extra may also be used by the middleware when it is sending intents to the app.
*/
@@ -143,41 +142,11 @@ 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 is likely a transient error and another {@link DownloadRequest} should be sent to try
- * the download again.
+ * 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).
*/
public static final int RESULT_IO_ERROR = 4;
-
- /**
- * 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;
+ // TODO - more results!
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/android/telephony/NetworkScanRequest.java b/android/telephony/NetworkScanRequest.java
index 9674c930..d2aef200 100644
--- a/android/telephony/NetworkScanRequest.java
+++ b/android/telephony/NetworkScanRequest.java
@@ -19,7 +19,6 @@ package android.telephony;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -39,20 +38,6 @@ 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;
@@ -61,84 +46,24 @@ 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,
- int searchPeriodicity,
- int maxSearchTime,
- boolean incrementalResults,
- int incrementalResultsPeriodicity,
- ArrayList<String> mccMncs) {
+ public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) {
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
@@ -150,11 +75,6 @@ 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) {
@@ -162,12 +82,6 @@ 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
@@ -185,24 +99,13 @@ public final class NetworkScanRequest implements Parcelable {
}
return (scanType == nsr.scanType
- && Arrays.equals(specifiers, nsr.specifiers)
- && searchPeriodicity == nsr.searchPeriodicity
- && maxSearchTime == nsr.maxSearchTime
- && incrementalResults == nsr.incrementalResults
- && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
- && (((mccMncs != null)
- && mccMncs.equals(nsr.mccMncs))));
+ && Arrays.equals(specifiers, nsr.specifiers));
}
@Override
public int hashCode () {
return ((scanType * 31)
- + (Arrays.hashCode(specifiers)) * 37
- + (searchPeriodicity * 41)
- + (maxSearchTime * 43)
- + ((incrementalResults == true? 1 : 0) * 47)
- + (incrementalResultsPeriodicity * 53)
- + (mccMncs.hashCode() * 59));
+ + (Arrays.hashCode(specifiers)) * 37);
}
public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/android/telephony/ServiceState.java b/android/telephony/ServiceState.java
index 116e711e..e448fb2a 100644
--- a/android/telephony/ServiceState.java
+++ b/android/telephony/ServiceState.java
@@ -1197,6 +1197,15 @@ public class ServiceState implements Parcelable {
}
}
+ /**
+ * @Deprecated to be removed Q3 2013 use {@link #getVoiceNetworkType}
+ * @hide
+ */
+ public int getNetworkType() {
+ Rlog.e(LOG_TAG, "ServiceState.getNetworkType() DEPRECATED will be removed *******");
+ return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology);
+ }
+
/** @hide */
public int getDataNetworkType() {
return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology);
diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java
index f0d60b68..5a57f322 100644
--- a/android/telephony/mbms/DownloadRequest.java
+++ b/android/telephony/mbms/DownloadRequest.java
@@ -16,7 +16,6 @@
package android.telephony.mbms;
-import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Intent;
import android.net.Uri;
@@ -27,6 +26,7 @@ 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,19 +71,6 @@ 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
@@ -105,6 +92,15 @@ 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
*/
@@ -320,11 +316,9 @@ public final class DownloadRequest implements Parcelable {
throw new RuntimeException("Could not get sha256 hash object");
}
if (version >= 1) {
- // Hash the source URI and the app intent
+ // Hash the source URI, destination URI, and the app intent
digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
- if (serializedResultIntentForApp != null) {
- digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
- }
+ 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 9af1eb9e..fe275372 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.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST);
+ List<Uri> tempFiles = intent.getParcelableExtra(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.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST);
+ List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -492,14 +492,9 @@ 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("App must declare the file provider authority as metadata " +
- "in the manifest.");
+ throw new RuntimeException("Must declare the file provider authority as meta data");
}
return authority;
}
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index c3b2c482..2f85a1df 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -113,10 +113,6 @@ 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
@@ -244,13 +240,6 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
public final int registerStateCallback(final DownloadRequest downloadRequest,
final IDownloadStateCallback callback, int flags) throws RemoteException {
final int uid = Binder.getCallingUid();
- if (downloadRequest == null) {
- throw new NullPointerException("Download request must not be null");
- }
- if (callback == null) {
- throw new NullPointerException("Callback must not be null");
- }
-
DeathRecipient deathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
@@ -303,13 +292,6 @@ 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 65b726df..f8f370a5 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -65,10 +65,6 @@ 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
@@ -156,10 +152,6 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public int startStreaming(final int subscriptionId, String serviceId,
final IStreamingServiceCallback callback) throws RemoteException {
- if (callback == null) {
- throw new NullPointerException("Callback must not be null");
- }
-
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
diff --git a/android/text/BoringLayoutCreateDrawPerfTest.java b/android/text/BoringLayoutCreateDrawPerfTest.java
index 586c3852..47dd257b 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 9d11f295..34de65de 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 fba358cf..24260c4f 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<>(3);
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
}
/**
@@ -440,7 +440,7 @@ public class DynamicLayout extends Layout
mEllipsizeAt = null;
}
- mObjects = new PackedObjectVector<>(1);
+ mObjects = new PackedObjectVector<Directions>(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<>(layout);
+ mLayout = new WeakReference<DynamicLayout>(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 4f1488e1..ad26f23a 100644
--- a/android/text/Hyphenator.java
+++ b/android/text/Hyphenator.java
@@ -16,15 +16,258 @@
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 just initializes the native implementation of automatic hyphenation,
+ * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
* in essence finding valid hyphenation opportunities in a word.
*
* @hide
*/
public class Hyphenator {
- public static void init() {
- nInit();
+ 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;
}
- private static native void nInit();
+
+ 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);
}
diff --git a/android/text/Layout.java b/android/text/Layout.java
index ac5c2e92..60fff738 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -319,6 +319,8 @@ public abstract class Layout {
private float getJustifyWidth(int lineNum) {
Alignment paraAlign = mAlignment;
+ TabStops tabStops = null;
+ boolean tabStopsIsInitialized = false;
int left = 0;
int right = mWidth;
@@ -369,6 +371,10 @@ 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;
@@ -1417,6 +1423,7 @@ public abstract class Layout {
float dist = Math.abs(getHorizontal(max, primary) - horiz);
if (dist <= bestdist) {
+ bestdist = dist;
best = max;
}
@@ -1563,7 +1570,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);
- TextLine.recycle(tl);
+ tl = TextLine.recycle(tl);
return caret;
}
@@ -1887,7 +1894,10 @@ public abstract class Layout {
int margin = 0;
- boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n';
+ boolean isFirstParaLine = lineStart == 0 ||
+ spanned.charAt(lineStart - 1) == '\n';
+
+ boolean useFirstLineMargin = isFirstParaLine;
for (int i = 0; i < spans.length; i++) {
if (spans[i] instanceof LeadingMarginSpan2) {
int spStart = spanned.getSpanStart(spans[i]);
diff --git a/android/text/PaintMeasureDrawPerfTest.java b/android/text/PaintMeasureDrawPerfTest.java
index 67687985..00b60add 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 5c60188d..961cd8ee 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -21,18 +21,21 @@ 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
@@ -98,6 +101,7 @@ public class StaticLayout extends Layout {
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+ b.mLocales = null;
b.mMeasuredText = MeasuredText.obtain();
return b;
@@ -114,6 +118,7 @@ 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);
@@ -404,6 +409,17 @@ 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.
@@ -417,17 +433,35 @@ 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) {
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
+ Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
+ locHyph.second);
}
/* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
- nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
+ Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
+ nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second);
}
/**
@@ -485,7 +519,9 @@ public class StaticLayout extends Layout {
// This will go away and be subsumed by native builder code
private MeasuredText mMeasuredText;
- private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
+ private LocaleList mLocales;
+
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
}
public StaticLayout(CharSequence source, TextPaint paint,
@@ -774,6 +810,9 @@ 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,
@@ -827,9 +866,10 @@ 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, widths);
+ lineBreaks.breaks.length);
final int[] breaks = lineBreaks.breaks;
final float[] lineWidths = lineBreaks.widths;
@@ -907,10 +947,10 @@ public class StaticLayout extends Layout {
boolean moreChars = (endPos < bufEnd);
final int ascent = fallbackLineSpacing
- ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+ ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex]))
: fmAscent;
final int descent = fallbackLineSpacing
- ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+ ? Math.max(fmDescent, (int) Math.round(descents[breakIndex]))
: fmDescent;
v = out(source, here, endPos,
ascent, descent, fmTop, fmBottom,
@@ -1137,7 +1177,7 @@ public class StaticLayout extends Layout {
mWorkPaint.set(paint);
do {
final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
- widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
+ widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir);
if (ellipsizedWidth <= avail) {
lineFits = true;
} else {
@@ -1167,7 +1207,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,
+ int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
TextPaint paint, boolean forceEllipsis, int dir) {
final int savedHyphenEdit = paint.getHyphenEdit();
paint.setHyphenEdit(0);
@@ -1501,28 +1541,26 @@ 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);
+ @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
+ @Nullable String languageTags, @Nullable long[] hyphenators);
- // TODO: Make this method CriticalNative once native code defers doing layouts.
- private static native void nAddReplacementRun(
- /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
+ private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
@IntRange(from = 0) int start, @IntRange(from = 0) int end,
- @FloatRange(from = 0.0f) float width);
+ @FloatRange(from = 0.0f) float width, @Nullable String languageTags,
+ @Nullable long[] hyphenators);
+
+ private static native void nGetWidths(long nativePtr, float[] widths);
// 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[] charWidths);
+ float[] recycleDescents, int[] recycleFlags, int recycleLength);
private int mLineCount;
private int mTopPadding, mBottomPadding;
diff --git a/android/text/StaticLayoutCreateDrawPerfTest.java b/android/text/StaticLayoutCreateDrawPerfTest.java
index bfdb7589..356e2e0d 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 def3c91c..63337f08 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -13,6 +13,7 @@ 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;
@@ -71,11 +72,13 @@ public class StaticLayout_Delegate {
@LayoutlibDelegate
/*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start,
- int end, boolean isRtl) {
+ int end, boolean isRtl, String languageTags, long[] hyphenators) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
return;
}
+ builder.mLocales = languageTags;
+ builder.mNativeHyphenators = hyphenators;
int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
@@ -83,20 +86,30 @@ public class StaticLayout_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nAddReplacementRun(long nativeBuilder, long nativePaint, int start,
- int end, float width) {
+ /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width,
+ String languageTags, long[] hyphenators) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
return;
}
+ builder.mLocales = languageTags;
+ builder.mNativeHyphenators = hyphenators;
builder.mWidths[start] = width;
Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
}
@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[] charWidths) {
+ float[] recycleDescents, int[] recycleFlags, int recycleLength) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
@@ -105,7 +118,7 @@ public class StaticLayout_Delegate {
// compute all possible breakpoints.
int length = builder.mWidths.length;
- BreakIterator it = BreakIterator.getLineInstance();
+ BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales));
it.setText(new Segment(builder.mText, 0, length));
// average word length in english is 5. So, initialize the possible breaks with a guess.
@@ -136,7 +149,6 @@ public class StaticLayout_Delegate {
builder.mTabStopCalculator);
}
builder.mLineBreaker.computeBreaks(recycle);
- System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
return recycle.breaks.length;
}
@@ -194,9 +206,11 @@ 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 20c0ed87..2dbff100 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(wp.getUnderlineThickness(), 1.0f);
+ Math.max(((Paint) wp).getUnderlineThickness(), 1.0f);
drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness,
decorationXLeft, decorationXRight, y);
}
if (info.isStrikeThruText) {
final float thickness =
- Math.max(wp.getStrikeThruThickness(), 1.0f);
+ Math.max(((Paint) 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 ff2d57ed..a2bf33e1 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 {@link TextView} measure/draw.
+ * Performance test for multi line, single style {@link StaticLayout} creation/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 b94e48b3..02998653 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,12 +16,45 @@
package android.util;
+import android.os.DeadSystemException;
+
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.LineBreakBufferedWriter;
+
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.io.Writer;
import java.net.UnknownHostException;
/**
- * Mock Log implementation for testing on non android host.
+ * API for sending log output.
+ *
+ * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
+ * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
+ * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
+ *
+ * <p>The order in terms of verbosity, from least to most is
+ * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled
+ * into an application except during development. Debug logs are compiled
+ * in but stripped at runtime. Error, warning and info logs are always kept.
+ *
+ * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
+ * in your class:
+ *
+ * <pre>private static final String TAG = "MyActivity";</pre>
+ *
+ * and use that in subsequent calls to the log methods.
+ * </p>
+ *
+ * <p><b>Tip:</b> Don't forget that when you make a call like
+ * <pre>Log.v(TAG, "index=" + i);</pre>
+ * that when you're building the string to pass into Log.d, the compiler uses a
+ * StringBuilder and at least three allocations occur: the StringBuilder
+ * itself, the buffer, and the String object. Realistically, there is also
+ * another buffer allocation and copy, and even more pressure on the gc.
+ * That means that if your log message is filtered out, you might be doing
+ * significant work and incurring significant overhead.
*/
public final class Log {
@@ -55,6 +88,29 @@ public final class Log {
*/
public static final int ASSERT = 7;
+ /**
+ * Exception class used to capture a stack trace in {@link #wtf}.
+ * @hide
+ */
+ public static class TerribleFailure extends Exception {
+ TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
+ }
+
+ /**
+ * Interface to handle terrible failures from {@link #wtf}.
+ *
+ * @hide
+ */
+ public interface TerribleFailureHandler {
+ void onTerribleFailure(String tag, TerribleFailure what, boolean system);
+ }
+
+ private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
+ public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
+ RuntimeInit.wtf(tag, what, system);
+ }
+ };
+
private Log() {
}
@@ -65,7 +121,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println(LOG_ID_MAIN, VERBOSE, tag, msg);
+ return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -76,7 +132,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}
/**
@@ -86,7 +142,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println(LOG_ID_MAIN, DEBUG, tag, msg);
+ return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -97,7 +153,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
}
/**
@@ -107,7 +163,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println(LOG_ID_MAIN, INFO, tag, msg);
+ return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -118,7 +174,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
}
/**
@@ -128,7 +184,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println(LOG_ID_MAIN, WARN, tag, msg);
+ return println_native(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -139,9 +195,31 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
}
+ /**
+ * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+ *
+ * The default level of any tag is set to INFO. This means that any level above and including
+ * INFO will be logged. Before you make any calls to a logging method you should check to see
+ * if your tag should be logged. You can change the default level by setting a system property:
+ * 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
+ * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
+ * turn off all logging for your tag. You can also create a local.prop file that with the
+ * following in it:
+ * 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
+ * and place that in /data/local.prop.
+ *
+ * @param tag The tag to check.
+ * @param level The level to check.
+ * @return Whether or not that this is allowed to be logged.
+ * @throws IllegalArgumentException is thrown if the tag.length() > 23
+ * for Nougat (7.0) releases (API <= 23) and prior, there is no
+ * tag limit of concern after this API level.
+ */
+ public static native boolean isLoggable(String tag, int level);
+
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -149,7 +227,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
}
/**
@@ -159,7 +237,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println(LOG_ID_MAIN, ERROR, tag, msg);
+ return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -170,7 +248,82 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@link android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public static int wtf(String tag, String msg) {
+ return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
+ }
+
+ /**
+ * Like {@link #wtf(String, String)}, but also writes to the log the full
+ * call stack.
+ * @hide
+ */
+ public static int wtfStack(String tag, String msg) {
+ return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, String)}, with an exception to log.
+ * @param tag Used to identify the source of a log message.
+ * @param tr An exception to log.
+ */
+ public static int wtf(String tag, Throwable tr) {
+ return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, Throwable)}, with a message as well.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log. May be null.
+ */
+ public static int wtf(String tag, String msg, Throwable tr) {
+ return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
+ }
+
+ static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
+ boolean system) {
+ TerribleFailure what = new TerribleFailure(msg, tr);
+ // Only mark this as ERROR, do not use ASSERT since that should be
+ // reserved for cases where the system is guaranteed to abort.
+ // The onTerribleFailure call does not always cause a crash.
+ int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
+ sWtfHandler.onTerribleFailure(tag, what, system);
+ return bytes;
+ }
+
+ static void wtfQuiet(int logId, String tag, String msg, boolean system) {
+ TerribleFailure what = new TerribleFailure(msg, null);
+ sWtfHandler.onTerribleFailure(tag, what, system);
+ }
+
+ /**
+ * Sets the terrible failure handler, for testing.
+ *
+ * @return the old handler
+ *
+ * @hide
+ */
+ public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("handler == null");
+ }
+ TerribleFailureHandler oldHandler = sWtfHandler;
+ sWtfHandler = handler;
+ return oldHandler;
}
/**
@@ -193,7 +346,7 @@ public final class Log {
}
StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
+ PrintWriter pw = new FastPrintWriter(sw, false, 256);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
@@ -208,7 +361,7 @@ public final class Log {
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
- return println(LOG_ID_MAIN, priority, tag, msg);
+ return println_native(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -217,9 +370,115 @@ public final class Log {
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
- /** @hide */ @SuppressWarnings("unused")
- public static int println(int bufID,
- int priority, String tag, String msg) {
- return 0;
+ /** @hide */ public static native int println_native(int bufID,
+ int priority, String tag, String msg);
+
+ /**
+ * Return the maximum payload the log daemon accepts without truncation.
+ * @return LOGGER_ENTRY_MAX_PAYLOAD.
+ */
+ private static native int logger_entry_max_payload_native();
+
+ /**
+ * Helper function for long messages. Uses the LineBreakBufferedWriter to break
+ * up long messages and stacktraces along newlines, but tries to write in large
+ * chunks. This is to avoid truncation.
+ * @hide
+ */
+ public static int printlns(int bufID, int priority, String tag, String msg,
+ Throwable tr) {
+ ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
+ // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
+ // and the length of the tag.
+ // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
+ // is too expensive to compute that ahead of time.
+ int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
+ - 2 // Two terminators.
+ - (tag != null ? tag.length() : 0) // Tag length.
+ - 32; // Some slack.
+ // At least assume you can print *some* characters (tag is not too large).
+ bufferSize = Math.max(bufferSize, 100);
+
+ LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
+
+ lbbw.println(msg);
+
+ if (tr != null) {
+ // This is to reduce the amount of log spew that apps do in the non-error
+ // condition of the network being unavailable.
+ Throwable t = tr;
+ while (t != null) {
+ if (t instanceof UnknownHostException) {
+ break;
+ }
+ if (t instanceof DeadSystemException) {
+ lbbw.println("DeadSystemException: The system died; "
+ + "earlier logs will point to the root cause");
+ break;
+ }
+ t = t.getCause();
+ }
+ if (t == null) {
+ tr.printStackTrace(lbbw);
+ }
+ }
+
+ lbbw.flush();
+
+ return logWriter.getWritten();
+ }
+
+ /**
+ * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
+ * a JNI call during logging.
+ */
+ static class PreloadHolder {
+ public final static int LOGGER_ENTRY_MAX_PAYLOAD =
+ logger_entry_max_payload_native();
+ }
+
+ /**
+ * Helper class to write to the logcat. Different from LogWriter, this writes
+ * the whole given buffer and does not break along newlines.
+ */
+ private static class ImmediateLogWriter extends Writer {
+
+ private int bufID;
+ private int priority;
+ private String tag;
+
+ private int written = 0;
+
+ /**
+ * Create a writer that immediately writes to the log, using the given
+ * parameters.
+ */
+ public ImmediateLogWriter(int bufID, int priority, String tag) {
+ this.bufID = bufID;
+ this.priority = priority;
+ this.tag = tag;
+ }
+
+ public int getWritten() {
+ return written;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) {
+ // Note: using String here has a bit of overhead as a Java object is created,
+ // but using the char[] directly is not easier, as it needs to be translated
+ // to a C char[] for logging.
+ written += println_native(bufID, priority, tag, new String(cbuf, off, len));
+ }
+
+ @Override
+ public void flush() {
+ // Ignored.
+ }
+
+ @Override
+ public void close() {
+ // Ignored.
+ }
}
}
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 52086065..40154880 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,10 +20,6 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
- * BEGIN LAYOUTLIB CHANGE
- * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
- * END LAYOUTLIB CHANGE
- *
* A cache that holds strong references to a limited number of values. Each time
* a value is accessed, it is moved to the head of a queue. When a value is
* added to a full cache, the value at the end of that queue is evicted and may
@@ -91,9 +87,8 @@ public class LruCache<K, V> {
/**
* Sets the size of the cache.
- * @param maxSize The new maximum size.
*
- * @hide
+ * @param maxSize The new maximum size.
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
@@ -190,10 +185,13 @@ public class LruCache<K, V> {
}
/**
+ * Remove the eldest entries until the total of remaining entries is at or
+ * below the requested size.
+ *
* @param maxSize the maximum size of the cache before returning. May be -1
- * to evict even 0-sized elements.
+ * to evict even 0-sized elements.
*/
- private void trimToSize(int maxSize) {
+ public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
@@ -207,16 +205,7 @@ public class LruCache<K, V> {
break;
}
- // BEGIN LAYOUTLIB CHANGE
- // get the last item in the linked list.
- // This is not efficient, the goal here is to minimize the changes
- // compared to the platform version.
- Map.Entry<K, V> toEvict = null;
- for (Map.Entry<K, V> entry : map.entrySet()) {
- toEvict = entry;
- }
- // END LAYOUTLIB CHANGE
-
+ Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
diff --git a/android/util/StatsLog.java b/android/util/StatsLog.java
new file mode 100644
index 00000000..0be1a8cf
--- /dev/null
+++ b/android/util/StatsLog.java
@@ -0,0 +1,76 @@
+/*
+ * 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
new file mode 100644
index 00000000..9ad0a23d
--- /dev/null
+++ b/android/util/StatsLogKey.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// THIS FILE IS AUTO-GENERATED.
+// DO NOT MODIFY.
+
+package android.util;
+
+/** @hide */
+public class StatsLogKey {
+ private StatsLogKey() {}
+
+ /** Constants for android.os.statsd.ScreenStateChange. */
+
+ /** display_state */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE = 1;
+
+ /** Constants for android.os.statsd.ProcessStateChange. */
+
+ /** state */
+ public static final int PROCESS_STATE_CHANGE__STATE = 1;
+
+ /** uid */
+ public static final int PROCESS_STATE_CHANGE__UID = 2;
+
+ /** package_name */
+ public static final int PROCESS_STATE_CHANGE__PACKAGE_NAME = 1002;
+
+ /** package_version */
+ public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION = 3;
+
+ /** package_version_string */
+ public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION_STRING = 4;
+
+}
diff --git a/android/support/car/drawer/DrawerItemClickListener.java b/android/util/StatsLogTag.java
index d707dbd0..5e5a8287 100644
--- a/android/support/car/drawer/DrawerItemClickListener.java
+++ b/android/util/StatsLogTag.java
@@ -14,16 +14,19 @@
* limitations under the License.
*/
-package android.support.car.drawer;
+// 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;
-/**
- * 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/util/StatsLogValue.java b/android/util/StatsLogValue.java
new file mode 100644
index 00000000..05b9d933
--- /dev/null
+++ b/android/util/StatsLogValue.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// THIS FILE IS AUTO-GENERATED.
+// DO NOT MODIFY.
+
+package android.util;
+
+/** @hide */
+public class StatsLogValue {
+ private StatsLogValue() {}
+
+ /** Constants for android.os.statsd.ScreenStateChange. */
+
+ /** display_state: STATE_UNKNOWN */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_UNKNOWN = 0;
+
+ /** display_state: STATE_OFF */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF = 1;
+
+ /** display_state: STATE_ON */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON = 2;
+
+ /** display_state: STATE_DOZE */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE = 3;
+
+ /** display_state: STATE_DOZE_SUSPEND */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE_SUSPEND = 4;
+
+ /** display_state: STATE_VR */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_VR = 5;
+
+ /** Constants for android.os.statsd.ProcessStateChange. */
+
+ /** state: START */
+ public static final int PROCESS_STATE_CHANGE__STATE__START = 1;
+
+ /** state: CRASH */
+ public static final int PROCESS_STATE_CHANGE__STATE__CRASH = 2;
+
+}
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 54825895..31daefff 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -21,22 +21,15 @@ 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
@@ -61,34 +54,25 @@ public class SurfaceControl {
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
- 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,
+ 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,
float dtdy, float dsdy);
- 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 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 boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -98,16 +82,15 @@ 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(long transactionObj,
+ private static native void nativeSetDisplaySurface(
IBinder displayToken, long nativeSurfaceObject);
- private static native void nativeSetDisplayLayerStack(long transactionObj,
+ private static native void nativeSetDisplayLayerStack(
IBinder displayToken, int layerStack);
- private static native void nativeSetDisplayProjection(long transactionObj,
+ private static native void nativeSetDisplayProjection(
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(long transactionObj, IBinder displayToken,
- int width, int height);
+ private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height);
private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
IBinder displayToken);
private static native int nativeGetActiveConfig(IBinder displayToken);
@@ -118,17 +101,16 @@ public class SurfaceControl {
int colorMode);
private static native void nativeSetDisplayPowerMode(
IBinder displayToken, int mode);
- private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
+ private static native void nativeDeferTransactionUntil(long nativeObject,
IBinder handle, long frame);
- private static native void nativeDeferTransactionUntilSurface(long transactionObj,
- long nativeObject,
+ private static native void nativeDeferTransactionUntilSurface(long nativeObject,
long surfaceObject, long frame);
- private static native void nativeReparentChildren(long transactionObj, long nativeObject,
+ private static native void nativeReparentChildren(long nativeObject,
IBinder handle);
- private static native void nativeReparent(long transactionObj, long nativeObject,
+ private static native void nativeReparent(long nativeObject,
IBinder parentHandle);
- private static native void nativeSeverChildren(long transactionObj, long nativeObject);
- private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
+ private static native void nativeSeverChildren(long nativeObject);
+ private static native void nativeSetOverrideScalingMode(long nativeObject,
int scalingMode);
private static native IBinder nativeGetHandle(long nativeObject);
private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
@@ -140,9 +122,6 @@ 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) */
/**
@@ -398,6 +377,11 @@ 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.
@@ -445,141 +429,102 @@ public class SurfaceControl {
/** start a transaction */
public static void openTransaction() {
- 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);
- }
+ nativeOpenTransaction();
}
/** end a transaction */
public static void closeTransaction() {
- closeTransaction(false);
+ nativeCloseTransaction(false);
}
public static void closeTransactionSync() {
- closeTransaction(true);
+ nativeCloseTransaction(true);
}
public void deferTransactionUntil(IBinder handle, long frame) {
if (frame > 0) {
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.deferTransactionUntil(this, handle, frame);
- }
+ nativeDeferTransactionUntil(mNativeObject, handle, frame);
}
}
public void deferTransactionUntil(Surface barrier, long frame) {
if (frame > 0) {
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame);
- }
+ nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
}
}
public void reparentChildren(IBinder newParentHandle) {
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.reparentChildren(this, newParentHandle);
- }
+ nativeReparentChildren(mNativeObject, newParentHandle);
}
+ /** Re-parents this layer to a new parent. */
public void reparent(IBinder newParentHandle) {
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.reparent(this, newParentHandle);
- }
+ nativeReparent(mNativeObject, newParentHandle);
}
public void detachChildren() {
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.detachChildren(this);
- }
+ nativeSeverChildren(mNativeObject);
}
public void setOverrideScalingMode(int scalingMode) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setOverrideScalingMode(this, scalingMode);
- }
+ nativeSetOverrideScalingMode(mNativeObject, scalingMode);
}
public IBinder getHandle() {
return nativeGetHandle(mNativeObject);
}
+ /** flag the transaction as an animation */
public static void setAnimationTransaction() {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setAnimationTransaction();
- }
+ nativeSetAnimationTransaction();
}
public void setLayer(int zorder) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setLayer(this, zorder);
- }
+ nativeSetLayer(mNativeObject, zorder);
}
public void setRelativeLayer(IBinder relativeTo, int zorder) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
- }
+ nativeSetRelativeLayer(mNativeObject, relativeTo, zorder);
}
public void setPosition(float x, float y) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setPosition(this, x, y);
- }
+ nativeSetPosition(mNativeObject, 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();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setGeometryAppliesWithResize(this);
- }
+ nativeSetGeometryAppliesWithResize(mNativeObject);
}
public void setSize(int w, int h) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setSize(this, w, h);
- }
+ nativeSetSize(mNativeObject, w, h);
}
public void hide() {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.hide(this);
- }
+ nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
}
public void show() {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.show(this);
- }
+ nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
}
public void setTransparentRegionHint(Region region) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setTransparentRegionHint(this, region);
- }
+ nativeSetTransparentRegionHint(mNativeObject, region);
}
public boolean clearContentFrameStats() {
@@ -600,70 +545,80 @@ 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();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setAlpha(this, alpha);
- }
+ nativeSetAlpha(mNativeObject, alpha);
}
+ /**
+ * Sets a color for the Surface.
+ * @param color A float array with three values to represent r, g, b in range [0..1]
+ */
public void setColor(@Size(3) float[] color) {
checkNotReleased();
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setColor(this, color);
- }
+ nativeSetColor(mNativeObject, color);
}
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setMatrix(this, dsdx, dtdx, dtdy, dsdy);
- }
+ nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
}
public void setWindowCrop(Rect crop) {
checkNotReleased();
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setWindowCrop(this, crop);
+ if (crop != null) {
+ nativeSetWindowCrop(mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
}
}
public void setFinalCrop(Rect crop) {
checkNotReleased();
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setFinalCrop(this, crop);
+ if (crop != null) {
+ nativeSetFinalCrop(mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0);
}
}
public void setLayerStack(int layerStack) {
checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setLayerStack(this, layerStack);
- }
+ nativeSetLayerStack(mNativeObject, 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();
-
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setOpaque(this, isOpaque);
+ if (isOpaque) {
+ nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
+ } else {
+ nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE);
}
}
+ /**
+ * 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();
-
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setSecure(this, isSecure);
+ if (isSecure) {
+ nativeSetFlags(mNativeObject, SECURE, SECURE);
+ } else {
+ nativeSetFlags(mNativeObject, 0, SECURE);
}
}
- @Override
- public String toString() {
- return "Surface(name=" + mName + ")/@0x" +
- Integer.toHexString(System.identityHashCode(this));
- }
-
/*
* set display parameters.
* needs to be inside open/closeTransaction block
@@ -786,28 +741,50 @@ public class SurfaceControl {
public static void setDisplayProjection(IBinder displayToken,
int orientation, Rect layerStackRect, Rect displayRect) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplayProjection(displayToken, orientation,
- layerStackRect, 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(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) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
}
+ nativeSetDisplayLayerStack(displayToken, layerStack);
}
public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplaySurface(displayToken, 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);
}
}
public static void setDisplaySize(IBinder displayToken, int width, int height) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplaySize(displayToken, width, 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(displayToken, width, height);
}
public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
@@ -969,261 +946,4 @@ public class SurfaceControl {
nativeScreenshot(display, consumer, sourceCrop, width, height,
minLayer, maxLayer, allLayers, useIdentityTransform);
}
-
- public static class Transaction implements Closeable {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Transaction.class.getClassLoader(),
- nativeGetNativeTransactionFinalizer(), 512);
- private long mNativeObject;
-
- Runnable mFreeNativeResources;
-
- public Transaction() {
- mNativeObject = nativeCreateTransaction();
- mFreeNativeResources
- = sRegistry.registerNativeAllocation(this, mNativeObject);
- }
-
- /**
- * Apply the transaction, clearing it's state, and making it usable
- * as a new transaction.
- */
- public void apply() {
- apply(false);
- }
-
- /**
- * Close the transaction, if the transaction was not already applied this will cancel the
- * transaction.
- */
- @Override
- public void close() {
- mFreeNativeResources.run();
- mNativeObject = 0;
- }
-
- /**
- * Jankier version of apply. Avoid use (b/28068298).
- */
- public void apply(boolean sync) {
- nativeApplyTransaction(mNativeObject, sync);
- }
-
- public Transaction show(SurfaceControl sc) {
- nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
- return this;
- }
-
- public Transaction hide(SurfaceControl sc) {
- nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
- return this;
- }
-
- public Transaction setPosition(SurfaceControl sc, float x, float y) {
- nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
- return this;
- }
-
- public Transaction setSize(SurfaceControl sc, int w, int h) {
- nativeSetSize(mNativeObject, sc.mNativeObject,
- w, h);
- return this;
- }
-
- public Transaction setLayer(SurfaceControl sc, int z) {
- nativeSetLayer(mNativeObject, sc.mNativeObject, z);
- return this;
- }
-
- public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
- nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
- relativeTo, z);
- return this;
- }
-
- public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
- nativeSetTransparentRegionHint(mNativeObject,
- sc.mNativeObject, transparentRegion);
- return this;
- }
-
- public Transaction setAlpha(SurfaceControl sc, float alpha) {
- nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
- return this;
- }
-
- public Transaction setMatrix(SurfaceControl sc,
- float dsdx, float dtdx, float dtdy, float dsdy) {
- nativeSetMatrix(mNativeObject, sc.mNativeObject,
- dsdx, dtdx, dtdy, dsdy);
- return this;
- }
-
- public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
- if (crop != null) {
- nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
- crop.left, crop.top, crop.right, crop.bottom);
- } else {
- nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
- }
-
- return this;
- }
-
- public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
- if (crop != null) {
- nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
- crop.left, crop.top, crop.right, crop.bottom);
- } else {
- nativeSetFinalCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
- }
-
- return this;
- }
-
- public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
- nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
- return this;
- }
-
- public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
- nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
- return this;
- }
-
- public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
- long frameNumber) {
- nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
- barrierSurface.mNativeObject, frameNumber);
- return this;
- }
-
- public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
- nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
- return this;
- }
-
- /** Re-parents a specific child layer to a new parent */
- public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
- nativeReparent(mNativeObject, sc.mNativeObject,
- newParentHandle);
- return this;
- }
-
- public Transaction detachChildren(SurfaceControl sc) {
- nativeSeverChildren(mNativeObject, sc.mNativeObject);
- return this;
- }
-
- public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
- nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
- overrideScalingMode);
- return this;
- }
-
- /**
- * Sets a color for the Surface.
- * @param color A float array with three values to represent r, g, b in range [0..1]
- */
- public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
- nativeSetColor(mNativeObject, sc.mNativeObject, color);
- return this;
- }
-
- /**
- * If the buffer size changes in this transaction, position and crop updates specified
- * in this transaction will not complete until a buffer of the new size
- * arrives. As transform matrix and size are already frozen in this fashion,
- * this enables totally freezing the surface until the resize has completed
- * (at which point the geometry influencing aspects of this transaction will then occur)
- */
- public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
- nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
- return this;
- }
-
- /**
- * Sets the security of the surface. Setting the flag is equivalent to creating the
- * Surface with the {@link #SECURE} flag.
- */
- Transaction setSecure(SurfaceControl sc, boolean isSecure) {
- if (isSecure) {
- nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
- } else {
- nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE);
- }
- return this;
- }
-
- /**
- * Sets the opacity of the surface. Setting the flag is equivalent to creating the
- * Surface with the {@link #OPAQUE} flag.
- */
- public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
- if (isOpaque) {
- nativeSetFlags(mNativeObject, sc.mNativeObject, OPAQUE, OPAQUE);
- } else {
- nativeSetFlags(mNativeObject, sc.mNativeObject, 0, OPAQUE);
- }
- return this;
- }
-
- public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
-
- if (surface != null) {
- synchronized (surface.mLock) {
- nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject);
- }
- } else {
- nativeSetDisplaySurface(mNativeObject, displayToken, 0);
- }
- return this;
- }
-
- public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack);
- return this;
- }
-
- public Transaction setDisplayProjection(IBinder displayToken,
- int orientation, Rect layerStackRect, Rect displayRect) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (layerStackRect == null) {
- throw new IllegalArgumentException("layerStackRect must not be null");
- }
- if (displayRect == null) {
- throw new IllegalArgumentException("displayRect must not be null");
- }
- nativeSetDisplayProjection(mNativeObject, displayToken, orientation,
- layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
- displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
- return this;
- }
-
- public Transaction setDisplaySize(IBinder displayToken, int width, int height) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("width and height must be positive");
- }
-
- nativeSetDisplaySize(mNativeObject, displayToken, width, height);
- return this;
- }
-
- /** flag the transaction as an animation */
- public Transaction setAnimationTransaction() {
- nativeSetAnimationTransaction(mNativeObject);
- return this;
- }
- }
}
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index ebb2af45..462dad3f 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,115 +16,1208 @@
package android.view;
-import com.android.layoutlib.bridge.MockView;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
import android.content.Context;
+import android.content.res.CompatibilityInfo.Translator;
+import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
/**
- * Mock version of the SurfaceView.
- * Only non override public methods from the real SurfaceView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ * Provides a dedicated drawing surface embedded inside of a view hierarchy.
+ * You can control the format of this surface and, if you like, its size; the
+ * SurfaceView takes care of placing the surface at the correct location on the
+ * screen
+ *
+ * <p>The surface is Z ordered so that it is behind the window holding its
+ * SurfaceView; the SurfaceView punches a hole in its window to allow its
+ * surface to be displayed. The view hierarchy will take care of correctly
+ * compositing with the Surface any siblings of the SurfaceView that would
+ * normally appear on top of it. This can be used to place overlays such as
+ * buttons on top of the Surface, though note however that it can have an
+ * impact on performance since a full alpha-blended composite will be performed
+ * each time the Surface changes.
+ *
+ * <p> The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
*
- * TODO: generate automatically.
+ * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
*
+ * <p>The Surface will be created for you while the SurfaceView's window is
+ * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
+ * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
+ * Surface is created and destroyed as the window is shown and hidden.
+ *
+ * <p>One of the purposes of this class is to provide a surface in which a
+ * secondary thread can render into the screen. If you are going to use it
+ * this way, you need to be aware of some threading semantics:
+ *
+ * <ul>
+ * <li> All SurfaceView and
+ * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
+ * from the thread running the SurfaceView's window (typically the main thread
+ * of the application). They thus need to correctly synchronize with any
+ * state that is also touched by the drawing thread.
+ * <li> You must ensure that the drawing thread only touches the underlying
+ * Surface while it is valid -- between
+ * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
+ * and
+ * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
+ * </ul>
+ *
+ * <p class="note"><strong>Note:</strong> Starting in platform version
+ * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
+ * updated synchronously with other View rendering. This means that translating
+ * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
+ * artifacts may occur on previous versions of the platform when its window is
+ * positioned asynchronously.</p>
*/
-public class SurfaceView extends MockView {
+public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
+ private static final String TAG = "SurfaceView";
+ private static final boolean DEBUG = false;
+
+ final ArrayList<SurfaceHolder.Callback> mCallbacks
+ = new ArrayList<SurfaceHolder.Callback>();
+
+ final int[] mLocation = new int[2];
+
+ final ReentrantLock mSurfaceLock = new ReentrantLock();
+ final Surface mSurface = new Surface(); // Current surface in use
+ boolean mDrawingStopped = true;
+ // We use this to track if the application has produced a frame
+ // in to the Surface. Up until that point, we should be careful not to punch
+ // holes.
+ boolean mDrawFinished = false;
+
+ final Rect mScreenRect = new Rect();
+ SurfaceSession mSurfaceSession;
+
+ SurfaceControl mSurfaceControl;
+ // In the case of format changes we switch out the surface in-place
+ // we need to preserve the old one until the new one has drawn.
+ SurfaceControl mDeferredDestroySurfaceControl;
+ final Rect mTmpRect = new Rect();
+ final Configuration mConfiguration = new Configuration();
+
+ int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+
+ boolean mIsCreating = false;
+ private volatile boolean mRtHandlingPositionUpdates = false;
+
+ private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+ = new ViewTreeObserver.OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ updateSurface();
+ }
+ };
+
+ private final ViewTreeObserver.OnPreDrawListener mDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ // reposition ourselves where the surface is
+ mHaveFrame = getWidth() > 0 && getHeight() > 0;
+ updateSurface();
+ return true;
+ }
+ };
+
+ boolean mRequestedVisible = false;
+ boolean mWindowVisibility = false;
+ boolean mLastWindowVisibility = false;
+ boolean mViewVisibility = false;
+ boolean mWindowStopped = false;
+
+ int mRequestedWidth = -1;
+ int mRequestedHeight = -1;
+ /* Set SurfaceView's format to 565 by default to maintain backward
+ * compatibility with applications assuming this format.
+ */
+ int mRequestedFormat = PixelFormat.RGB_565;
+
+ boolean mHaveFrame = false;
+ boolean mSurfaceCreated = false;
+ long mLastLockTime = 0;
+
+ boolean mVisible = false;
+ int mWindowSpaceLeft = -1;
+ int mWindowSpaceTop = -1;
+ int mSurfaceWidth = -1;
+ int mSurfaceHeight = -1;
+ int mFormat = -1;
+ final Rect mSurfaceFrame = new Rect();
+ int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
+ private Translator mTranslator;
+
+ private boolean mGlobalListenersAdded;
+ private boolean mAttachedToWindow;
+
+ private int mSurfaceFlags = SurfaceControl.HIDDEN;
+
+ private int mPendingReportDraws;
public SurfaceView(Context context) {
this(context, null);
}
public SurfaceView(Context context, AttributeSet attrs) {
- this(context, attrs , 0);
+ this(context, attrs, 0);
}
- public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mRenderNode.requestPositionUpdates(this);
+
+ setWillNotDraw(true);
+ }
+
+ /**
+ * Return the SurfaceHolder providing access and control over this
+ * SurfaceView's underlying surface.
+ *
+ * @return SurfaceHolder The holder of the surface.
+ */
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
+ }
+
+ private void updateRequestedVisibility() {
+ mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
+ }
+
+ /** @hide */
+ @Override
+ public void windowStopped(boolean stopped) {
+ mWindowStopped = stopped;
+ updateRequestedVisibility();
+ updateSurface();
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getViewRootImpl().addWindowStoppedCallback(this);
+ mWindowStopped = false;
+
+ mViewVisibility = getVisibility() == VISIBLE;
+ updateRequestedVisibility();
+
+ mAttachedToWindow = true;
+ mParent.requestTransparentRegion(SurfaceView.this);
+ if (!mGlobalListenersAdded) {
+ ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnScrollChangedListener(mScrollChangedListener);
+ observer.addOnPreDrawListener(mDrawListener);
+ mGlobalListenersAdded = true;
+ }
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ mWindowVisibility = visibility == VISIBLE;
+ updateRequestedVisibility();
+ updateSurface();
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ mViewVisibility = visibility == VISIBLE;
+ boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
+ if (newRequestedVisible != mRequestedVisible) {
+ // our base class (View) invalidates the layout only when
+ // we go from/to the GONE state. However, SurfaceView needs
+ // to request a re-layout when the visibility changes at all.
+ // This is needed because the transparent region is computed
+ // as part of the layout phase, and it changes (obviously) when
+ // the visibility changes.
+ requestLayout();
+ }
+ mRequestedVisible = newRequestedVisible;
+ updateSurface();
+ }
+
+ private void performDrawFinished() {
+ if (mPendingReportDraws > 0) {
+ mDrawFinished = true;
+ if (mAttachedToWindow) {
+ notifyDrawFinished();
+ invalidate();
+ }
+ } else {
+ Log.e(TAG, System.identityHashCode(this) + "finished drawing"
+ + " but no pending report draw (extra call"
+ + " to draw completion runnable?)");
+ }
+ }
+
+ void notifyDrawFinished() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.pendingDrawFinished();
+ }
+ mPendingReportDraws--;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ // It's possible to create a SurfaceView using the default constructor and never
+ // attach it to a view hierarchy, this is a common use case when dealing with
+ // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
+ // the lifecycle. Instead of attaching it to a view, he/she can just pass
+ // the SurfaceHolder forward, most live wallpapers do it.
+ if (viewRoot != null) {
+ viewRoot.removeWindowStoppedCallback(this);
+ }
+
+ mAttachedToWindow = false;
+ if (mGlobalListenersAdded) {
+ ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnScrollChangedListener(mScrollChangedListener);
+ observer.removeOnPreDrawListener(mDrawListener);
+ mGlobalListenersAdded = false;
+ }
+
+ while (mPendingReportDraws > 0) {
+ notifyDrawFinished();
+ }
+
+ mRequestedVisible = false;
+
+ updateSurface();
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ }
+ mSurfaceControl = null;
+
+ mHaveFrame = false;
+
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = mRequestedWidth >= 0
+ ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
+ : getDefaultSize(0, widthMeasureSpec);
+ int height = mRequestedHeight >= 0
+ ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
+ : getDefaultSize(0, heightMeasureSpec);
+ setMeasuredDimension(width, height);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean result = super.setFrame(left, top, right, bottom);
+ updateSurface();
+ return result;
+ }
+
+ @Override
public boolean gatherTransparentRegion(Region region) {
- return false;
+ if (isAboveParent() || !mDrawFinished) {
+ return super.gatherTransparentRegion(region);
+ }
+
+ boolean opaque = true;
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+ // this view draws, remove it from the transparent region
+ opaque = super.gatherTransparentRegion(region);
+ } else if (region != null) {
+ int w = getWidth();
+ int h = getHeight();
+ if (w>0 && h>0) {
+ getLocationInWindow(mLocation);
+ // otherwise, punch a hole in the whole hierarchy
+ int l = mLocation[0];
+ int t = mLocation[1];
+ region.op(l, t, l+w, t+h, Region.Op.UNION);
+ }
+ }
+ if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ opaque = false;
+ }
+ return opaque;
}
+ @Override
+ public void draw(Canvas canvas) {
+ if (mDrawFinished && !isAboveParent()) {
+ // draw() is not called when SKIP_DRAW is set
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+ // punch a whole in the view-hierarchy below us
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
+ }
+ super.draw(canvas);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mDrawFinished && !isAboveParent()) {
+ // draw() is not called when SKIP_DRAW is set
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ // punch a whole in the view-hierarchy below us
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ /**
+ * Control whether the surface view's surface is placed on top of another
+ * regular surface view in the window (but still behind the window itself).
+ * This is typically used to place overlays on top of an underlying media
+ * surface view.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+ */
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ mSubLayer = isMediaOverlay
+ ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
}
+ /**
+ * Control whether the surface view's surface is placed on top of its
+ * window. Normally it is placed behind the window, to allow it to
+ * (for the most part) appear to composite with the views in the
+ * hierarchy. By setting this, you cause it to be placed above the
+ * window. This means that none of the contents of the window this
+ * SurfaceView is in will be visible on top of its surface.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+ */
public void setZOrderOnTop(boolean onTop) {
+ if (onTop) {
+ mSubLayer = APPLICATION_PANEL_SUBLAYER;
+ } else {
+ mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+ }
}
+ /**
+ * Control whether the surface view's content should be treated as secure,
+ * preventing it from appearing in screenshots or from being viewed on
+ * non-secure displays.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>See {@link android.view.Display#FLAG_SECURE} for details.
+ *
+ * @param isSecure True if the surface view is secure.
+ */
public void setSecure(boolean isSecure) {
+ if (isSecure) {
+ mSurfaceFlags |= SurfaceControl.SECURE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.SECURE;
+ }
}
- public SurfaceHolder getHolder() {
- return mSurfaceHolder;
+ private void updateOpaqueFlag() {
+ if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ mSurfaceFlags |= SurfaceControl.OPAQUE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.OPAQUE;
+ }
}
- private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private Rect getParentSurfaceInsets() {
+ final ViewRootImpl root = getViewRootImpl();
+ if (root == null) {
+ return null;
+ } else {
+ return root.mWindowAttributes.surfaceInsets;
+ }
+ }
+
+ /** @hide */
+ protected void updateSurface() {
+ if (!mHaveFrame) {
+ return;
+ }
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+ return;
+ }
+
+ mTranslator = viewRoot.mTranslator;
+ if (mTranslator != null) {
+ mSurface.setCompatibilityTranslator(mTranslator);
+ }
+
+ int myWidth = mRequestedWidth;
+ if (myWidth <= 0) myWidth = getWidth();
+ int myHeight = mRequestedHeight;
+ if (myHeight <= 0) myHeight = getHeight();
+
+ final boolean formatChanged = mFormat != mRequestedFormat;
+ final boolean visibleChanged = mVisible != mRequestedVisible;
+ final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
+ && mRequestedVisible;
+ final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
+ final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
+ boolean redrawNeeded = false;
+
+ if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+ getLocationInWindow(mLocation);
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Changes: creating=" + creating
+ + " format=" + formatChanged + " size=" + sizeChanged
+ + " visible=" + visibleChanged
+ + " left=" + (mWindowSpaceLeft != mLocation[0])
+ + " top=" + (mWindowSpaceTop != mLocation[1]));
+
+ try {
+ final boolean visible = mVisible = mRequestedVisible;
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
+ mFormat = mRequestedFormat;
+ mLastWindowVisibility = mWindowVisibility;
+
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ final Rect surfaceInsets = getParentSurfaceInsets();
+ mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+
+ if (creating) {
+ mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
+ mDeferredDestroySurfaceControl = mSurfaceControl;
+
+ updateOpaqueFlag();
+ mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
+ "SurfaceView - " + viewRoot.getTitle().toString(),
+ mSurfaceWidth, mSurfaceHeight, mFormat,
+ mSurfaceFlags);
+ } else if (mSurfaceControl == null) {
+ return;
+ }
+
+ boolean realSizeChanged = false;
+
+ mSurfaceLock.lock();
+ try {
+ mDrawingStopped = !visible;
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
+
+ SurfaceControl.openTransaction();
+ try {
+ mSurfaceControl.setLayer(mSubLayer);
+ if (mViewVisibility) {
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
+ }
+
+ // While creating the surface, we will set it's initial
+ // geometry. Outside of that though, we should generally
+ // leave it to the RenderThread.
+ //
+ // There is one more case when the buffer size changes we aren't yet
+ // prepared to sync (as even following the transaction applying
+ // we still need to latch a buffer).
+ // b/28866173
+ if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+ mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+ mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ mScreenRect.height() / (float) mSurfaceHeight);
+ }
+ if (sizeChanged) {
+ mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+ }
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+
+ if (sizeChanged || creating) {
+ redrawNeeded = true;
+ }
+
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (mTranslator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
+ } else {
+ float appInvertedScale = mTranslator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+ }
+
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+ } finally {
+ mSurfaceLock.unlock();
+ }
+
+ try {
+ redrawNeeded |= visible && !mDrawFinished;
+
+ SurfaceHolder.Callback callbacks[] = null;
+
+ final boolean surfaceChanged = creating;
+ if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
+ mSurfaceCreated = false;
+ if (mSurface.isValid()) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceDestroyed");
+ callbacks = getSurfaceCallbacks();
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
+ // Since Android N the same surface may be reused and given to us
+ // again by the system server at a later point. However
+ // as we didn't do this in previous releases, clients weren't
+ // necessarily required to clean up properly in
+ // surfaceDestroyed. This leads to problems for example when
+ // clients don't destroy their EGL context, and try
+ // and create a new one on the same surface following reuse.
+ // Since there is no valid use of the surface in-between
+ // surfaceDestroyed and surfaceCreated, we force a disconnect,
+ // so the next connect will always work if we end up reusing
+ // the surface.
+ if (mSurface.isValid()) {
+ mSurface.forceScopedDisconnect();
+ }
+ }
+ }
+
+ if (creating) {
+ mSurface.copyFrom(mSurfaceControl);
+ }
+
+ if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ mSurface.createFrom(mSurfaceControl);
+ }
+
+ if (visible && mSurface.isValid()) {
+ if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ mSurfaceCreated = true;
+ mIsCreating = true;
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceCreated");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
+ }
+ if (creating || formatChanged || sizeChanged
+ || visibleChanged || realSizeChanged) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceChanged -- format=" + mFormat
+ + " w=" + myWidth + " h=" + myHeight);
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ }
+ }
+ if (redrawNeeded) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceRedrawNeeded");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+
+ mPendingReportDraws++;
+ viewRoot.drawPending();
+ SurfaceCallbackHelper sch =
+ new SurfaceCallbackHelper(this::onDrawFinished);
+ sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+ }
+ }
+ } finally {
+ mIsCreating = false;
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ mSurface.release();
+ // If we are not in the stopped state, then the destruction of the Surface
+ // represents a visual change we need to display, and we should go ahead
+ // and destroy the SurfaceControl. However if we are in the stopped state,
+ // we can just leave the Surface around so it can be a part of animations,
+ // and we let the life-time be tied to the parent surface.
+ if (!mWindowStopped) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ }
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ if (DEBUG) Log.v(
+ TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+ + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+ + ", frame=" + mSurfaceFrame);
+ } else {
+ // Calculate the window position in case RT loses the window
+ // and we need to fallback to a UI-thread driven position update
+ getLocationInSurface(mLocation);
+ final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+ || mWindowSpaceTop != mLocation[1];
+ final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
+ || getHeight() != mScreenRect.height();
+ if (positionChanged || layoutSizeChanged) { // Only the position has changed
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
+ // in view local space.
+ mLocation[0] = getWidth();
+ mLocation[1] = getHeight();
+
+ mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+ mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, -1);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+ }
+
+ private void onDrawFinished() {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " "
+ + "finishedDrawing");
+ }
+
+ if (mDeferredDestroySurfaceControl != null) {
+ mDeferredDestroySurfaceControl.destroy();
+ mDeferredDestroySurfaceControl = null;
+ }
+
+ runOnUiThread(() -> {
+ performDrawFinished();
+ });
+ }
+
+ private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+
+ SurfaceControl.openTransaction();
+ try {
+ if (frameNumber > 0) {
+ mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
+ }
+ mSurfaceControl.setPosition(position.left, position.top);
+ mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ position.height() / (float) mSurfaceHeight);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
+ private Rect mRTLastReportedPosition = new Rect();
+
+ /**
+ * Called by native by a Rendering Worker thread to update the window position
+ * @hide
+ */
+ public final void updateSurfacePosition_renderWorker(long frameNumber,
+ int left, int top, int right, int bottom) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+ // its 2nd frame if RenderThread is running slowly could potentially see
+ // this as false, enter the branch, get pre-empted, then this comes along
+ // and reports a new position, then the UI thread resumes and reports
+ // its position. This could therefore be de-sync'd in that interval, but
+ // the synchronization would violate the rule that RT must never block
+ // on the UI thread which would open up potential deadlocks. The risk of
+ // a single-frame desync is therefore preferable for now.
+ mRtHandlingPositionUpdates = true;
+ if (mRTLastReportedPosition.left == left
+ && mRTLastReportedPosition.top == top
+ && mRTLastReportedPosition.right == right
+ && mRTLastReportedPosition.bottom == bottom) {
+ return;
+ }
+ try {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ frameNumber, left, top, right, bottom));
+ }
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+ // Now overwrite mRTLastReportedPosition with our values
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception from repositionChild", ex);
+ }
+ }
+
+ /**
+ * Called by native on RenderThread to notify that the view is no longer in the
+ * draw tree. UI thread is blocked at this point.
+ * @hide
+ */
+ public final void surfacePositionLost_uiRtSync(long frameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
+ System.identityHashCode(this), frameNumber));
+ }
+ mRTLastReportedPosition.setEmpty();
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+ if (mRtHandlingPositionUpdates) {
+ mRtHandlingPositionUpdates = false;
+ // This callback will happen while the UI thread is blocked, so we can
+ // safely access other member variables at this time.
+ // So do what the UI thread would have done if RT wasn't handling position
+ // updates.
+ if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, frameNumber);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+
+ private SurfaceHolder.Callback[] getSurfaceCallbacks() {
+ SurfaceHolder.Callback callbacks[];
+ synchronized (mCallbacks) {
+ callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+ mCallbacks.toArray(callbacks);
+ }
+ return callbacks;
+ }
+
+ /**
+ * This method still exists only for compatibility reasons because some applications have relied
+ * on this method via reflection. See Issue 36345857 for details.
+ *
+ * @deprecated No platform code is using this method anymore.
+ * @hide
+ */
+ @Deprecated
+ public void setWindowType(int type) {
+ if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+ throw new UnsupportedOperationException(
+ "SurfaceView#setWindowType() has never been a public API.");
+ }
+
+ if (type == TYPE_APPLICATION_PANEL) {
+ Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
+ + "just to make the SurfaceView to be placed on top of its window, you must "
+ + "call setZOrderOnTop(true) instead.", new Throwable());
+ setZOrderOnTop(true);
+ return;
+ }
+ Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
+ + "type=" + type, new Throwable());
+ }
+
+ private void runOnUiThread(Runnable runnable) {
+ Handler handler = getHandler();
+ if (handler != null && handler.getLooper() != Looper.myLooper()) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ /**
+ * Check to see if the surface has fixed size dimensions or if the surface's
+ * dimensions are dimensions are dependent on its current layout.
+ *
+ * @return true if the surface has dimensions that are fixed in size
+ * @hide
+ */
+ public boolean isFixedSize() {
+ return (mRequestedWidth != -1 || mRequestedHeight != -1);
+ }
+
+ private boolean isAboveParent() {
+ return mSubLayer >= 0;
+ }
+
+ private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private static final String LOG_TAG = "SurfaceHolder";
@Override
public boolean isCreating() {
- return false;
+ return mIsCreating;
}
@Override
public void addCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ // This is a linear search, but in practice we'll
+ // have only a couple callbacks, so it doesn't matter.
+ if (mCallbacks.contains(callback) == false) {
+ mCallbacks.add(callback);
+ }
+ }
}
@Override
public void removeCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
}
@Override
public void setFixedSize(int width, int height) {
+ if (mRequestedWidth != width || mRequestedHeight != height) {
+ mRequestedWidth = width;
+ mRequestedHeight = height;
+ requestLayout();
+ }
}
@Override
public void setSizeFromLayout() {
+ if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+ mRequestedWidth = mRequestedHeight = -1;
+ requestLayout();
+ }
}
@Override
public void setFormat(int format) {
+ // for backward compatibility reason, OPAQUE always
+ // means 565 for SurfaceView
+ if (format == PixelFormat.OPAQUE)
+ format = PixelFormat.RGB_565;
+
+ mRequestedFormat = format;
+ if (mSurfaceControl != null) {
+ updateSurface();
+ }
}
+ /**
+ * @deprecated setType is now ignored.
+ */
@Override
- public void setType(int type) {
- }
+ @Deprecated
+ public void setType(int type) { }
@Override
public void setKeepScreenOn(boolean screenOn) {
+ runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
}
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * The caller must redraw the entire surface.
+ * @return A canvas for drawing into the surface.
+ */
@Override
public Canvas lockCanvas() {
- return null;
+ return internalLockCanvas(null, false);
}
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param inOutDirty A rectangle that represents the dirty region that the caller wants
+ * to redraw. This function may choose to expand the dirty rectangle if for example
+ * the surface has been resized or if the previous contents of the surface were
+ * not available. The caller must redraw the entire dirty region as represented
+ * by the contents of the inOutDirty rectangle upon return from this function.
+ * The caller may also pass <code>null</code> instead, in the case where the
+ * entire surface should be redrawn.
+ * @return A canvas for drawing into the surface.
+ */
@Override
- public Canvas lockCanvas(Rect dirty) {
+ public Canvas lockCanvas(Rect inOutDirty) {
+ return internalLockCanvas(inOutDirty, false);
+ }
+
+ @Override
+ public Canvas lockHardwareCanvas() {
+ return internalLockCanvas(null, true);
+ }
+
+ private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
+ mSurfaceLock.lock();
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+ + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
+
+ Canvas c = null;
+ if (!mDrawingStopped && mSurfaceControl != null) {
+ try {
+ if (hardware) {
+ c = mSurface.lockHardwareCanvas();
+ } else {
+ c = mSurface.lockCanvas(dirty);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception locking surface", e);
+ }
+ }
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
+ if (c != null) {
+ mLastLockTime = SystemClock.uptimeMillis();
+ return c;
+ }
+
+ // If the Surface is not ready to be drawn, then return null,
+ // but throttle calls to this function so it isn't called more
+ // than every 100ms.
+ long now = SystemClock.uptimeMillis();
+ long nextTime = mLastLockTime + 100;
+ if (nextTime > now) {
+ try {
+ Thread.sleep(nextTime-now);
+ } catch (InterruptedException e) {
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ mLastLockTime = now;
+ mSurfaceLock.unlock();
+
return null;
}
+ /**
+ * Posts the new contents of the {@link Canvas} to the surface and
+ * releases the {@link Canvas}.
+ *
+ * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+ */
@Override
public void unlockCanvasAndPost(Canvas canvas) {
+ mSurface.unlockCanvasAndPost(canvas);
+ mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
- return null;
+ return mSurface;
}
@Override
public Rect getSurfaceFrame() {
- return null;
+ return mSurfaceFrame;
}
};
-}
+ class SurfaceControlWithBackground extends SurfaceControl {
+ private SurfaceControl mBackgroundControl;
+ private boolean mOpaque = true;
+ public boolean mVisible = false;
+
+ public SurfaceControlWithBackground(SurfaceSession s,
+ String name, int w, int h, int format, int flags)
+ throws Exception {
+ super(s, name, w, h, format, flags);
+ mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
+ PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
+ mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ mBackgroundControl.setAlpha(alpha);
+ }
+
+ @Override
+ public void setLayer(int zorder) {
+ super.setLayer(zorder);
+ // -3 is below all other child layers as SurfaceView never goes below -2
+ mBackgroundControl.setLayer(-3);
+ }
+
+ @Override
+ public void setPosition(float x, float y) {
+ super.setPosition(x, y);
+ mBackgroundControl.setPosition(x, y);
+ }
+
+ @Override
+ public void setSize(int w, int h) {
+ super.setSize(w, h);
+ mBackgroundControl.setSize(w, h);
+ }
+
+ @Override
+ public void setWindowCrop(Rect crop) {
+ super.setWindowCrop(crop);
+ mBackgroundControl.setWindowCrop(crop);
+ }
+
+ @Override
+ public void setFinalCrop(Rect crop) {
+ super.setFinalCrop(crop);
+ mBackgroundControl.setFinalCrop(crop);
+ }
+
+ @Override
+ public void setLayerStack(int layerStack) {
+ super.setLayerStack(layerStack);
+ mBackgroundControl.setLayerStack(layerStack);
+ }
+
+ @Override
+ public void setOpaque(boolean isOpaque) {
+ super.setOpaque(isOpaque);
+ mOpaque = isOpaque;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void setSecure(boolean isSecure) {
+ super.setSecure(isSecure);
+ }
+
+ @Override
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ }
+
+ @Override
+ public void hide() {
+ super.hide();
+ mVisible = false;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ mVisible = true;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ mBackgroundControl.destroy();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ mBackgroundControl.release();
+ }
+
+ @Override
+ public void setTransparentRegionHint(Region region) {
+ super.setTransparentRegionHint(region);
+ mBackgroundControl.setTransparentRegionHint(region);
+ }
+
+ @Override
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ super.deferTransactionUntil(handle, frame);
+ mBackgroundControl.deferTransactionUntil(handle, frame);
+ }
+
+ @Override
+ public void deferTransactionUntil(Surface barrier, long frame) {
+ super.deferTransactionUntil(barrier, frame);
+ mBackgroundControl.deferTransactionUntil(barrier, frame);
+ }
+
+ void updateBackgroundVisibility() {
+ if (mOpaque && mVisible) {
+ mBackgroundControl.show();
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+ }
+}
diff --git a/android/view/View.java b/android/view/View.java
index c043dcac..b6be2961 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -1448,59 +1448,17 @@ 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 = {
@@ -2342,9 +2300,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int PFLAG_HOVERED = 0x10000000;
/**
- * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
+ * no longer needed, should be reused
*/
- private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
+ private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000;
/** {@hide} */
static final int PFLAG_ACTIVATED = 0x40000000;
@@ -6397,42 +6355,6 @@ 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
@@ -6441,14 +6363,7 @@ 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) {
@@ -8992,21 +8907,7 @@ 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;
@@ -9024,21 +8925,7 @@ 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);
}
@@ -11546,7 +11433,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
if (isClickable()) {
- performClickInternal();
+ performClick();
return true;
}
} break;
@@ -12658,7 +12545,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!event.isCanceled()) {
- return performClickInternal();
+ return performClick();
}
}
}
@@ -13230,7 +13117,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
- performClickInternal();
+ performClick();
}
}
}
@@ -18216,21 +18103,7 @@ 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);
@@ -18243,21 +18116,7 @@ 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;
@@ -18271,11 +18130,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@SuppressWarnings({"UnusedDeclaration"})
public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
- 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) + ")");
+ 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) + ")");
if (clear) {
mPrivateFlags &= clearMask;
}
@@ -18399,21 +18257,7 @@ 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);
}
@@ -18444,21 +18288,7 @@ 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;
@@ -18478,21 +18308,7 @@ 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();
@@ -18514,21 +18330,7 @@ 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;
@@ -18540,21 +18342,7 @@ 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;
@@ -18564,21 +18352,7 @@ 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);
}
@@ -18605,21 +18379,7 @@ 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)) {
@@ -20052,7 +19812,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean changed = false;
if (DBG) {
- Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
@@ -25098,7 +24858,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private final class PerformClick implements Runnable {
@Override
public void run() {
- performClickInternal();
+ performClick();
}
}
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
index afa94131..3426485e 100644
--- a/android/view/ViewDebug.java
+++ b/android/view/ViewDebug.java
@@ -528,23 +528,84 @@ public class ViewDebug {
/** @hide */
public static void profileViewAndChildren(final View view, BufferedWriter out)
throws IOException {
- RenderNode node = RenderNode.create("ViewDebug", null);
- profileViewAndChildren(view, node, out, true);
- node.destroy();
+ profileViewAndChildren(view, out, true);
}
- private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
- boolean root) throws IOException {
+ private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
+ throws IOException {
+
long durationMeasure =
(root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
- ? profileViewMeasure(view) : 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;
long durationLayout =
(root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
- ? profileViewLayout(view) : 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;
long durationDraw =
(root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
- ? profileViewDraw(view, node) : 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
+ };
+ }
+ 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));
@@ -555,86 +616,34 @@ public class ViewDebug {
ViewGroup group = (ViewGroup) view;
final int count = group.getChildCount();
for (int i = 0; i < count; i++) {
- 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();
+ profileViewAndChildren(group.getChildAt(i), out, false);
}
}
}
- interface ViewOperation {
- default void pre() {}
-
- void run();
+ interface ViewOperation<T> {
+ T[] pre();
+ void run(T... data);
+ void post(T... data);
}
- private static long profileViewOperation(View view, final ViewOperation operation) {
+ private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
final CountDownLatch latch = new CountDownLatch(1);
final long[] duration = new long[1];
- view.post(() -> {
- try {
- operation.pre();
- long start = Debug.threadCpuTimeNanos();
- //noinspection unchecked
- operation.run();
- duration[0] = Debug.threadCpuTimeNanos() - start;
- } finally {
- latch.countDown();
+ 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();
+ }
}
});
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
index 929beaea..b2e5a163 100644
--- a/android/view/ViewGroup.java
+++ b/android/view/ViewGroup.java
@@ -421,78 +421,22 @@ 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
@@ -3825,21 +3769,7 @@ 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;
@@ -6401,21 +6331,7 @@ 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"),
@@ -6436,21 +6352,7 @@ 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 99438d87..71106ada 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -72,7 +72,6 @@ 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;
@@ -1669,6 +1668,8 @@ 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();
@@ -2826,7 +2827,7 @@ public final class ViewRootImpl implements ViewParent,
try {
mWindowDrawCountDown.await();
} catch (InterruptedException e) {
- Log.e(mTag, "Window redraw count down interrupted!");
+ Log.e(mTag, "Window redraw count down interruped!");
}
mWindowDrawCountDown = null;
}
@@ -2896,6 +2897,8 @@ 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.
@@ -3466,7 +3469,6 @@ public final class ViewRootImpl implements ViewParent,
}
void dispatchDetachedFromWindow() {
- mFirstInputStage.onDetachedFromWindow();
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
@@ -3729,273 +3731,266 @@ 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();
+ 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) {
break;
- case MSG_RESIZED: {
- // Recycled in the fall through...
+ }
+ } // fall through...
+ case MSG_RESIZED_REPORT:
+ if (mAdded) {
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) {
- break;
+
+ 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);
}
- } // 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();
-
- if (msg.what == MSG_RESIZED_REPORT) {
- reportNextDraw();
- }
+ 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;
- if (mView != null && framesChanged) {
- forceLayout(mView);
- }
- requestLayout();
+ args.recycle();
+
+ if (msg.what == MSG_RESIZED_REPORT) {
+ reportNextDraw();
}
- 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);
+
+ if (mView != null && framesChanged) {
+ forceLayout(mView);
}
- 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;
+ 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);
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);
- try {
- if (!mWindowSession.outOfMemory(mWindow)) {
- Slog.w(mTag, "No processes killed for memory;"
- + " killing self");
- Process.killProcess(Process.myPid());
- }
- } catch (RemoteException ex) {
+ if (!mWindowSession.outOfMemory(mWindow)) {
+ Slog.w(mTag, "No processes killed for memory; killing self");
+ Process.killProcess(Process.myPid());
}
- // Retry in a bit.
- sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2),
- 500);
- return;
+ } catch (RemoteException ex) {
}
+ // Retry in a bit.
+ sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
+ return;
}
}
+ }
- mLastWasImTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
+ 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);
+ 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);
- if (mAttachInfo.mTooltipHost != null) {
- mAttachInfo.mTooltipHost.hideTooltip();
- }
+ if (mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.hideTooltip();
}
+ }
- // 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 &=
+ // 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;
+ ((WindowManager.LayoutParams)mView.getLayoutParams())
+ .softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- ((WindowManager.LayoutParams) mView.getLayoutParams())
- .softInputMode &=
- ~WindowManager.LayoutParams
- .SOFT_INPUT_IS_FORWARD_NAVIGATION;
- mHasHadWindowFocus = true;
- } else {
- if (mPointerCapture) {
- handlePointerCaptureChanged(false);
- }
+ mHasHadWindowFocus = true;
+ } else {
+ if (mPointerCapture) {
+ handlePointerCaptureChanged(false);
}
}
- 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();
- }
+ }
+ } 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();
+ }
- // 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;
}
}
}
@@ -4208,18 +4203,6 @@ 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);
@@ -4973,9 +4956,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();
+ mTrackball.cancel(event);
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
- mJoystick.cancel();
+ mJoystick.cancel(event);
} else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
== InputDevice.SOURCE_TOUCH_NAVIGATION) {
mTouchNavigation.cancel(event);
@@ -4984,18 +4967,6 @@ 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();
- }
}
/**
@@ -5108,7 +5079,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- public void cancel() {
+ public void cancel(MotionEvent event) {
mLastTime = Integer.MIN_VALUE;
// If we reach this, we consumed a trackball event.
@@ -5292,11 +5263,14 @@ 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 final JoystickAxesState mJoystickAxesState = new JoystickAxesState();
- private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>();
+ private int mLastXDirection;
+ private int mLastYDirection;
+ private int mLastXKeyCode;
+ private int mLastYKeyCode;
public SyntheticJoystickHandler() {
super(true);
@@ -5307,10 +5281,11 @@ 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);
@@ -5322,176 +5297,97 @@ public final class ViewRootImpl implements ViewParent,
public void process(MotionEvent event) {
switch(event.getActionMasked()) {
- case MotionEvent.ACTION_CANCEL:
- cancel();
- break;
- case MotionEvent.ACTION_MOVE:
- update(event);
- break;
- default:
- Log.w(mTag, "Unexpected action: " + 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());
}
}
- private void cancel() {
+ private void cancel(MotionEvent event) {
removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
- 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();
- 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));
- mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
- event.getAxisValue(MotionEvent.AXIS_HAT_Y));
+ update(event, false);
}
- 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);
+ private void update(MotionEvent event, boolean synthesizeNewKeys) {
+ final long time = event.getEventTime();
+ final int metaState = event.getMetaState();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
- final int currentState;
- if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
- currentState = mAxisStatesStick[axisStateIndex];
- } else {
- currentState = mAxisStatesHat[axisStateIndex];
- }
+ int xDirection = joystickAxisValueToDirection(
+ event.getAxisValue(MotionEvent.AXIS_HAT_X));
+ if (xDirection == 0) {
+ xDirection = joystickAxisValueToDirection(event.getX());
+ }
- if (currentState == newState) {
- return;
+ int yDirection = joystickAxisValueToDirection(
+ event.getAxisValue(MotionEvent.AXIS_HAT_Y));
+ if (yDirection == 0) {
+ yDirection = joystickAxisValueToDirection(event.getY());
+ }
+
+ 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;
}
- final int metaState = event.getMetaState();
- final int deviceId = event.getDeviceId();
- final int source = event.getSource();
+ mLastXDirection = xDirection;
- 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 (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 (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;
+ 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;
}
- }
- 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;
- }
+ mLastYDirection = yDirection;
- 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 (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());
}
- 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 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;
- }
+ private int joystickAxisValueToDirection(float value) {
+ if (value >= 0.5f) {
+ return 1;
+ } else if (value <= -0.5f) {
+ return -1;
+ } else {
+ return 0;
}
}
}
@@ -6212,6 +6108,7 @@ 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) {
@@ -6232,6 +6129,7 @@ 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 309366c6..f671c349 100644
--- a/android/view/ViewStructure.java
+++ b/android/view/ViewStructure.java
@@ -365,30 +365,6 @@ public abstract class ViewStructure {
public abstract void setDataIsSensitive(boolean sensitive);
/**
- * Sets the minimum width in ems of the text associated with this view, when supported.
- *
- * <p>Should only be set when the node is used for autofill purposes - it will be ignored
- * when used for Assist.
- */
- public abstract void setMinTextEms(int minEms);
-
- /**
- * Sets the maximum width in ems of the text associated with this view, when supported.
- *
- * <p>Should only be set when the node is used for autofill purposes - it will be ignored
- * when used for Assist.
- */
- public abstract void setMaxTextEms(int maxEms);
-
- /**
- * Sets the maximum length of the text associated with this view, when supported.
- *
- * <p>Should only be set when the node is used for autofill purposes - it will be ignored
- * when used for Assist.
- */
- public abstract void setMaxTextLength(int maxLength);
-
- /**
* Call when done populating a {@link ViewStructure} returned by
* {@link #asyncNewChild}.
*/
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
index 69cc1002..98f8dc8e 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 a stack in the windowing mode is currently visible. */
- public abstract boolean isStackVisible(int windowingMode);
+ /** Returns true if the stack with the input Id is currently visible. */
+ public abstract boolean isStackVisible(int stackId);
/**
* @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 137e551d..da72535d 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -467,8 +467,11 @@ public interface WindowManagerPolicy {
*/
public boolean isDimming();
- /** @return the current windowing mode of this window. */
- int getWindowingMode();
+ /**
+ * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if
+ * not attached to any stack.
+ */
+ int getStackId();
/**
* 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 d7851171..0f21c5c8 100644
--- a/android/view/accessibility/AccessibilityCache.java
+++ b/android/view/accessibility/AccessibilityCache.java
@@ -329,6 +329,8 @@ public final class AccessibilityCache {
final long oldParentId = oldInfo.getParentNodeId();
if (info.getParentNodeId() != oldParentId) {
clearSubTreeLocked(windowId, oldParentId);
+ } else {
+ oldInfo.recycle();
}
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 11cb046a..0b9bc576 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,46 +16,152 @@
package android.view.accessibility;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
+
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.IWindow;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IntPair;
+
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
- * Such events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
+ * and provides facilities for querying the accessibility state of the system.
+ * Accessibility events are generated when something notable happens in the user interface,
* for example an {@link android.app.Activity} starts, the focus or selection of a
* {@link android.view.View} changes etc. Parties interested in handling accessibility
* events implement and register an accessibility service which extends
- * {@code android.accessibilityservice.AccessibilityService}.
+ * {@link android.accessibilityservice.AccessibilityService}.
*
* @see AccessibilityEvent
- * @see android.content.Context#getSystemService
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
*/
-@SuppressWarnings("UnusedDeclaration")
+@SystemService(Context.ACCESSIBILITY_SERVICE)
public final class AccessibilityManager {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = "AccessibilityManager";
+
+ /** @hide */
+ public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
+
+ /** @hide */
+ public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+
+ /** @hide */
+ public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
+
+ /** @hide */
+ public static final int DALTONIZER_DISABLED = -1;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
+
+ /** @hide */
+ public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
+
+ /** @hide */
+ public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+
+ /**
+ * Activity action: Launch UI to manage which accessibility service or feature is assigned
+ * to the navigation bar Accessibility button.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+ "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
+ static final Object sInstanceSync = new Object();
+
+ private static AccessibilityManager sInstance;
+
+ private final Object mLock = new Object();
+
+ private IAccessibilityManager mService;
+
+ final int mUserId;
+
+ final Handler mHandler;
+
+ final Handler.Callback mCallback;
+
+ boolean mIsEnabled;
- private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
+ int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ boolean mIsTouchExplorationEnabled;
+
+ boolean mIsHighTextContrastEnabled;
+
+ private final ArrayMap<AccessibilityStateChangeListener, Handler>
+ mAccessibilityStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<TouchExplorationStateChangeListener, Handler>
+ mTouchExplorationStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<HighTextContrastChangeListener, Handler>
+ mHighTextContrastStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
+ mServicesStateChangeListeners = new ArrayMap<>();
+
+ /**
+ * Map from a view's accessibility id to the list of request preparers set for that view
+ */
+ private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
/**
- * Listener for the accessibility state.
+ * Listener for the system accessibility state. To listen for changes to the
+ * accessibility state on the device, implement this interface and register
+ * it with the system by calling {@link #addAccessibilityStateChangeListener}.
*/
public interface AccessibilityStateChangeListener {
/**
- * Called back on change in the accessibility state.
+ * Called when the accessibility enabled state changes.
*
* @param enabled Whether accessibility is enabled.
*/
- public void onAccessibilityStateChanged(boolean enabled);
+ void onAccessibilityStateChanged(boolean enabled);
}
/**
@@ -71,7 +177,24 @@ public final class AccessibilityManager {
*
* @param enabled Whether touch exploration is enabled.
*/
- public void onTouchExplorationStateChanged(boolean enabled);
+ void onTouchExplorationStateChanged(boolean enabled);
+ }
+
+ /**
+ * Listener for changes to the state of accessibility services. Changes include services being
+ * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
+ * {@see #addAccessibilityServicesStateChangeListener}.
+ *
+ * @hide
+ */
+ public interface AccessibilityServicesStateChangeListener {
+
+ /**
+ * Called when the state of accessibility services changes.
+ *
+ * @param manager The manager that is calling back
+ */
+ void onAccessibilityServicesStateChanged(AccessibilityManager manager);
}
/**
@@ -79,6 +202,8 @@ public final class AccessibilityManager {
* the high text contrast state on the device, implement this interface and
* register it with the system by calling
* {@link #addHighTextContrastStateChangeListener}.
+ *
+ * @hide
*/
public interface HighTextContrastChangeListener {
@@ -87,26 +212,72 @@ public final class AccessibilityManager {
*
* @param enabled Whether high text contrast is enabled.
*/
- public void onHighTextContrastStateChanged(boolean enabled);
+ void onHighTextContrastStateChanged(boolean enabled);
}
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
- public void setState(int state) {
- }
+ @Override
+ public void setState(int state) {
+ // We do not want to change this immediately as the application may
+ // have already checked that accessibility is on and fired an event,
+ // that is now propagating up the view tree, Hence, if accessibility
+ // is now off an exception will be thrown. We want to have the exception
+ // enforcement to guard against apps that fire unnecessary accessibility
+ // events when accessibility is off.
+ mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
+ }
- public void notifyServicesStateChanged() {
+ @Override
+ public void notifyServicesStateChanged() {
+ final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mServicesStateChangeListeners.isEmpty()) {
+ return;
}
+ listeners = new ArrayMap<>(mServicesStateChangeListeners);
+ }
- public void setRelevantEventTypes(int eventTypes) {
- }
- };
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final AccessibilityServicesStateChangeListener listener =
+ mServicesStateChangeListeners.keyAt(i);
+ mServicesStateChangeListeners.valueAt(i).post(() -> listener
+ .onAccessibilityServicesStateChanged(AccessibilityManager.this));
+ }
+ }
+
+ @Override
+ public void setRelevantEventTypes(int eventTypes) {
+ mRelevantEventTypes = eventTypes;
+ }
+ };
/**
* Get an AccessibilityManager instance (create one if necessary).
*
+ * @param context Context in which this manager operates.
+ *
+ * @hide
*/
public static AccessibilityManager getInstance(Context context) {
+ synchronized (sInstanceSync) {
+ if (sInstance == null) {
+ final int userId;
+ if (Binder.getCallingUid() == Process.SYSTEM_UID
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS)
+ == PackageManager.PERMISSION_GRANTED
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ userId = UserHandle.USER_CURRENT;
+ } else {
+ userId = UserHandle.myUserId();
+ }
+ sInstance = new AccessibilityManager(context, null, userId);
+ }
+ }
return sInstance;
}
@@ -114,21 +285,68 @@ public final class AccessibilityManager {
* Create an instance.
*
* @param context A {@link Context}.
+ * @param service An interface to the backing service.
+ * @param userId User id under which to run.
+ *
+ * @hide
*/
public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
+ // Constructor can't be chained because we can't create an instance of an inner class
+ // before calling another constructor.
+ mCallback = new MyCallback();
+ mHandler = new Handler(context.getMainLooper(), mCallback);
+ mUserId = userId;
+ synchronized (mLock) {
+ tryConnectToServiceLocked(service);
+ }
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param handler The handler to use
+ * @param service An interface to the backing service.
+ * @param userId User id under which to run.
+ *
+ * @hide
+ */
+ public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+ mCallback = new MyCallback();
+ mHandler = handler;
+ mUserId = userId;
+ synchronized (mLock) {
+ tryConnectToServiceLocked(service);
+ }
}
+ /**
+ * @hide
+ */
public IAccessibilityManagerClient getClient() {
return mClient;
}
/**
- * Returns if the {@link AccessibilityManager} is enabled.
+ * @hide
+ */
+ @VisibleForTesting
+ public Handler.Callback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Returns if the accessibility in the system is enabled.
*
- * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+ * @return True if accessibility is enabled, false otherwise.
*/
public boolean isEnabled() {
- return false;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsEnabled;
+ }
}
/**
@@ -137,7 +355,13 @@ public final class AccessibilityManager {
* @return True if touch exploration is enabled, false otherwise.
*/
public boolean isTouchExplorationEnabled() {
- return true;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsTouchExplorationEnabled;
+ }
}
/**
@@ -147,35 +371,169 @@ public final class AccessibilityManager {
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
+ * @return True if high text contrast is enabled, false otherwise.
+ *
+ * @hide
*/
public boolean isHighTextContrastEnabled() {
- return false;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsHighTextContrastEnabled;
+ }
}
/**
* Sends an {@link AccessibilityEvent}.
+ *
+ * @param event The event to send.
+ *
+ * @throws IllegalStateException if accessibility is not enabled.
+ *
+ * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
+ * events is through calling
+ * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+ * instead of this method to allow predecessors to augment/filter events sent by
+ * their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ Looper myLooper = Looper.myLooper();
+ if (myLooper == Looper.getMainLooper()) {
+ throw new IllegalStateException(
+ "Accessibility off. Did you forget to check that?");
+ } else {
+ // If we're not running on the thread with the main looper, it's possible for
+ // the state of accessibility to change between checking isEnabled and
+ // calling this method. So just log the error rather than throwing the
+ // exception.
+ Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
+ return;
+ }
+ }
+ if ((event.getEventType() & mRelevantEventTypes) == 0) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
+ + " that is not among "
+ + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
+ }
+ return;
+ }
+ userId = mUserId;
+ }
+ try {
+ event.setEventTime(SystemClock.uptimeMillis());
+ // it is possible that this manager is in the same process as the service but
+ // client using it is called through Binder from another process. Example: MMS
+ // app adds a SMS notification and the NotificationManagerService calls this method
+ long identityToken = Binder.clearCallingIdentity();
+ service.sendAccessibilityEvent(event, userId);
+ Binder.restoreCallingIdentity(identityToken);
+ if (DEBUG) {
+ Log.i(LOG_TAG, event + " sent");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error during sending " + event + " ", re);
+ } finally {
+ event.recycle();
+ }
}
/**
- * Requests interruption of the accessibility feedback from all accessibility services.
+ * Requests feedback interruption from all accessibility services.
*/
public void interrupt() {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ Looper myLooper = Looper.myLooper();
+ if (myLooper == Looper.getMainLooper()) {
+ throw new IllegalStateException(
+ "Accessibility off. Did you forget to check that?");
+ } else {
+ // If we're not running on the thread with the main looper, it's possible for
+ // the state of accessibility to change between checking isEnabled and
+ // calling this method. So just log the error rather than throwing the
+ // exception.
+ Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
+ return;
+ }
+ }
+ userId = mUserId;
+ }
+ try {
+ service.interrupt(userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requested interrupt from all services");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
+ }
}
/**
* Returns the {@link ServiceInfo}s of the installed accessibility services.
*
* @return An unmodifiable list with {@link ServiceInfo}s.
+ *
+ * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
*/
@Deprecated
public List<ServiceInfo> getAccessibilityServiceList() {
- return Collections.emptyList();
+ List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
+ List<ServiceInfo> services = new ArrayList<>();
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityServiceInfo info = infos.get(i);
+ services.add(info.getResolveInfo().serviceInfo);
+ }
+ return Collections.unmodifiableList(services);
}
+ /**
+ * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ */
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
- return Collections.emptyList();
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
+ List<AccessibilityServiceInfo> services = null;
+ try {
+ services = service.getInstalledAccessibilityServiceList(userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
}
/**
@@ -190,21 +548,48 @@ public final class AccessibilityManager {
* @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
* @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
* @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+ * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
*/
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
int feedbackTypeFlags) {
- return Collections.emptyList();
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
+ List<AccessibilityServiceInfo> services = null;
+ try {
+ services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
}
/**
* Registers an {@link AccessibilityStateChangeListener} for changes in
- * the global accessibility state of the system.
+ * the global accessibility state of the system. Equivalent to calling
+ * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+ * with a null handler.
*
* @param listener The listener.
- * @return True if successfully registered.
+ * @return Always returns {@code true}.
*/
public boolean addAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
+ @NonNull AccessibilityStateChangeListener listener) {
+ addAccessibilityStateChangeListener(listener, null);
return true;
}
@@ -218,22 +603,40 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
+ @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mAccessibilityStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+ /**
+ * Unregisters an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if the listener was previously registered.
+ */
public boolean removeAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
- return true;
+ @NonNull AccessibilityStateChangeListener listener) {
+ synchronized (mLock) {
+ int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+ mAccessibilityStateChangeListeners.remove(listener);
+ return (index >= 0);
+ }
}
/**
* Registers a {@link TouchExplorationStateChangeListener} for changes in
- * the global touch exploration state of the system.
+ * the global touch exploration state of the system. Equivalent to calling
+ * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+ * with a null handler.
*
* @param listener The listener.
- * @return True if successfully registered.
+ * @return Always returns {@code true}.
*/
public boolean addTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
+ addTouchExplorationStateChangeListener(listener, null);
return true;
}
@@ -247,17 +650,103 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
+ @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mTouchExplorationStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
/**
* Unregisters a {@link TouchExplorationStateChangeListener}.
*
* @param listener The listener.
- * @return True if successfully unregistered.
+ * @return True if listener was previously registered.
*/
public boolean removeTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- return true;
+ synchronized (mLock) {
+ int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
+ mTouchExplorationStateChangeListeners.remove(listener);
+ return (index >= 0);
+ }
+ }
+
+ /**
+ * Registers a {@link AccessibilityServicesStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @param handler The handler on which the listener should be called back, or {@code null}
+ * for a callback on the process's main handler.
+ * @hide
+ */
+ public void addAccessibilityServicesStateChangeListener(
+ @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mServicesStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+
+ /**
+ * Unregisters a {@link AccessibilityServicesStateChangeListener}.
+ *
+ * @param listener The listener.
+ *
+ * @hide
+ */
+ public void removeAccessibilityServicesStateChangeListener(
+ @NonNull AccessibilityServicesStateChangeListener listener) {
+ // Final CopyOnWriteArrayList - no lock needed.
+ mServicesStateChangeListeners.remove(listener);
+ }
+
+ /**
+ * Registers a {@link AccessibilityRequestPreparer}.
+ */
+ public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ mRequestPreparerLists = new SparseArray<>(1);
+ }
+ int id = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
+ if (requestPreparerList == null) {
+ requestPreparerList = new ArrayList<>(1);
+ mRequestPreparerLists.put(id, requestPreparerList);
+ }
+ requestPreparerList.add(preparer);
+ }
+
+ /**
+ * Unregisters a {@link AccessibilityRequestPreparer}.
+ */
+ public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ return;
+ }
+ int viewId = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
+ if (requestPreparerList != null) {
+ requestPreparerList.remove(preparer);
+ if (requestPreparerList.isEmpty()) {
+ mRequestPreparerLists.remove(viewId);
+ }
+ }
+ }
+
+ /**
+ * Get the preparers that are registered for an accessibility ID
+ *
+ * @param id The ID of interest
+ * @return The list of preparers, or {@code null} if there are none.
+ *
+ * @hide
+ */
+ public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
+ if (mRequestPreparerLists == null) {
+ return null;
+ }
+ return mRequestPreparerLists.get(id);
}
/**
@@ -269,7 +758,12 @@ public final class AccessibilityManager {
* @hide
*/
public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
+ @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mHighTextContrastStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
/**
* Unregisters a {@link HighTextContrastChangeListener}.
@@ -279,7 +773,51 @@ public final class AccessibilityManager {
* @hide
*/
public void removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {}
+ @NonNull HighTextContrastChangeListener listener) {
+ synchronized (mLock) {
+ mHighTextContrastStateChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Check if the accessibility volume stream is active.
+ *
+ * @return True if accessibility volume is active (i.e. some service has requested it). False
+ * otherwise.
+ * @hide
+ */
+ public boolean isAccessibilityVolumeStreamActive() {
+ List<AccessibilityServiceInfo> serviceInfos =
+ getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ for (int i = 0; i < serviceInfos.size(); i++) {
+ if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Report a fingerprint gesture to accessibility. Only available for the system process.
+ *
+ * @param keyCode The key code of the gesture
+ * @return {@code true} if accessibility consumes the event. {@code false} if not.
+ * @hide
+ */
+ public boolean sendFingerprintGesture(int keyCode) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+ try {
+ return service.sendFingerprintGesture(keyCode);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
/**
* Sets the current state and notifies listeners, if necessary.
@@ -287,14 +825,314 @@ public final class AccessibilityManager {
* @param stateFlags The state flags.
*/
private void setStateLocked(int stateFlags) {
+ final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
+ final boolean touchExplorationEnabled =
+ (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
+ final boolean highTextContrastEnabled =
+ (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
+
+ final boolean wasEnabled = mIsEnabled;
+ final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
+ final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+
+ // Ensure listeners get current state from isZzzEnabled() calls.
+ mIsEnabled = enabled;
+ mIsTouchExplorationEnabled = touchExplorationEnabled;
+ mIsHighTextContrastEnabled = highTextContrastEnabled;
+
+ if (wasEnabled != enabled) {
+ notifyAccessibilityStateChanged();
+ }
+
+ if (wasTouchExplorationEnabled != touchExplorationEnabled) {
+ notifyTouchExplorationStateChanged();
+ }
+
+ if (wasHighTextContrastEnabled != highTextContrastEnabled) {
+ notifyHighTextContrastStateChanged();
+ }
}
+ /**
+ * Find an installed service with the specified {@link ComponentName}.
+ *
+ * @param componentName The name to match to the service.
+ *
+ * @return The info corresponding to the installed service, or {@code null} if no such service
+ * is installed.
+ * @hide
+ */
+ public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
+ ComponentName componentName) {
+ final List<AccessibilityServiceInfo> installedServiceInfos =
+ getInstalledAccessibilityServiceList();
+ if ((installedServiceInfos == null) || (componentName == null)) {
+ return null;
+ }
+ for (int i = 0; i < installedServiceInfos.size(); i++) {
+ if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
+ return installedServiceInfos.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is added.
+ * @param connection The connection.
+ *
+ * @hide
+ */
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return View.NO_ID;
+ }
+ userId = mUserId;
+ }
+ try {
+ return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
+ }
return View.NO_ID;
}
+ /**
+ * Removed an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is removed.
+ *
+ * @hide
+ */
public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.removeAccessibilityInteractionConnection(windowToken);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
+ }
+ }
+
+ /**
+ * Perform the accessibility shortcut if the caller has permission.
+ *
+ * @hide
+ */
+ public void performAccessibilityShortcut() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.performAccessibilityShortcut();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
+ }
+ }
+
+ /**
+ * Notifies that the accessibility button in the system's navigation area has been clicked
+ *
+ * @hide
+ */
+ public void notifyAccessibilityButtonClicked() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonClicked();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
+ }
+ }
+
+ /**
+ * Notifies that the visibility of the accessibility button in the system's navigation area
+ * has changed.
+ *
+ * @param shown {@code true} if the accessibility button is visible within the system
+ * navigation area, {@code false} otherwise
+ * @hide
+ */
+ public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonVisibilityChanged(shown);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
+ }
+ }
+
+ /**
+ * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
+ * window. Intended for use by the System UI only.
+ *
+ * @param connection The connection to handle the actions. Set to {@code null} to avoid
+ * affecting the actions.
+ *
+ * @hide
+ */
+ public void setPictureInPictureActionReplacingConnection(
+ @Nullable IAccessibilityInteractionConnection connection) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.setPictureInPictureActionReplacingConnection(connection);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
+ }
}
+ private IAccessibilityManager getServiceLocked() {
+ if (mService == null) {
+ tryConnectToServiceLocked(null);
+ }
+ return mService;
+ }
+
+ private void tryConnectToServiceLocked(IAccessibilityManager service) {
+ if (service == null) {
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ if (iBinder == null) {
+ return;
+ }
+ service = IAccessibilityManager.Stub.asInterface(iBinder);
+ }
+
+ try {
+ final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
+ setStateLocked(IntPair.first(userStateAndRelevantEvents));
+ mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
+ mService = service;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+ }
+ }
+
+ /**
+ * Notifies the registered {@link AccessibilityStateChangeListener}s.
+ */
+ private void notifyAccessibilityStateChanged() {
+ final boolean isEnabled;
+ final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mAccessibilityStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isEnabled = mIsEnabled;
+ listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final AccessibilityStateChangeListener listener =
+ mAccessibilityStateChangeListeners.keyAt(i);
+ mAccessibilityStateChangeListeners.valueAt(i)
+ .post(() -> listener.onAccessibilityStateChanged(isEnabled));
+ }
+ }
+
+ /**
+ * Notifies the registered {@link TouchExplorationStateChangeListener}s.
+ */
+ private void notifyTouchExplorationStateChanged() {
+ final boolean isTouchExplorationEnabled;
+ final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mTouchExplorationStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isTouchExplorationEnabled = mIsTouchExplorationEnabled;
+ listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final TouchExplorationStateChangeListener listener =
+ mTouchExplorationStateChangeListeners.keyAt(i);
+ mTouchExplorationStateChangeListeners.valueAt(i)
+ .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
+ }
+ }
+
+ /**
+ * Notifies the registered {@link HighTextContrastChangeListener}s.
+ */
+ private void notifyHighTextContrastStateChanged() {
+ final boolean isHighTextContrastEnabled;
+ final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mHighTextContrastStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isHighTextContrastEnabled = mIsHighTextContrastEnabled;
+ listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final HighTextContrastChangeListener listener =
+ mHighTextContrastStateChangeListeners.keyAt(i);
+ mHighTextContrastStateChangeListeners.valueAt(i)
+ .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
+ }
+ }
+
+ /**
+ * Determines if the accessibility button within the system navigation area is supported.
+ *
+ * @return {@code true} if the accessibility button is supported on this device,
+ * {@code false} otherwise
+ */
+ public static boolean isAccessibilityButtonSupported() {
+ final Resources res = Resources.getSystem();
+ return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+ }
+
+ private final class MyCallback implements Handler.Callback {
+ public static final int MSG_SET_STATE = 1;
+
+ @Override
+ public boolean handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_SET_STATE: {
+ // See comment at mClient
+ final int state = message.arg1;
+ synchronized (mLock) {
+ setStateLocked(state);
+ }
+ } break;
+ }
+ return true;
+ }
+ }
}
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index e564fa34..4fb2a99a 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 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
+ * 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
* {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
- * selects a dataset from the UI, all views present in the dataset are autofilled, through
+ * selects a dataset from the affordance, 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 an autofill save UI if the value of savable views have changed. If the user selects the
+ * shows a save UI affordance 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,12 +150,6 @@ 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}
*/
@@ -317,14 +311,6 @@ 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 {
/**
@@ -848,46 +834,6 @@ 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.
*
@@ -904,15 +850,12 @@ public final class AutofillManager {
return;
}
synchronized (mLock) {
- commitLocked();
- }
- }
+ if (!mEnabled && !isActiveLocked()) {
+ return;
+ }
- private void commitLocked() {
- if (!mEnabled && !isActiveLocked()) {
- return;
+ finishSessionLocked();
}
- finishSessionLocked();
}
/**
@@ -931,15 +874,12 @@ public final class AutofillManager {
return;
}
synchronized (mLock) {
- cancelLocked();
- }
- }
+ if (!mEnabled && !isActiveLocked()) {
+ return;
+ }
- private void cancelLocked() {
- if (!mEnabled && !isActiveLocked()) {
- return;
+ cancelSessionLocked();
}
- cancelSessionLocked();
}
/** @hide */
@@ -997,12 +937,7 @@ public final class AutofillManager {
}
private AutofillClient getClientLocked() {
- final AutofillClient client = mContext.getAutofillClient();
- if (client == null && sDebug) {
- Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
- + mContext);
- }
- return client;
+ return mContext.getAutofillClient();
}
/** @hide */
@@ -1024,10 +959,6 @@ 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());
@@ -1107,7 +1038,6 @@ public final class AutofillManager {
mState = STATE_UNKNOWN;
mTrackedViews = null;
mFillableIds = null;
- mSaveTriggerId = null;
}
private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
@@ -1359,15 +1289,12 @@ 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, boolean saveOnFinish,
- @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
+ boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
synchronized (mLock) {
if (mEnabled && mSessionId == sessionId) {
if (saveOnAllViewsInvisible) {
@@ -1375,7 +1302,6 @@ public final class AutofillManager {
} else {
mTrackedViews = null;
}
- mSaveOnFinish = saveOnFinish;
if (fillableIds != null) {
if (mFillableIds == null) {
mFillableIds = new ArraySet<>(fillableIds.length);
@@ -1388,30 +1314,10 @@ 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) {
@@ -1584,7 +1490,6 @@ 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);
@@ -1599,8 +1504,6 @@ 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() {
@@ -1849,7 +1752,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 is shown / hidden.
+ * enable / disable such views when the autofill UI affordance is shown / hidden.
*/
public abstract static class AutofillCallback {
@@ -1859,26 +1762,26 @@ public final class AutofillManager {
public @interface AutofillEventType {}
/**
- * The autofill input UI associated with the view was shown.
+ * The autofill input UI affordance associated with the view was shown.
*
- * <p>If the view provides its own auto-complete UI and its currently shown, it
+ * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
* should be hidden upon receiving this event.
*/
public static final int EVENT_INPUT_SHOWN = 1;
/**
- * The autofill input UI associated with the view was hidden.
+ * The autofill input UI affordance associated with the view was hidden.
*
- * <p>If the view provides its own auto-complete UI that was hidden upon a
+ * <p>If the view provides its own auto-complete UI affordance 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 associated with the view isn't shown because
+ * The autofill input UI affordance associated with the view isn't shown because
* autofill is not available.
*
- * <p>If the view provides its own auto-complete UI but was not displaying it
+ * <p>If the view provides its own auto-complete UI affordance but was not displaying it
* to avoid flickering, it could shown it upon receiving this event.
*/
public static final int EVENT_INPUT_UNAVAILABLE = 3;
@@ -1980,12 +1883,12 @@ public final class AutofillManager {
@Override
public void setTrackedViews(int sessionId, AutofillId[] ids,
- boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
- AutofillId saveTriggerId) {
+ boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
- saveOnFinish, fillableIds, saveTriggerId));
+ afm.post(() ->
+ afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
+ );
}
}
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index c3601d9d..bb1e693f 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -152,12 +152,4 @@ 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
deleted file mode 100644
index 51e6168e..00000000
--- a/android/view/textclassifier/TextClassifierConstants.java
+++ /dev/null
@@ -1,90 +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.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 ef087472..2aa81a2c 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -24,12 +24,12 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
+import android.icu.text.BreakIterator;
import android.net.Uri;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.ContactsContract;
-import android.provider.Settings;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.method.WordIterator;
@@ -47,7 +47,6 @@ 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;
@@ -92,8 +91,6 @@ 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);
}
@@ -192,15 +189,6 @@ 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 8e1f2183..f368c74a 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,58 +1,213 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
*/
package android.view.textservice;
+import android.annotation.SystemService;
+import android.content.Context;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+
import java.util.Locale;
/**
- * A stub class of TextServicesManager for Layout-Lib.
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ *
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the text services
+ * framework (TSF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>text services manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts. It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> A <strong>text service</strong> implements a particular
+ * interaction model allowing the client application to retrieve information of text.
+ * The system binds to the current text service that is in use, causing it to be created and run.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the text service
+ * manager for connections to text services.
+ * </ul>
+ *
+ * <h3>Text services sessions</h3>
+ * <ul>
+ * <li>The <strong>spell checker session</strong> is one of the text services.
+ * {@link android.view.textservice.SpellCheckerSession}</li>
+ * </ul>
+ *
*/
+@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
public final class TextServicesManager {
- private static final TextServicesManager sInstance = new TextServicesManager();
- private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
+ private static final String TAG = TextServicesManager.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ private static TextServicesManager sInstance;
+
+ private final ITextServicesManager mService;
+
+ private TextServicesManager() throws ServiceNotFoundException {
+ mService = ITextServicesManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
+ }
/**
* Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
* @hide
*/
public static TextServicesManager getInstance() {
- return sInstance;
+ synchronized (TextServicesManager.class) {
+ if (sInstance == null) {
+ try {
+ sInstance = new TextServicesManager();
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Returns the language component of a given locale string.
+ */
+ private static String parseLanguageFromLocaleString(String locale) {
+ final int idx = locale.indexOf('_');
+ if (idx < 0) {
+ return locale;
+ } else {
+ return locale.substring(0, idx);
+ }
}
+ /**
+ * Get a spell checker session for the specified spell checker
+ * @param locale the locale for the spell checker. If {@code locale} is null and
+ * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+ * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
+ * the locale specified in Settings will be returned only when it is same as {@code locale}.
+ * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
+ * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+ * selected.
+ * @param listener a spell checker session lister for getting results from a spell checker.
+ * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
+ * languages in settings will be returned.
+ * @return the spell checker session of the spell checker
+ */
public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
- return null;
+ if (listener == null) {
+ throw new NullPointerException();
+ }
+ if (!referToSpellCheckerLanguageSettings && locale == null) {
+ throw new IllegalArgumentException("Locale should not be null if you don't refer"
+ + " settings.");
+ }
+
+ if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+ return null;
+ }
+
+ final SpellCheckerInfo sci;
+ try {
+ sci = mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ return null;
+ }
+ if (sci == null) {
+ return null;
+ }
+ SpellCheckerSubtype subtypeInUse = null;
+ if (referToSpellCheckerLanguageSettings) {
+ subtypeInUse = getCurrentSpellCheckerSubtype(true);
+ if (subtypeInUse == null) {
+ return null;
+ }
+ if (locale != null) {
+ final String subtypeLocale = subtypeInUse.getLocale();
+ final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+ if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
+ return null;
+ }
+ }
+ } else {
+ final String localeStr = locale.toString();
+ for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+ final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+ final String tempSubtypeLocale = subtype.getLocale();
+ final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
+ if (tempSubtypeLocale.equals(localeStr)) {
+ subtypeInUse = subtype;
+ break;
+ } else if (tempSubtypeLanguage.length() >= 2 &&
+ locale.getLanguage().equals(tempSubtypeLanguage)) {
+ subtypeInUse = subtype;
+ }
+ }
+ }
+ if (subtypeInUse == null) {
+ return null;
+ }
+ final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
+ try {
+ mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
+ session.getTextServicesSessionListener(),
+ session.getSpellCheckerSessionListener(), bundle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return session;
}
/**
* @hide
*/
public SpellCheckerInfo[] getEnabledSpellCheckers() {
- return EMPTY_SPELL_CHECKER_INFO;
+ try {
+ final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
+ if (DBG) {
+ Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
+ }
+ return retval;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* @hide
*/
public SpellCheckerInfo getCurrentSpellChecker() {
- return null;
+ try {
+ // Passing null as a locale for ICS
+ return mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -60,13 +215,22 @@ public final class TextServicesManager {
*/
public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
boolean allowImplicitlySelectedSubtype) {
- return null;
+ try {
+ // Passing null as a locale until we support multiple enabled spell checker subtypes.
+ return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* @hide
*/
public boolean isSpellCheckerEnabled() {
- return false;
+ try {
+ return mService.isSpellCheckerEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 202f2046..dfc81b2b 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,223 +16,3001 @@
package android.webkit;
-import com.android.layoutlib.bridge.MockView;
-
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.Widget;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.http.SslCertificate;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.print.PrintDocumentAdapter;
+import android.security.KeyChain;
import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.DragEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
+import android.view.ViewStructure;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.textclassifier.TextClassifier;
+import android.widget.AbsoluteLayout;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
/**
- * Mock version of the WebView.
- * Only non override public methods from the real WebView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
- *
- * TODO: generate automatically.
+ * <p>A View that displays web pages. This class is the basis upon which you
+ * can roll your own web browser or simply display some online content within your Activity.
+ * It uses the WebKit rendering engine to display
+ * web pages and includes methods to navigate forward and backward
+ * through a history, zoom in and out, perform text searches and more.
+ *
+ * <p>Note that, in order for your Activity to access the Internet and load web pages
+ * in a WebView, you must add the {@code INTERNET} permissions to your
+ * Android Manifest file:
+ *
+ * <pre>
+ * {@code <uses-permission android:name="android.permission.INTERNET" />}
+ * </pre>
+ *
+ * <p>This must be a child of the <a
+ * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
+ * element.
+ *
+ * <p>For more information, read
+ * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>.
+ *
+ * <h3>Basic usage</h3>
+ *
+ * <p>By default, a WebView provides no browser-like widgets, does not
+ * enable JavaScript and web page errors are ignored. If your goal is only
+ * to display some HTML as a part of your UI, this is probably fine;
+ * the user won't need to interact with the web page beyond reading
+ * it, and the web page won't need to interact with the user. If you
+ * actually want a full-blown web browser, then you probably want to
+ * invoke the Browser application with a URL Intent rather than show it
+ * with a WebView. For example:
+ * <pre>
+ * Uri uri = Uri.parse("https://www.example.com");
+ * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ * startActivity(intent);
+ * </pre>
+ * <p>See {@link android.content.Intent} for more information.
+ *
+ * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
+ * or set the entire Activity window as a WebView during {@link
+ * android.app.Activity#onCreate(Bundle) onCreate()}:
+ *
+ * <pre class="prettyprint">
+ * WebView webview = new WebView(this);
+ * setContentView(webview);
+ * </pre>
+ *
+ * <p>Then load the desired web page:
+ *
+ * <pre>
+ * // Simplest usage: note that an exception will NOT be thrown
+ * // if there is an error loading this page (see below).
+ * webview.loadUrl("https://example.com/");
+ *
+ * // OR, you can also load from an HTML string:
+ * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
+ * webview.loadData(summary, "text/html", null);
+ * // ... although note that there are restrictions on what this HTML can do.
+ * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
+ * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
+ * </pre>
+ *
+ * <p>A WebView has several customization points where you can add your
+ * own behavior. These are:
+ *
+ * <ul>
+ * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
+ * This class is called when something that might impact a
+ * browser UI happens, for instance, progress updates and
+ * JavaScript alerts are sent here (see <a
+ * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
+ * </li>
+ * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
+ * It will be called when things happen that impact the
+ * rendering of the content, eg, errors or form submissions. You
+ * can also intercept URL loading here (via {@link
+ * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
+ * shouldOverrideUrlLoading()}).</li>
+ * <li>Modifying the {@link android.webkit.WebSettings}, such as
+ * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
+ * setJavaScriptEnabled()}. </li>
+ * <li>Injecting Java objects into the WebView using the
+ * {@link android.webkit.WebView#addJavascriptInterface} method. This
+ * method allows you to inject Java objects into a page's JavaScript
+ * context, so that they can be accessed by JavaScript in the page.</li>
+ * </ul>
+ *
+ * <p>Here's a more complicated example, showing error handling,
+ * settings, and progress notification:
+ *
+ * <pre class="prettyprint">
+ * // Let's display the progress in the activity title bar, like the
+ * // browser app does.
+ * getWindow().requestFeature(Window.FEATURE_PROGRESS);
+ *
+ * webview.getSettings().setJavaScriptEnabled(true);
+ *
+ * final Activity activity = this;
+ * webview.setWebChromeClient(new WebChromeClient() {
+ * public void onProgressChanged(WebView view, int progress) {
+ * // Activities and WebViews measure progress with different scales.
+ * // The progress meter will automatically disappear when we reach 100%
+ * activity.setProgress(progress * 1000);
+ * }
+ * });
+ * webview.setWebViewClient(new WebViewClient() {
+ * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
+ * }
+ * });
+ *
+ * webview.loadUrl("https://developer.android.com/");
+ * </pre>
+ *
+ * <h3>Zoom</h3>
+ *
+ * <p>To enable the built-in zoom, set
+ * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
+ * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
+ *
+ * <p>NOTE: Using zoom if either the height or width is set to
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
+ * and should be avoided.
+ *
+ * <h3>Cookie and window management</h3>
+ *
+ * <p>For obvious security reasons, your application has its own
+ * cache, cookie store etc.&mdash;it does not share the Browser
+ * application's data.
+ *
+ * <p>By default, requests by the HTML to open new windows are
+ * ignored. This is {@code true} whether they be opened by JavaScript or by
+ * the target attribute on a link. You can customize your
+ * {@link WebChromeClient} to provide your own behavior for opening multiple windows,
+ * and render them in whatever manner you want.
+ *
+ * <p>The standard behavior for an Activity is to be destroyed and
+ * recreated when the device orientation or any other configuration changes. This will cause
+ * the WebView to reload the current page. If you don't want that, you
+ * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
+ * changes, and then just leave the WebView alone. It'll automatically
+ * re-orient itself as appropriate. Read <a
+ * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
+ * more information about how to handle configuration changes during runtime.
+ *
+ *
+ * <h3>Building web pages to support different screen densities</h3>
+ *
+ * <p>The screen density of a device is based on the screen resolution. A screen with low density
+ * has fewer available pixels per inch, where a screen with high density
+ * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
+ * screen is important because, other things being equal, a UI element (such as a button) whose
+ * height and width are defined in terms of screen pixels will appear larger on the lower density
+ * screen and smaller on the higher density screen.
+ * For simplicity, Android collapses all actual screen densities into three generalized densities:
+ * high, medium, and low.
+ * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
+ * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
+ * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
+ * are bigger).
+ * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS,
+ * and meta tag features to help you (as a web developer) target screens with different screen
+ * densities.
+ * <p>Here's a summary of the features you can use to handle different screen densities:
+ * <ul>
+ * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
+ * default scaling factor used for the current device. For example, if the value of {@code
+ * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
+ * and default scaling is not applied to the web page; if the value is "1.5", then the device is
+ * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
+ * value is "0.75", then the device is considered a low density device (ldpi) and the content is
+ * scaled 0.75x.</li>
+ * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
+ * densities for which this style sheet is to be used. The corresponding value should be either
+ * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
+ * density, or high density screens, respectively. For example:
+ * <pre>
+ * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
+ * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
+ * which is the high density pixel ratio.
+ * </li>
+ * </ul>
+ *
+ * <h3>HTML5 Video support</h3>
+ *
+ * <p>In order to support inline HTML5 video in your application you need to have hardware
+ * acceleration turned on.
+ *
+ * <h3>Full screen support</h3>
+ *
+ * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
+ * {@link android.webkit.WebChromeClient} and implement both
+ * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
+ * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
+ * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
+ * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
+ * is loading.
+ *
+ * <h3>HTML5 Geolocation API support</h3>
+ *
+ * <p>For applications targeting Android N and later releases
+ * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on
+ * secure origins such as https. For such applications requests to geolocation api on non-secure
+ * origins are automatically denied without invoking the corresponding
+ * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
+ * method.
+ *
+ * <h3>Layout size</h3>
+ * <p>
+ * It is recommended to set the WebView layout height to a fixed value or to
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
+ * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+ * for the height none of the WebView's parents should use a
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
+ * incorrect sizing of the views.
+ *
+ * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * enables the following behaviors:
+ * <ul>
+ * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
+ * relative to the HTML body may not be sized correctly. </li>
+ * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
+ * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
+ * </ul>
+ *
+ * <p>
+ * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
+ * supported. If such a width is used the WebView will attempt to use the width of the parent
+ * instead.
+ *
+ * <h3>Metrics</h3>
+ *
+ * <p>
+ * WebView may upload anonymous diagnostic data to Google when the user has consented. This data
+ * helps Google improve WebView. Data is collected on a per-app basis for each app which has
+ * instantiated a WebView. An individual app can opt out of this feature by putting the following
+ * tag in its manifest:
+ * <pre>
+ * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
+ * android:value="true" /&gt;
+ * </pre>
+ * <p>
+ * Data will only be uploaded for a given app if the user has consented AND the app has not opted
+ * out.
+ *
+ * <h3>Safe Browsing</h3>
+ *
+ * <p>
+ * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
+ * user to allow them to navigate back safely or proceed to the malicious page.
+ * <p>
+ * The recommended way for apps to enable the feature is putting the following tag in the manifest:
+ * <p>
+ * <pre>
+ * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
+ * android:value="true" /&gt;
+ * </pre>
*
*/
-public class WebView extends MockView {
+// Implementation notes.
+// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
+// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
+// Methods are delegated to the provider implementation: all public API methods introduced in this
+// file are fully delegated, whereas public and protected methods from the View base classes are
+// only delegated where a specific need exists for them to do so.
+@Widget
+public class WebView extends AbsoluteLayout
+ implements ViewTreeObserver.OnGlobalFocusChangeListener,
+ ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
+
+ private static final String LOGTAG = "WebView";
+
+ // Throwing an exception for incorrect thread usage if the
+ // build target is JB MR2 or newer. Defaults to false, and is
+ // set in the WebView constructor.
+ private static volatile boolean sEnforceThreadChecking = false;
+
+ /**
+ * Transportation object for returning WebView across thread boundaries.
+ */
+ public class WebViewTransport {
+ private WebView mWebview;
+ /**
+ * Sets the WebView to the transportation object.
+ *
+ * @param webview the WebView to transport
+ */
+ public synchronized void setWebView(WebView webview) {
+ mWebview = webview;
+ }
+
+ /**
+ * Gets the WebView object.
+ *
+ * @return the transported WebView object
+ */
+ public synchronized WebView getWebView() {
+ return mWebview;
+ }
+ }
+
+ /**
+ * URI scheme for telephone number.
+ */
+ public static final String SCHEME_TEL = "tel:";
/**
- * Construct a new WebView with a Context object.
- * @param context A Context object used to access application assets.
+ * URI scheme for email address.
+ */
+ public static final String SCHEME_MAILTO = "mailto:";
+ /**
+ * URI scheme for map address.
+ */
+ public static final String SCHEME_GEO = "geo:0,0?q=";
+
+ /**
+ * Interface to listen for find results.
+ */
+ public interface FindListener {
+ /**
+ * Notifies the listener about progress made by a find operation.
+ *
+ * @param activeMatchOrdinal the zero-based ordinal of the currently selected match
+ * @param numberOfMatches how many matches have been found
+ * @param isDoneCounting whether the find operation has actually completed. The listener
+ * may be notified multiple times while the
+ * operation is underway, and the numberOfMatches
+ * value should not be considered final unless
+ * isDoneCounting is {@code true}.
+ */
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting);
+ }
+
+ /**
+ * Callback interface supplied to {@link #postVisualStateCallback} for receiving
+ * notifications about the visual state.
+ */
+ public static abstract class VisualStateCallback {
+ /**
+ * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
+ *
+ * @param requestId The identifier passed to {@link #postVisualStateCallback} when this
+ * callback was posted.
+ */
+ public abstract void onComplete(long requestId);
+ }
+
+ /**
+ * Interface to listen for new pictures as they change.
+ *
+ * @deprecated This interface is now obsolete.
+ */
+ @Deprecated
+ public interface PictureListener {
+ /**
+ * Used to provide notification that the WebView's picture has changed.
+ * See {@link WebView#capturePicture} for details of the picture.
+ *
+ * @param view the WebView that owns the picture
+ * @param picture the new picture. Applications targeting
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
+ * will always receive a {@code null} Picture.
+ * @deprecated Deprecated due to internal changes.
+ */
+ @Deprecated
+ void onNewPicture(WebView view, @Nullable Picture picture);
+ }
+
+ public static class HitTestResult {
+ /**
+ * Default HitTestResult, where the target is unknown.
+ */
+ public static final int UNKNOWN_TYPE = 0;
+ /**
+ * @deprecated This type is no longer used.
+ */
+ @Deprecated
+ public static final int ANCHOR_TYPE = 1;
+ /**
+ * HitTestResult for hitting a phone number.
+ */
+ public static final int PHONE_TYPE = 2;
+ /**
+ * HitTestResult for hitting a map address.
+ */
+ public static final int GEO_TYPE = 3;
+ /**
+ * HitTestResult for hitting an email address.
+ */
+ public static final int EMAIL_TYPE = 4;
+ /**
+ * HitTestResult for hitting an HTML::img tag.
+ */
+ public static final int IMAGE_TYPE = 5;
+ /**
+ * @deprecated This type is no longer used.
+ */
+ @Deprecated
+ public static final int IMAGE_ANCHOR_TYPE = 6;
+ /**
+ * HitTestResult for hitting a HTML::a tag with src=http.
+ */
+ public static final int SRC_ANCHOR_TYPE = 7;
+ /**
+ * HitTestResult for hitting a HTML::a tag with src=http + HTML::img.
+ */
+ public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
+ /**
+ * HitTestResult for hitting an edit text area.
+ */
+ public static final int EDIT_TEXT_TYPE = 9;
+
+ private int mType;
+ private String mExtra;
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public HitTestResult() {
+ mType = UNKNOWN_TYPE;
+ }
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public void setExtra(String extra) {
+ mExtra = extra;
+ }
+
+ /**
+ * Gets the type of the hit test result. See the XXX_TYPE constants
+ * defined in this class.
+ *
+ * @return the type of the hit test result
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets additional type-dependant information about the result. See
+ * {@link WebView#getHitTestResult()} for details. May either be {@code null}
+ * or contain extra information about this result.
+ *
+ * @return additional type-dependant information about the result
+ */
+ @Nullable
+ public String getExtra() {
+ return mExtra;
+ }
+ }
+
+ /**
+ * Constructs a new WebView with a Context object.
+ *
+ * @param context a Context object used to access application assets
*/
public WebView(Context context) {
this(context, null);
}
/**
- * Construct a new WebView with layout parameters.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
+ * Constructs a new WebView with layout parameters.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
*/
public WebView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
/**
- * Construct a new WebView with layout parameters and a default style.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
- * @param defStyle The default style resource ID.
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public WebView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
-
- // START FAKE PUBLIC METHODS
-
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes a resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ this(context, attrs, defStyleAttr, defStyleRes, null, false);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param privateBrowsing whether this WebView will be initialized in
+ * private mode
+ *
+ * @deprecated Private browsing is no longer supported directly via
+ * WebView and will be removed in a future release. Prefer using
+ * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
+ * and {@link WebStorage} for fine-grained control of privacy data.
+ */
+ @Deprecated
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr,
+ boolean privateBrowsing) {
+ this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters, a default style and a set
+ * of custom JavaScript interfaces to be added to this WebView at initialization
+ * time. This guarantees that these interfaces will be available when the JS
+ * context is initialized.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param javaScriptInterfaces a Map of interface names, as keys, and
+ * object implementing those interfaces, as
+ * values
+ * @param privateBrowsing whether this WebView will be initialized in
+ * private mode
+ * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to
+ * be added synchronously, before a subsequent loadUrl call takes effect.
+ */
+ protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
+ Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
+ this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
+ }
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.
+ protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
+ Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ // WebView is important by default, unless app developer overrode attribute.
+ if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
+ setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
+ }
+
+ if (context == null) {
+ throw new IllegalArgumentException("Invalid context argument");
+ }
+ sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.JELLY_BEAN_MR2;
+ checkThread();
+
+ ensureProviderCreated();
+ mProvider.init(javaScriptInterfaces, privateBrowsing);
+ // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
+ CookieSyncManager.setGetInstanceIsAllowed();
+ }
+
+ /**
+ * Specifies whether the horizontal scrollbar has overlay style.
+ *
+ * @deprecated This method has no effect.
+ * @param overlay {@code true} if horizontal scrollbar should have overlay style
+ */
+ @Deprecated
public void setHorizontalScrollbarOverlay(boolean overlay) {
}
+ /**
+ * Specifies whether the vertical scrollbar has overlay style.
+ *
+ * @deprecated This method has no effect.
+ * @param overlay {@code true} if vertical scrollbar should have overlay style
+ */
+ @Deprecated
public void setVerticalScrollbarOverlay(boolean overlay) {
}
+ /**
+ * Gets whether horizontal scrollbar has overlay style.
+ *
+ * @deprecated This method is now obsolete.
+ * @return {@code true}
+ */
+ @Deprecated
public boolean overlayHorizontalScrollbar() {
- return false;
+ // The old implementation defaulted to true, so return true for consistency
+ return true;
}
+ /**
+ * Gets whether vertical scrollbar has overlay style.
+ *
+ * @deprecated This method is now obsolete.
+ * @return {@code false}
+ */
+ @Deprecated
public boolean overlayVerticalScrollbar() {
+ // The old implementation defaulted to false, so return false for consistency
return false;
}
+ /**
+ * Gets the visible height (in pixels) of the embedded title bar (if any).
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public int getVisibleTitleHeight() {
+ checkThread();
+ return mProvider.getVisibleTitleHeight();
+ }
+
+ /**
+ * Gets the SSL certificate for the main top-level page or {@code null} if there is
+ * no certificate (the site is not secure).
+ *
+ * @return the SSL certificate for the main top-level page
+ */
+ @Nullable
+ public SslCertificate getCertificate() {
+ checkThread();
+ return mProvider.getCertificate();
+ }
+
+ /**
+ * Sets the SSL certificate for the main top-level page.
+ *
+ * @deprecated Calling this function has no useful effect, and will be
+ * ignored in future releases.
+ */
+ @Deprecated
+ public void setCertificate(SslCertificate certificate) {
+ checkThread();
+ mProvider.setCertificate(certificate);
+ }
+
+ //-------------------------------------------------------------------------
+ // Methods called by activity
+ //-------------------------------------------------------------------------
+
+ /**
+ * Sets a username and password pair for the specified host. This data is
+ * used by the WebView to autocomplete username and password fields in web
+ * forms. Note that this is unrelated to the credentials used for HTTP
+ * authentication.
+ *
+ * @param host the host that required the credentials
+ * @param username the username for the given host
+ * @param password the password for the given host
+ * @see WebViewDatabase#clearUsernamePassword
+ * @see WebViewDatabase#hasUsernamePassword
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
+ */
+ @Deprecated
public void savePassword(String host, String username, String password) {
+ checkThread();
+ mProvider.savePassword(host, username, password);
}
+ /**
+ * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase}
+ * instance.
+ *
+ * @param host the host to which the credentials apply
+ * @param realm the realm to which the credentials apply
+ * @param username the username
+ * @param password the password
+ * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead
+ */
+ @Deprecated
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
+ checkThread();
+ mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
}
+ /**
+ * Retrieves HTTP authentication credentials for a given host and realm from the {@link
+ * WebViewDatabase} instance.
+ * @param host the host to which the credentials apply
+ * @param realm the realm to which the credentials apply
+ * @return the credentials as a String array, if found. The first element
+ * is the username and the second element is the password. {@code null} if
+ * no credentials are found.
+ * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
+ */
+ @Deprecated
+ @Nullable
public String[] getHttpAuthUsernamePassword(String host, String realm) {
- return null;
+ checkThread();
+ return mProvider.getHttpAuthUsernamePassword(host, realm);
}
+ /**
+ * Destroys the internal state of this WebView. This method should be called
+ * after this WebView has been removed from the view system. No other
+ * methods may be called on this WebView after destroy.
+ */
public void destroy() {
+ checkThread();
+ mProvider.destroy();
}
+ /**
+ * Enables platform notifications of data state and proxy changes.
+ * Notifications are enabled by default.
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
public static void enablePlatformNotifications() {
+ // noop
}
+ /**
+ * Disables platform notifications of data state and proxy changes.
+ * Notifications are enabled by default.
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
public static void disablePlatformNotifications() {
+ // noop
}
+ /**
+ * Used only by internal tests to free up memory.
+ *
+ * @hide
+ */
+ public static void freeMemoryForTests() {
+ getFactory().getStatics().freeMemoryForTests();
+ }
+
+ /**
+ * Informs WebView of the network state. This is used to set
+ * the JavaScript property window.navigator.isOnline and
+ * generates the online/offline event as specified in HTML5, sec. 5.7.7
+ *
+ * @param networkUp a boolean indicating if network is available
+ */
+ public void setNetworkAvailable(boolean networkUp) {
+ checkThread();
+ mProvider.setNetworkAvailable(networkUp);
+ }
+
+ /**
+ * Saves the state of this WebView used in
+ * {@link android.app.Activity#onSaveInstanceState}. Please note that this
+ * method no longer stores the display data for this WebView. The previous
+ * behavior could potentially leak files if {@link #restoreState} was never
+ * called.
+ *
+ * @param outState the Bundle to store this WebView's state
+ * @return the same copy of the back/forward list used to save the state, {@code null} if the
+ * method fails.
+ */
+ @Nullable
+ public WebBackForwardList saveState(Bundle outState) {
+ checkThread();
+ return mProvider.saveState(outState);
+ }
+
+ /**
+ * Saves the current display data to the Bundle given. Used in conjunction
+ * with {@link #saveState}.
+ * @param b a Bundle to store the display data
+ * @param dest the file to store the serialized picture data. Will be
+ * overwritten with this WebView's picture data.
+ * @return {@code true} if the picture was successfully saved
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public boolean savePicture(Bundle b, final File dest) {
+ checkThread();
+ return mProvider.savePicture(b, dest);
+ }
+
+ /**
+ * Restores the display data that was saved in {@link #savePicture}. Used in
+ * conjunction with {@link #restoreState}. Note that this will not work if
+ * this WebView is hardware accelerated.
+ *
+ * @param b a Bundle containing the saved display data
+ * @param src the file where the picture data was stored
+ * @return {@code true} if the picture was successfully restored
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public boolean restorePicture(Bundle b, File src) {
+ checkThread();
+ return mProvider.restorePicture(b, src);
+ }
+
+ /**
+ * Restores the state of this WebView from the given Bundle. This method is
+ * intended for use in {@link android.app.Activity#onRestoreInstanceState}
+ * and should be called to restore the state of this WebView. If
+ * it is called after this WebView has had a chance to build state (load
+ * pages, create a back/forward list, etc.) there may be undesirable
+ * side-effects. Please note that this method no longer restores the
+ * display data for this WebView.
+ *
+ * @param inState the incoming Bundle of state
+ * @return the restored back/forward list or {@code null} if restoreState failed
+ */
+ @Nullable
+ public WebBackForwardList restoreState(Bundle inState) {
+ checkThread();
+ return mProvider.restoreState(inState);
+ }
+
+ /**
+ * Loads the given URL with the specified additional HTTP headers.
+ * <p>
+ * Also see compatibility note on {@link #evaluateJavascript}.
+ *
+ * @param url the URL of the resource to load
+ * @param additionalHttpHeaders the additional headers to be used in the
+ * HTTP request for this URL, specified as a map from name to
+ * value. Note that if this map contains any of the headers
+ * that are set by default by this WebView, such as those
+ * controlling caching, accept types or the User-Agent, their
+ * values may be overridden by this WebView's defaults.
+ */
+ public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
+ checkThread();
+ mProvider.loadUrl(url, additionalHttpHeaders);
+ }
+
+ /**
+ * Loads the given URL.
+ * <p>
+ * Also see compatibility note on {@link #evaluateJavascript}.
+ *
+ * @param url the URL of the resource to load
+ */
public void loadUrl(String url) {
+ checkThread();
+ mProvider.loadUrl(url);
+ }
+
+ /**
+ * Loads the URL with postData using "POST" method into this WebView. If url
+ * is not a network URL, it will be loaded with {@link #loadUrl(String)}
+ * instead, ignoring the postData param.
+ *
+ * @param url the URL of the resource to load
+ * @param postData the data will be passed to "POST" request, which must be
+ * be "application/x-www-form-urlencoded" encoded.
+ */
+ public void postUrl(String url, byte[] postData) {
+ checkThread();
+ if (URLUtil.isNetworkUrl(url)) {
+ mProvider.postUrl(url, postData);
+ } else {
+ mProvider.loadUrl(url);
+ }
}
- public void loadData(String data, String mimeType, String encoding) {
+ /**
+ * Loads the given data into this WebView using a 'data' scheme URL.
+ * <p>
+ * Note that JavaScript's same origin policy means that script running in a
+ * page loaded using this method will be unable to access content loaded
+ * using any scheme other than 'data', including 'http(s)'. To avoid this
+ * restriction, use {@link
+ * #loadDataWithBaseURL(String,String,String,String,String)
+ * loadDataWithBaseURL()} with an appropriate base URL.
+ * <p>
+ * The encoding parameter specifies whether the data is base64 or URL
+ * encoded. If the data is base64 encoded, the value of the encoding
+ * parameter must be 'base64'. For all other values of the parameter,
+ * including {@code null}, it is assumed that the data uses ASCII encoding for
+ * octets inside the range of safe URL characters and use the standard %xx
+ * hex encoding of URLs for octets outside that range. For example, '#',
+ * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
+ * <p>
+ * The 'data' scheme URL formed by this method uses the default US-ASCII
+ * charset. If you need need to set a different charset, you should form a
+ * 'data' scheme URL which explicitly specifies a charset parameter in the
+ * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
+ * Note that the charset obtained from the mediatype portion of a data URL
+ * always overrides that specified in the HTML or XML document itself.
+ *
+ * @param data a String of data in the given encoding
+ * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
+ * defaults to 'text/html'.
+ * @param encoding the encoding of the data
+ */
+ public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) {
+ checkThread();
+ mProvider.loadData(data, mimeType, encoding);
}
- public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding, String failUrl) {
+ /**
+ * Loads the given data into this WebView, using baseUrl as the base URL for
+ * the content. The base URL is used both to resolve relative URLs and when
+ * applying JavaScript's same origin policy. The historyUrl is used for the
+ * history entry.
+ * <p>
+ * Note that content specified in this way can access local device files
+ * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
+ * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
+ * <p>
+ * If the base URL uses the data scheme, this method is equivalent to
+ * calling {@link #loadData(String,String,String) loadData()} and the
+ * historyUrl is ignored, and the data will be treated as part of a data: URL.
+ * If the base URL uses any other scheme, then the data will be loaded into
+ * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded
+ * entities in the string will not be decoded.
+ * <p>
+ * Note that the baseUrl is sent in the 'Referer' HTTP header when
+ * requesting subresources (images, etc.) of the page loaded using this method.
+ *
+ * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
+ * 'about:blank'.
+ * @param data a String of data in the given encoding
+ * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
+ * defaults to 'text/html'.
+ * @param encoding the encoding of the data
+ * @param historyUrl the URL to use as the history entry. If {@code null} defaults
+ * to 'about:blank'. If non-null, this must be a valid URL.
+ */
+ public void loadDataWithBaseURL(@Nullable String baseUrl, String data,
+ @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {
+ checkThread();
+ mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
+ /**
+ * Asynchronously evaluates JavaScript in the context of the currently displayed page.
+ * If non-null, |resultCallback| will be invoked with any result returned from that
+ * execution. This method must be called on the UI thread and the callback will
+ * be made on the UI thread.
+ * <p>
+ * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
+ * later, JavaScript state from an empty WebView is no longer persisted across navigations like
+ * {@link #loadUrl(String)}. For example, global variables and functions defined before calling
+ * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
+ * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
+ *
+ * @param script the JavaScript to execute.
+ * @param resultCallback A callback to be invoked when the script execution
+ * completes with the result of the execution (if any).
+ * May be {@code null} if no notification of the result is required.
+ */
+ public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) {
+ checkThread();
+ mProvider.evaluateJavaScript(script, resultCallback);
+ }
+
+ /**
+ * Saves the current view as a web archive.
+ *
+ * @param filename the filename where the archive should be placed
+ */
+ public void saveWebArchive(String filename) {
+ checkThread();
+ mProvider.saveWebArchive(filename);
+ }
+
+ /**
+ * Saves the current view as a web archive.
+ *
+ * @param basename the filename where the archive should be placed
+ * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename
+ * is assumed to be a directory in which a filename will be
+ * chosen according to the URL of the current page.
+ * @param callback called after the web archive has been saved. The
+ * parameter for onReceiveValue will either be the filename
+ * under which the file was saved, or {@code null} if saving the
+ * file failed.
+ */
+ public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String>
+ callback) {
+ checkThread();
+ mProvider.saveWebArchive(basename, autoname, callback);
+ }
+
+ /**
+ * Stops the current load.
+ */
public void stopLoading() {
+ checkThread();
+ mProvider.stopLoading();
}
+ /**
+ * Reloads the current URL.
+ */
public void reload() {
+ checkThread();
+ mProvider.reload();
}
+ /**
+ * Gets whether this WebView has a back history item.
+ *
+ * @return {@code true} iff this WebView has a back history item
+ */
public boolean canGoBack() {
- return false;
+ checkThread();
+ return mProvider.canGoBack();
}
+ /**
+ * Goes back in the history of this WebView.
+ */
public void goBack() {
+ checkThread();
+ mProvider.goBack();
}
+ /**
+ * Gets whether this WebView has a forward history item.
+ *
+ * @return {@code true} iff this WebView has a forward history item
+ */
public boolean canGoForward() {
- return false;
+ checkThread();
+ return mProvider.canGoForward();
}
+ /**
+ * Goes forward in the history of this WebView.
+ */
public void goForward() {
+ checkThread();
+ mProvider.goForward();
}
+ /**
+ * Gets whether the page can go back or forward the given
+ * number of steps.
+ *
+ * @param steps the negative or positive number of steps to move the
+ * history
+ */
public boolean canGoBackOrForward(int steps) {
- return false;
+ checkThread();
+ return mProvider.canGoBackOrForward(steps);
}
+ /**
+ * Goes to the history item that is the number of steps away from
+ * the current item. Steps is negative if backward and positive
+ * if forward.
+ *
+ * @param steps the number of steps to take back or forward in the back
+ * forward list
+ */
public void goBackOrForward(int steps) {
+ checkThread();
+ mProvider.goBackOrForward(steps);
+ }
+
+ /**
+ * Gets whether private browsing is enabled in this WebView.
+ */
+ public boolean isPrivateBrowsingEnabled() {
+ checkThread();
+ return mProvider.isPrivateBrowsingEnabled();
}
+ /**
+ * Scrolls the contents of this WebView up by half the view size.
+ *
+ * @param top {@code true} to jump to the top of the page
+ * @return {@code true} if the page was scrolled
+ */
public boolean pageUp(boolean top) {
- return false;
+ checkThread();
+ return mProvider.pageUp(top);
}
-
+
+ /**
+ * Scrolls the contents of this WebView down by half the page size.
+ *
+ * @param bottom {@code true} to jump to bottom of page
+ * @return {@code true} if the page was scrolled
+ */
public boolean pageDown(boolean bottom) {
- return false;
+ checkThread();
+ return mProvider.pageDown(bottom);
}
+ /**
+ * Posts a {@link VisualStateCallback}, which will be called when
+ * the current state of the WebView is ready to be drawn.
+ *
+ * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
+ * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The
+ * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
+ * the DOM at the current time are ready to be drawn the next time the {@link WebView}
+ * draws.
+ *
+ * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
+ * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also
+ * contain updates applied after the callback was posted.
+ *
+ * <p>The state of the DOM covered by this API includes the following:
+ * <ul>
+ * <li>primitive HTML elements (div, img, span, etc..)</li>
+ * <li>images</li>
+ * <li>CSS animations</li>
+ * <li>WebGL</li>
+ * <li>canvas</li>
+ * </ul>
+ * It does not include the state of:
+ * <ul>
+ * <li>the video tag</li>
+ * </ul>
+ *
+ * <p>To guarantee that the {@link WebView} will successfully render the first frame
+ * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
+ * must be met:
+ * <ul>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
+ * the {@link WebView} must be attached to the view hierarchy.</li>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
+ * then the {@link WebView} must be attached to the view hierarchy and must be made
+ * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
+ * {@link WebView} must be attached to the view hierarchy and its
+ * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
+ * values and must be made {@link View#VISIBLE VISIBLE} from the
+ * {@link VisualStateCallback#onComplete} method.</li>
+ * </ul>
+ *
+ * <p>When using this API it is also recommended to enable pre-rasterization if the {@link
+ * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for
+ * more details and do consider its caveats.
+ *
+ * @param requestId An id that will be returned in the callback to allow callers to match
+ * requests with callbacks.
+ * @param callback The callback to be invoked.
+ */
+ public void postVisualStateCallback(long requestId, VisualStateCallback callback) {
+ checkThread();
+ mProvider.insertVisualStateCallback(requestId, callback);
+ }
+
+ /**
+ * Clears this WebView so that onDraw() will draw nothing but white background,
+ * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
+ * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
+ * and release page resources (including any running JavaScript).
+ */
+ @Deprecated
public void clearView() {
+ checkThread();
+ mProvider.clearView();
}
-
+
+ /**
+ * Gets a new picture that captures the current contents of this WebView.
+ * The picture is of the entire document being displayed, and is not
+ * limited to the area currently displayed by this WebView. Also, the
+ * picture is a static copy and is unaffected by later changes to the
+ * content being displayed.
+ * <p>
+ * Note that due to internal changes, for API levels between
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
+ * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
+ * picture does not include fixed position elements or scrollable divs.
+ * <p>
+ * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture
+ * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve
+ * additional conversion at a cost in memory and performance. Also the
+ * {@link android.graphics.Picture#createFromStream} and
+ * {@link android.graphics.Picture#writeToStream} methods are not supported on the
+ * returned object.
+ *
+ * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or
+ * {@link #saveWebArchive} to save the content to a file.
+ *
+ * @return a picture that captures the current contents of this WebView
+ */
+ @Deprecated
public Picture capturePicture() {
- return null;
+ checkThread();
+ return mProvider.capturePicture();
+ }
+
+ /**
+ * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
+ * to provide a print document name.
+ */
+ @Deprecated
+ public PrintDocumentAdapter createPrintDocumentAdapter() {
+ checkThread();
+ return mProvider.createPrintDocumentAdapter("default");
}
+ /**
+ * Creates a PrintDocumentAdapter that provides the content of this WebView for printing.
+ *
+ * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot
+ * be drawn during the conversion process - any such draws are undefined. It is recommended
+ * to use a dedicated off screen WebView for the printing. If necessary, an application may
+ * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
+ * wrapped around the object returned and observing the onStart and onFinish methods. See
+ * {@link android.print.PrintDocumentAdapter} for more information.
+ *
+ * @param documentName The user-facing name of the printed document. See
+ * {@link android.print.PrintDocumentInfo}
+ */
+ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
+ checkThread();
+ return mProvider.createPrintDocumentAdapter(documentName);
+ }
+
+ /**
+ * Gets the current scale of this WebView.
+ *
+ * @return the current scale
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
+ */
+ @Deprecated
+ @ViewDebug.ExportedProperty(category = "webview")
public float getScale() {
- return 0;
+ checkThread();
+ return mProvider.getScale();
}
+ /**
+ * Sets the initial scale for this WebView. 0 means default.
+ * The behavior for the default scale depends on the state of
+ * {@link WebSettings#getUseWideViewPort()} and
+ * {@link WebSettings#getLoadWithOverviewMode()}.
+ * If the content fits into the WebView control by width, then
+ * the zoom is set to 100%. For wide content, the behavior
+ * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}.
+ * If its value is {@code true}, the content will be zoomed out to be fit
+ * by width into the WebView control, otherwise not.
+ *
+ * If initial scale is greater than 0, WebView starts with this value
+ * as initial scale.
+ * Please note that unlike the scale properties in the viewport meta tag,
+ * this method doesn't take the screen density into account.
+ *
+ * @param scaleInPercent the initial scale in percent
+ */
public void setInitialScale(int scaleInPercent) {
+ checkThread();
+ mProvider.setInitialScale(scaleInPercent);
}
+ /**
+ * Invokes the graphical zoom picker widget for this WebView. This will
+ * result in the zoom widget appearing on the screen to control the zoom
+ * level of this WebView.
+ */
public void invokeZoomPicker() {
+ checkThread();
+ mProvider.invokeZoomPicker();
+ }
+
+ /**
+ * Gets a HitTestResult based on the current cursor node. If a HTML::a
+ * tag is found and the anchor has a non-JavaScript URL, the HitTestResult
+ * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field.
+ * If the anchor does not have a URL or if it is a JavaScript URL, the type
+ * will be UNKNOWN_TYPE and the URL has to be retrieved through
+ * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
+ * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in
+ * the "extra" field. A type of
+ * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as
+ * a child node. If a phone number is found, the HitTestResult type is set
+ * to PHONE_TYPE and the phone number is set in the "extra" field of
+ * HitTestResult. If a map address is found, the HitTestResult type is set
+ * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
+ * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
+ * and the email is set in the "extra" field of HitTestResult. Otherwise,
+ * HitTestResult type is set to UNKNOWN_TYPE.
+ */
+ public HitTestResult getHitTestResult() {
+ checkThread();
+ return mProvider.getHitTestResult();
}
- public void requestFocusNodeHref(Message hrefMsg) {
+ /**
+ * Requests the anchor or image element URL at the last tapped point.
+ * If hrefMsg is {@code null}, this method returns immediately and does not
+ * dispatch hrefMsg to its target. If the tapped point hits an image,
+ * an anchor, or an image in an anchor, the message associates
+ * strings in named keys in its data. The value paired with the key
+ * may be an empty string.
+ *
+ * @param hrefMsg the message to be dispatched with the result of the
+ * request. The message data contains three keys. "url"
+ * returns the anchor's href attribute. "title" returns the
+ * anchor's text. "src" returns the image's src attribute.
+ */
+ public void requestFocusNodeHref(@Nullable Message hrefMsg) {
+ checkThread();
+ mProvider.requestFocusNodeHref(hrefMsg);
}
+ /**
+ * Requests the URL of the image last touched by the user. msg will be sent
+ * to its target with a String representing the URL as its object.
+ *
+ * @param msg the message to be dispatched with the result of the request
+ * as the data member with "url" as key. The result can be {@code null}.
+ */
public void requestImageRef(Message msg) {
+ checkThread();
+ mProvider.requestImageRef(msg);
}
+ /**
+ * Gets the URL for the current page. This is not always the same as the URL
+ * passed to WebViewClient.onPageStarted because although the load for
+ * that URL has begun, the current page may not have changed.
+ *
+ * @return the URL for the current page
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
public String getUrl() {
- return null;
+ checkThread();
+ return mProvider.getUrl();
}
+ /**
+ * Gets the original URL for the current page. This is not always the same
+ * as the URL passed to WebViewClient.onPageStarted because although the
+ * load for that URL has begun, the current page may not have changed.
+ * Also, there may have been redirects resulting in a different URL to that
+ * originally requested.
+ *
+ * @return the URL that was originally requested for the current page
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
+ public String getOriginalUrl() {
+ checkThread();
+ return mProvider.getOriginalUrl();
+ }
+
+ /**
+ * Gets the title for the current page. This is the title of the current page
+ * until WebViewClient.onReceivedTitle is called.
+ *
+ * @return the title for the current page
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
public String getTitle() {
- return null;
+ checkThread();
+ return mProvider.getTitle();
}
+ /**
+ * Gets the favicon for the current page. This is the favicon of the current
+ * page until WebViewClient.onReceivedIcon is called.
+ *
+ * @return the favicon for the current page
+ */
public Bitmap getFavicon() {
- return null;
+ checkThread();
+ return mProvider.getFavicon();
}
+ /**
+ * Gets the touch icon URL for the apple-touch-icon <link> element, or
+ * a URL on this site's server pointing to the standard location of a
+ * touch icon.
+ *
+ * @hide
+ */
+ public String getTouchIconUrl() {
+ return mProvider.getTouchIconUrl();
+ }
+
+ /**
+ * Gets the progress for the current page.
+ *
+ * @return the progress for the current page between 0 and 100
+ */
public int getProgress() {
- return 0;
+ checkThread();
+ return mProvider.getProgress();
}
-
+
+ /**
+ * Gets the height of the HTML content.
+ *
+ * @return the height of the HTML content
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
public int getContentHeight() {
- return 0;
+ checkThread();
+ return mProvider.getContentHeight();
+ }
+
+ /**
+ * Gets the width of the HTML content.
+ *
+ * @return the width of the HTML content
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
+ public int getContentWidth() {
+ return mProvider.getContentWidth();
}
+ /**
+ * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
+ * is a global requests, not restricted to just this WebView. This can be
+ * useful if the application has been paused.
+ */
public void pauseTimers() {
+ checkThread();
+ mProvider.pauseTimers();
}
+ /**
+ * Resumes all layout, parsing, and JavaScript timers for all WebViews.
+ * This will resume dispatching all timers.
+ */
public void resumeTimers() {
+ checkThread();
+ mProvider.resumeTimers();
+ }
+
+ /**
+ * Does a best-effort attempt to pause any processing that can be paused
+ * safely, such as animations and geolocation. Note that this call
+ * does not pause JavaScript. To pause JavaScript globally, use
+ * {@link #pauseTimers}.
+ *
+ * To resume WebView, call {@link #onResume}.
+ */
+ public void onPause() {
+ checkThread();
+ mProvider.onPause();
+ }
+
+ /**
+ * Resumes a WebView after a previous call to {@link #onPause}.
+ */
+ public void onResume() {
+ checkThread();
+ mProvider.onResume();
+ }
+
+ /**
+ * Gets whether this WebView is paused, meaning onPause() was called.
+ * Calling onResume() sets the paused state back to {@code false}.
+ *
+ * @hide
+ */
+ public boolean isPaused() {
+ return mProvider.isPaused();
+ }
+
+ /**
+ * Informs this WebView that memory is low so that it can free any available
+ * memory.
+ * @deprecated Memory caches are automatically dropped when no longer needed, and in response
+ * to system memory pressure.
+ */
+ @Deprecated
+ public void freeMemory() {
+ checkThread();
+ mProvider.freeMemory();
}
- public void clearCache() {
+ /**
+ * Clears the resource cache. Note that the cache is per-application, so
+ * this will clear the cache for all WebViews used.
+ *
+ * @param includeDiskFiles if {@code false}, only the RAM cache is cleared
+ */
+ public void clearCache(boolean includeDiskFiles) {
+ checkThread();
+ mProvider.clearCache(includeDiskFiles);
}
+ /**
+ * Removes the autocomplete popup from the currently focused form field, if
+ * present. Note this only affects the display of the autocomplete popup,
+ * it does not remove any saved form data from this WebView's store. To do
+ * that, use {@link WebViewDatabase#clearFormData}.
+ */
public void clearFormData() {
+ checkThread();
+ mProvider.clearFormData();
}
+ /**
+ * Tells this WebView to clear its internal back/forward list.
+ */
public void clearHistory() {
+ checkThread();
+ mProvider.clearHistory();
}
+ /**
+ * Clears the SSL preferences table stored in response to proceeding with
+ * SSL certificate errors.
+ */
public void clearSslPreferences() {
+ checkThread();
+ mProvider.clearSslPreferences();
+ }
+
+ /**
+ * Clears the client certificate preferences stored in response
+ * to proceeding/cancelling client cert requests. Note that WebView
+ * automatically clears these preferences when it receives a
+ * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are
+ * shared by all the WebViews that are created by the embedder application.
+ *
+ * @param onCleared A runnable to be invoked when client certs are cleared.
+ * The runnable will be called in UI thread.
+ */
+ public static void clearClientCertPreferences(@Nullable Runnable onCleared) {
+ getFactory().getStatics().clearClientCertPreferences(onCleared);
+ }
+
+ /**
+ * Starts Safe Browsing initialization.
+ * <p>
+ * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
+ * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
+ * devices {@code callback} will receive {@code false}.
+ * <p>
+ * This does not enable the Safe Browsing feature itself, and should only be called if Safe
+ * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
+ * prepares resources used for Safe Browsing.
+ * <p>
+ * This should be called with the Application Context (and will always use the Application
+ * context to do its work regardless).
+ *
+ * @param context Application Context.
+ * @param callback will be called on the UI thread with {@code true} if initialization is
+ * successful, {@code false} otherwise.
+ */
+ public static void startSafeBrowsing(Context context,
+ @Nullable ValueCallback<Boolean> callback) {
+ getFactory().getStatics().initSafeBrowsing(context, callback);
+ }
+
+ /**
+ * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
+ * global for all the WebViews.
+ * <p>
+ * Each rule should take one of these:
+ * <table>
+ * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
+ * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
+ * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
+ * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
+ * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
+ * </table>
+ * <p>
+ * All other rules, including wildcards, are invalid.
+ *
+ * @param urls the list of URLs
+ * @param callback will be called with {@code true} if URLs are successfully added to the
+ * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
+ * be run on the UI thread
+ */
+ public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
+ @Nullable ValueCallback<Boolean> callback) {
+ getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
+ }
+
+ /**
+ * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
+ *
+ * @return the url pointing to a privacy policy document which can be displayed to users.
+ */
+ @NonNull
+ public static Uri getSafeBrowsingPrivacyPolicyUrl() {
+ return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
+ }
+
+ /**
+ * Gets the WebBackForwardList for this WebView. This contains the
+ * back/forward list for use in querying each item in the history stack.
+ * This is a copy of the private WebBackForwardList so it contains only a
+ * snapshot of the current state. Multiple calls to this method may return
+ * different objects. The object returned from this method will not be
+ * updated to reflect any new state.
+ */
+ public WebBackForwardList copyBackForwardList() {
+ checkThread();
+ return mProvider.copyBackForwardList();
+
+ }
+
+ /**
+ * Registers the listener to be notified as find-on-page operations
+ * progress. This will replace the current listener.
+ *
+ * @param listener an implementation of {@link FindListener}
+ */
+ public void setFindListener(FindListener listener) {
+ checkThread();
+ setupFindListenerIfNeeded();
+ mFindListener.mUserFindListener = listener;
+ }
+
+ /**
+ * Highlights and scrolls to the next match found by
+ * {@link #findAllAsync}, wrapping around page boundaries as necessary.
+ * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
+ * has not been called yet, or if {@link #clearMatches} has been called since the
+ * last find operation, this function does nothing.
+ *
+ * @param forward the direction to search
+ * @see #setFindListener
+ */
+ public void findNext(boolean forward) {
+ checkThread();
+ mProvider.findNext(forward);
+ }
+
+ /**
+ * Finds all instances of find on the page and highlights them.
+ * Notifies any registered {@link FindListener}.
+ *
+ * @param find the string to find
+ * @return the number of occurrences of the String "find" that were found
+ * @deprecated {@link #findAllAsync} is preferred.
+ * @see #setFindListener
+ */
+ @Deprecated
+ public int findAll(String find) {
+ checkThread();
+ StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
+ return mProvider.findAll(find);
+ }
+
+ /**
+ * Finds all instances of find on the page and highlights them,
+ * asynchronously. Notifies any registered {@link FindListener}.
+ * Successive calls to this will cancel any pending searches.
+ *
+ * @param find the string to find.
+ * @see #setFindListener
+ */
+ public void findAllAsync(String find) {
+ checkThread();
+ mProvider.findAllAsync(find);
+ }
+
+ /**
+ * Starts an ActionMode for finding text in this WebView. Only works if this
+ * WebView is attached to the view system.
+ *
+ * @param text if non-null, will be the initial text to search for.
+ * Otherwise, the last String searched for in this WebView will
+ * be used to start.
+ * @param showIme if {@code true}, show the IME, assuming the user will begin typing.
+ * If {@code false} and text is non-null, perform a find all.
+ * @return {@code true} if the find dialog is shown, {@code false} otherwise
+ * @deprecated This method does not work reliably on all Android versions;
+ * implementing a custom find dialog using WebView.findAllAsync()
+ * provides a more robust solution.
+ */
+ @Deprecated
+ public boolean showFindDialog(@Nullable String text, boolean showIme) {
+ checkThread();
+ return mProvider.showFindDialog(text, showIme);
}
+ /**
+ * Gets the first substring consisting of the address of a physical
+ * location. Currently, only addresses in the United States are detected,
+ * and consist of:
+ * <ul>
+ * <li>a house number</li>
+ * <li>a street name</li>
+ * <li>a street type (Road, Circle, etc), either spelled out or
+ * abbreviated</li>
+ * <li>a city name</li>
+ * <li>a state or territory, either spelled out or two-letter abbr</li>
+ * <li>an optional 5 digit or 9 digit zip code</li>
+ * </ul>
+ * All names must be correctly capitalized, and the zip code, if present,
+ * must be valid for the state. The street type must be a standard USPS
+ * spelling or abbreviation. The state or territory must also be spelled
+ * or abbreviated using USPS standards. The house number may not exceed
+ * five digits.
+ *
+ * @param addr the string to search for addresses
+ * @return the address, or if no address is found, {@code null}
+ */
+ @Nullable
public static String findAddress(String addr) {
- return null;
+ // TODO: Rewrite this in Java so it is not needed to start up chromium
+ // Could also be deprecated
+ return getFactory().getStatics().findAddress(addr);
+ }
+
+ /**
+ * For apps targeting the L release, WebView has a new default behavior that reduces
+ * memory footprint and increases performance by intelligently choosing
+ * the portion of the HTML document that needs to be drawn. These
+ * optimizations are transparent to the developers. However, under certain
+ * circumstances, an App developer may want to disable them:
+ * <ol>
+ * <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
+ * of the page that is way outside the visible portion of the page.</li>
+ * <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
+ * Note that capturePicture is a deprecated API.</li>
+ * </ol>
+ * Enabling drawing the entire HTML document has a significant performance
+ * cost. This method should be called before any WebViews are created.
+ */
+ public static void enableSlowWholeDocumentDraw() {
+ getFactory().getStatics().enableSlowWholeDocumentDraw();
}
+ /**
+ * Clears the highlighting surrounding text matches created by
+ * {@link #findAllAsync}.
+ */
+ public void clearMatches() {
+ checkThread();
+ mProvider.clearMatches();
+ }
+
+ /**
+ * Queries the document to see if it contains any image references. The
+ * message object will be dispatched with arg1 being set to 1 if images
+ * were found and 0 if the document does not reference any images.
+ *
+ * @param response the message that will be dispatched with the result
+ */
public void documentHasImages(Message response) {
+ checkThread();
+ mProvider.documentHasImages(response);
}
+ /**
+ * Sets the WebViewClient that will receive various notifications and
+ * requests. This will replace the current handler.
+ *
+ * @param client an implementation of WebViewClient
+ * @see #getWebViewClient
+ */
public void setWebViewClient(WebViewClient client) {
+ checkThread();
+ mProvider.setWebViewClient(client);
+ }
+
+ /**
+ * Gets the WebViewClient.
+ *
+ * @return the WebViewClient, or a default client if not yet set
+ * @see #setWebViewClient
+ */
+ public WebViewClient getWebViewClient() {
+ checkThread();
+ return mProvider.getWebViewClient();
}
+ /**
+ * Registers the interface to be used when content can not be handled by
+ * the rendering engine, and should be downloaded instead. This will replace
+ * the current handler.
+ *
+ * @param listener an implementation of DownloadListener
+ */
public void setDownloadListener(DownloadListener listener) {
+ checkThread();
+ mProvider.setDownloadListener(listener);
}
+ /**
+ * Sets the chrome handler. This is an implementation of WebChromeClient for
+ * use in handling JavaScript dialogs, favicons, titles, and the progress.
+ * This will replace the current handler.
+ *
+ * @param client an implementation of WebChromeClient
+ * @see #getWebChromeClient
+ */
public void setWebChromeClient(WebChromeClient client) {
+ checkThread();
+ mProvider.setWebChromeClient(client);
}
- public void addJavascriptInterface(Object obj, String interfaceName) {
+ /**
+ * Gets the chrome handler.
+ *
+ * @return the WebChromeClient, or {@code null} if not yet set
+ * @see #setWebChromeClient
+ */
+ @Nullable
+ public WebChromeClient getWebChromeClient() {
+ checkThread();
+ return mProvider.getWebChromeClient();
+ }
+
+ /**
+ * Sets the Picture listener. This is an interface used to receive
+ * notifications of a new Picture.
+ *
+ * @param listener an implementation of WebView.PictureListener
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public void setPictureListener(PictureListener listener) {
+ checkThread();
+ mProvider.setPictureListener(listener);
+ }
+
+ /**
+ * Injects the supplied Java object into this WebView. The object is
+ * injected into the JavaScript context of the main frame, using the
+ * supplied name. This allows the Java object's methods to be
+ * accessed from JavaScript. For applications targeted to API
+ * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ * and above, only public methods that are annotated with
+ * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
+ * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
+ * all public methods (including the inherited ones) can be accessed, see the
+ * important security note below for implications.
+ * <p> Note that injected objects will not appear in JavaScript until the page is next
+ * (re)loaded. JavaScript should be enabled before injecting the object. For example:
+ * <pre>
+ * class JsObject {
+ * {@literal @}JavascriptInterface
+ * public String toString() { return "injectedObject"; }
+ * }
+ * webview.getSettings().setJavaScriptEnabled(true);
+ * webView.addJavascriptInterface(new JsObject(), "injectedObject");
+ * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
+ * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
+ * <p>
+ * <strong>IMPORTANT:</strong>
+ * <ul>
+ * <li> This method can be used to allow JavaScript to control the host
+ * application. This is a powerful feature, but also presents a security
+ * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
+ * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+ * are still vulnerable if the app runs on a device running Android earlier than 4.2.
+ * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ * and to ensure the method is called only when running on Android 4.2 or later.
+ * With these older versions, JavaScript could use reflection to access an
+ * injected object's public fields. Use of this method in a WebView
+ * containing untrusted content could allow an attacker to manipulate the
+ * host application in unintended ways, executing Java code with the
+ * permissions of the host application. Use extreme care when using this
+ * method in a WebView which could contain untrusted content.</li>
+ * <li> JavaScript interacts with Java object on a private, background
+ * thread of this WebView. Care is therefore required to maintain thread
+ * safety.
+ * </li>
+ * <li> The Java object's fields are not accessible.</li>
+ * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
+ * and above, methods of injected Java objects are enumerable from
+ * JavaScript.</li>
+ * </ul>
+ *
+ * @param object the Java object to inject into this WebView's JavaScript
+ * context. {@code null} values are ignored.
+ * @param name the name used to expose the object in JavaScript
+ */
+ public void addJavascriptInterface(Object object, String name) {
+ checkThread();
+ mProvider.addJavascriptInterface(object, name);
+ }
+
+ /**
+ * Removes a previously injected Java object from this WebView. Note that
+ * the removal will not be reflected in JavaScript until the page is next
+ * (re)loaded. See {@link #addJavascriptInterface}.
+ *
+ * @param name the name used to expose the object in JavaScript
+ */
+ public void removeJavascriptInterface(@NonNull String name) {
+ checkThread();
+ mProvider.removeJavascriptInterface(name);
+ }
+
+ /**
+ * Creates a message channel to communicate with JS and returns the message
+ * ports that represent the endpoints of this message channel. The HTML5 message
+ * channel functionality is described
+ * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
+ * </a>
+ *
+ * <p>The returned message channels are entangled and already in started state.
+ *
+ * @return the two message ports that form the message channel.
+ */
+ public WebMessagePort[] createWebMessageChannel() {
+ checkThread();
+ return mProvider.createWebMessageChannel();
+ }
+
+ /**
+ * Post a message to main frame. The embedded application can restrict the
+ * messages to a certain target origin. See
+ * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
+ * HTML5 spec</a> for how target origin can be used.
+ * <p>
+ * A target origin can be set as a wildcard ("*"). However this is not recommended.
+ * See the page above for security issues.
+ *
+ * @param message the WebMessage
+ * @param targetOrigin the target origin.
+ */
+ public void postWebMessage(WebMessage message, Uri targetOrigin) {
+ checkThread();
+ mProvider.postMessageToMainFrame(message, targetOrigin);
+ }
+
+ /**
+ * Gets the WebSettings object used to control the settings for this
+ * WebView.
+ *
+ * @return a WebSettings object that can be used to control this WebView's
+ * settings
+ */
+ public WebSettings getSettings() {
+ checkThread();
+ return mProvider.getSettings();
+ }
+
+ /**
+ * Enables debugging of web contents (HTML / CSS / JavaScript)
+ * loaded into any WebViews of this application. This flag can be enabled
+ * in order to facilitate debugging of web layouts and JavaScript
+ * code running inside WebViews. Please refer to WebView documentation
+ * for the debugging guide.
+ *
+ * The default is {@code false}.
+ *
+ * @param enabled whether to enable web contents debugging
+ */
+ public static void setWebContentsDebuggingEnabled(boolean enabled) {
+ getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
+ }
+
+ /**
+ * Gets the list of currently loaded plugins.
+ *
+ * @return the list of currently loaded plugins
+ * @deprecated This was used for Gears, which has been deprecated.
+ * @hide
+ */
+ @Deprecated
+ public static synchronized PluginList getPluginList() {
+ return new PluginList();
+ }
+
+ /**
+ * @deprecated This was used for Gears, which has been deprecated.
+ * @hide
+ */
+ @Deprecated
+ public void refreshPlugins(boolean reloadOpenPages) {
+ checkThread();
+ }
+
+ /**
+ * Puts this WebView into text selection mode. Do not rely on this
+ * functionality; it will be deprecated in the future.
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public void emulateShiftHeld() {
+ checkThread();
+ }
+
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Override
+ // Cannot add @hide as this can always be accessed via the interface.
+ @Deprecated
+ public void onChildViewAdded(View parent, View child) {}
+
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Override
+ // Cannot add @hide as this can always be accessed via the interface.
+ @Deprecated
+ public void onChildViewRemoved(View p, View child) {}
+
+ /**
+ * @deprecated WebView should not have implemented
+ * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
+ */
+ @Override
+ // Cannot add @hide as this can always be accessed via the interface.
+ @Deprecated
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ }
+
+ /**
+ * @deprecated Only the default case, {@code true}, will be supported in a future version.
+ */
+ @Deprecated
+ public void setMapTrackballToArrowKeys(boolean setMap) {
+ checkThread();
+ mProvider.setMapTrackballToArrowKeys(setMap);
}
+
+ public void flingScroll(int vx, int vy) {
+ checkThread();
+ mProvider.flingScroll(vx, vy);
+ }
+
+ /**
+ * Gets the zoom controls for this WebView, as a separate View. The caller
+ * is responsible for inserting this View into the layout hierarchy.
+ * <p/>
+ * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced
+ * built-in zoom mechanisms for the WebView, as opposed to these separate
+ * zoom controls. The built-in mechanisms are preferred and can be enabled
+ * using {@link WebSettings#setBuiltInZoomControls}.
+ *
+ * @deprecated the built-in zoom mechanisms are preferred
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+ */
+ @Deprecated
public View getZoomControls() {
- return null;
+ checkThread();
+ return mProvider.getZoomControls();
}
+ /**
+ * Gets whether this WebView can be zoomed in.
+ *
+ * @return {@code true} if this WebView can be zoomed in
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
+ */
+ @Deprecated
+ public boolean canZoomIn() {
+ checkThread();
+ return mProvider.canZoomIn();
+ }
+
+ /**
+ * Gets whether this WebView can be zoomed out.
+ *
+ * @return {@code true} if this WebView can be zoomed out
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
+ */
+ @Deprecated
+ public boolean canZoomOut() {
+ checkThread();
+ return mProvider.canZoomOut();
+ }
+
+ /**
+ * Performs a zoom operation in this WebView.
+ *
+ * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's
+ * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
+ */
+ public void zoomBy(float zoomFactor) {
+ checkThread();
+ if (zoomFactor < 0.01)
+ throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
+ if (zoomFactor > 100.0)
+ throw new IllegalArgumentException("zoomFactor must be less than 100.");
+ mProvider.zoomBy(zoomFactor);
+ }
+
+ /**
+ * Performs zoom in in this WebView.
+ *
+ * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes
+ */
public boolean zoomIn() {
- return false;
+ checkThread();
+ return mProvider.zoomIn();
}
+ /**
+ * Performs zoom out in this WebView.
+ *
+ * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
+ */
public boolean zoomOut() {
- return false;
+ checkThread();
+ return mProvider.zoomOut();
+ }
+
+ /**
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public void debugDump() {
+ checkThread();
+ }
+
+ /**
+ * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
+ * @hide
+ */
+ @Override
+ public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
+ mProvider.dumpViewHierarchyWithProperties(out, level);
+ }
+
+ /**
+ * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
+ * @hide
+ */
+ @Override
+ public View findHierarchyView(String className, int hashCode) {
+ return mProvider.findHierarchyView(className, hashCode);
+ }
+
+ /** @hide */
+ @IntDef({
+ RENDERER_PRIORITY_WAIVED,
+ RENDERER_PRIORITY_BOUND,
+ RENDERER_PRIORITY_IMPORTANT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RendererPriority {}
+
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
+ * {@link WebView} renderers will be strong targets for out of memory
+ * killing.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_WAIVED = 0;
+ /**
+ * The renderer associated with this WebView is bound with
+ * the default priority for services.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_BOUND = 1;
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_IMPORTANT}.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_IMPORTANT = 2;
+
+ /**
+ * Set the renderer priority policy for this {@link WebView}. The
+ * priority policy will be used to determine whether an out of
+ * process renderer should be considered to be a target for OOM
+ * killing.
+ *
+ * Because a renderer can be associated with more than one
+ * WebView, the final priority it is computed as the maximum of
+ * any attached WebViews. When a WebView is destroyed it will
+ * cease to be considerered when calculating the renderer
+ * priority. Once no WebViews remain associated with the renderer,
+ * the priority of the renderer will be reduced to
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ *
+ * The default policy is to set the priority to
+ * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
+ * and this should not be changed unless the caller also handles
+ * renderer crashes with
+ * {@link WebViewClient#onRenderProcessGone}. Any other setting
+ * will result in WebView renderers being killed by the system
+ * more aggressively than the application.
+ *
+ * @param rendererRequestedPriority the minimum priority at which
+ * this WebView desires the renderer process to be bound.
+ * @param waivedWhenNotVisible if {@code true}, this flag specifies that
+ * when this WebView is not visible, it will be treated as
+ * if it had requested a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ */
+ public void setRendererPriorityPolicy(
+ @RendererPriority int rendererRequestedPriority,
+ boolean waivedWhenNotVisible) {
+ mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
+ }
+
+ /**
+ * Get the requested renderer priority for this WebView.
+ *
+ * @return the requested renderer priority policy.
+ */
+ @RendererPriority
+ public int getRendererRequestedPriority() {
+ return mProvider.getRendererRequestedPriority();
+ }
+
+ /**
+ * Return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ *
+ * @return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ */
+ public boolean getRendererPriorityWaivedWhenNotVisible() {
+ return mProvider.getRendererPriorityWaivedWhenNotVisible();
+ }
+
+ /**
+ * Sets the {@link TextClassifier} for this WebView.
+ */
+ public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+ mProvider.setTextClassifier(textClassifier);
+ }
+
+ /**
+ * Returns the {@link TextClassifier} used by this WebView.
+ * If no TextClassifier has been set, this WebView uses the default set by the system.
+ */
+ @NonNull
+ public TextClassifier getTextClassifier() {
+ return mProvider.getTextClassifier();
+ }
+
+ //-------------------------------------------------------------------------
+ // Interface for WebView providers
+ //-------------------------------------------------------------------------
+
+ /**
+ * Gets the WebViewProvider. Used by providers to obtain the underlying
+ * implementation, e.g. when the application responds to
+ * WebViewClient.onCreateWindow() request.
+ *
+ * @hide WebViewProvider is not public API.
+ */
+ @SystemApi
+ public WebViewProvider getWebViewProvider() {
+ return mProvider;
+ }
+
+ /**
+ * Callback interface, allows the provider implementation to access non-public methods
+ * and fields, and make super-class calls in this WebView instance.
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public class PrivateAccess {
+ // ---- Access to super-class methods ----
+ public int super_getScrollBarStyle() {
+ return WebView.super.getScrollBarStyle();
+ }
+
+ public void super_scrollTo(int scrollX, int scrollY) {
+ WebView.super.scrollTo(scrollX, scrollY);
+ }
+
+ public void super_computeScroll() {
+ WebView.super.computeScroll();
+ }
+
+ public boolean super_onHoverEvent(MotionEvent event) {
+ return WebView.super.onHoverEvent(event);
+ }
+
+ public boolean super_performAccessibilityAction(int action, Bundle arguments) {
+ return WebView.super.performAccessibilityActionInternal(action, arguments);
+ }
+
+ public boolean super_performLongClick() {
+ return WebView.super.performLongClick();
+ }
+
+ public boolean super_setFrame(int left, int top, int right, int bottom) {
+ return WebView.super.setFrame(left, top, right, bottom);
+ }
+
+ public boolean super_dispatchKeyEvent(KeyEvent event) {
+ return WebView.super.dispatchKeyEvent(event);
+ }
+
+ public boolean super_onGenericMotionEvent(MotionEvent event) {
+ return WebView.super.onGenericMotionEvent(event);
+ }
+
+ public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) {
+ return WebView.super.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public void super_setLayoutParams(ViewGroup.LayoutParams params) {
+ WebView.super.setLayoutParams(params);
+ }
+
+ public void super_startActivityForResult(Intent intent, int requestCode) {
+ WebView.super.startActivityForResult(intent, requestCode);
+ }
+
+ // ---- Access to non-public methods ----
+ public void overScrollBy(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverScrollX, int maxOverScrollY,
+ boolean isTouchEvent) {
+ WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
+ maxOverScrollX, maxOverScrollY, isTouchEvent);
+ }
+
+ public void awakenScrollBars(int duration) {
+ WebView.this.awakenScrollBars(duration);
+ }
+
+ public void awakenScrollBars(int duration, boolean invalidate) {
+ WebView.this.awakenScrollBars(duration, invalidate);
+ }
+
+ public float getVerticalScrollFactor() {
+ return WebView.this.getVerticalScrollFactor();
+ }
+
+ public float getHorizontalScrollFactor() {
+ return WebView.this.getHorizontalScrollFactor();
+ }
+
+ public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+ WebView.this.setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ public void onScrollChanged(int l, int t, int oldl, int oldt) {
+ WebView.this.onScrollChanged(l, t, oldl, oldt);
+ }
+
+ public int getHorizontalScrollbarHeight() {
+ return WebView.this.getHorizontalScrollbarHeight();
+ }
+
+ public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+ int l, int t, int r, int b) {
+ WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
+ }
+
+ // ---- Access to (non-public) fields ----
+ /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
+ public void setScrollXRaw(int scrollX) {
+ WebView.this.mScrollX = scrollX;
+ }
+
+ /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */
+ public void setScrollYRaw(int scrollY) {
+ WebView.this.mScrollY = scrollY;
+ }
+
+ }
+
+ //-------------------------------------------------------------------------
+ // Package-private internal stuff
+ //-------------------------------------------------------------------------
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void setFindDialogFindListener(FindListener listener) {
+ checkThread();
+ setupFindListenerIfNeeded();
+ mFindListener.mFindDialogFindListener = listener;
+ }
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void notifyFindDialogDismissed() {
+ checkThread();
+ mProvider.notifyFindDialogDismissed();
+ }
+
+ //-------------------------------------------------------------------------
+ // Private internal stuff
+ //-------------------------------------------------------------------------
+
+ private WebViewProvider mProvider;
+
+ /**
+ * In addition to the FindListener that the user may set via the WebView.setFindListener
+ * API, FindActionModeCallback will register it's own FindListener. We keep them separate
+ * via this class so that the two FindListeners can potentially exist at once.
+ */
+ private class FindListenerDistributor implements FindListener {
+ private FindListener mFindDialogFindListener;
+ private FindListener mUserFindListener;
+
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting) {
+ if (mFindDialogFindListener != null) {
+ mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+
+ if (mUserFindListener != null) {
+ mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+ }
+ }
+ private FindListenerDistributor mFindListener;
+
+ private void setupFindListenerIfNeeded() {
+ if (mFindListener == null) {
+ mFindListener = new FindListenerDistributor();
+ mProvider.setFindListener(mFindListener);
+ }
+ }
+
+ private void ensureProviderCreated() {
+ checkThread();
+ if (mProvider == null) {
+ // As this can get called during the base class constructor chain, pass the minimum
+ // number of dependencies here; the rest are deferred to init().
+ mProvider = getFactory().createWebView(this, new PrivateAccess());
+ }
+ }
+
+ private static WebViewFactoryProvider getFactory() {
+ return WebViewFactory.getProvider();
+ }
+
+ private final Looper mWebViewThread = Looper.myLooper();
+
+ private void checkThread() {
+ // Ignore mWebViewThread == null because this can be called during in the super class
+ // constructor, before this class's own constructor has even started.
+ if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
+ Throwable throwable = new Throwable(
+ "A WebView method was called on thread '" +
+ Thread.currentThread().getName() + "'. " +
+ "All WebView methods must be called on the same thread. " +
+ "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
+ ", FYI main Looper is " + Looper.getMainLooper() + ")");
+ Log.w(LOGTAG, Log.getStackTraceString(throwable));
+ StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
+
+ if (sEnforceThreadChecking) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Override View methods
+ //-------------------------------------------------------------------------
+
+ // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures
+ // there's a corresponding override (or better, caller) for each of them in here.
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mProvider.getViewDelegate().onAttachedToWindow();
+ }
+
+ /** @hide */
+ @Override
+ protected void onDetachedFromWindowInternal() {
+ mProvider.getViewDelegate().onDetachedFromWindow();
+ super.onDetachedFromWindowInternal();
+ }
+
+ /** @hide */
+ @Override
+ public void onMovedToDisplay(int displayId, Configuration config) {
+ mProvider.getViewDelegate().onMovedToDisplay(displayId, config);
+ }
+
+ @Override
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ mProvider.getViewDelegate().setLayoutParams(params);
+ }
+
+ @Override
+ public void setOverScrollMode(int mode) {
+ super.setOverScrollMode(mode);
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
+ ensureProviderCreated();
+ mProvider.getViewDelegate().setOverScrollMode(mode);
+ }
+
+ @Override
+ public void setScrollBarStyle(int style) {
+ mProvider.getViewDelegate().setScrollBarStyle(style);
+ super.setScrollBarStyle(style);
+ }
+
+ @Override
+ protected int computeHorizontalScrollRange() {
+ return mProvider.getScrollDelegate().computeHorizontalScrollRange();
+ }
+
+ @Override
+ protected int computeHorizontalScrollOffset() {
+ return mProvider.getScrollDelegate().computeHorizontalScrollOffset();
+ }
+
+ @Override
+ protected int computeVerticalScrollRange() {
+ return mProvider.getScrollDelegate().computeVerticalScrollRange();
+ }
+
+ @Override
+ protected int computeVerticalScrollOffset() {
+ return mProvider.getScrollDelegate().computeVerticalScrollOffset();
+ }
+
+ @Override
+ protected int computeVerticalScrollExtent() {
+ return mProvider.getScrollDelegate().computeVerticalScrollExtent();
+ }
+
+ @Override
+ public void computeScroll() {
+ mProvider.getScrollDelegate().computeScroll();
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onHoverEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onGenericMotionEvent(event);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event);
+ }
+
+ /*
+ TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not
+ to be delegating them too.
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyPreIme(keyCode, event);
+ }
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyLongPress(keyCode, event);
+ }
+ @Override
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyShortcut(keyCode, event);
+ }
+ */
+
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+ AccessibilityNodeProvider provider =
+ mProvider.getViewDelegate().getAccessibilityNodeProvider();
+ return provider == null ? super.getAccessibilityNodeProvider() : provider;
+ }
+
+ @Deprecated
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return mProvider.getViewDelegate().shouldDelayChildPressedState();
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return WebView.class.getName();
+ }
+
+ @Override
+ public void onProvideVirtualStructure(ViewStructure structure) {
+ mProvider.getViewDelegate().onProvideVirtualStructure(structure);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
+ * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
+ * understood by the {@link android.service.autofill.AutofillService} implementations:
+ *
+ * <ol>
+ * <li>Only the HTML nodes inside a {@code FORM} are generated.
+ * <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the
+ * node representing the WebView.
+ * <li>If a web page has multiple {@code FORM}s, only the data for the current form is
+ * represented&mdash;if the user taps a field from another form, then the current autofill
+ * context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and
+ * a new context is created for that {@code FORM}.
+ * <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in
+ * the view structure until the user taps a field from a {@code FORM} inside the
+ * {@code IFRAME}, in which case it would be treated the same way as multiple forms described
+ * above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the
+ * {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node.
+ * <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
+ * {@link ViewStructure#setAutofillHints(String[])}.
+ * <li>If the view is editable, the {@link ViewStructure#setAutofillType(int)} and
+ * {@link ViewStructure#setAutofillValue(AutofillValue)} must be set.
+ * <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
+ * <li>Other HTML attributes can be represented through
+ * {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}.
+ * </ol>
+ *
+ * <p>If the WebView implementation can determine that the value of a field was set statically
+ * (for example, not through Javascript), it should also call
+ * {@code structure.setDataIsSensitive(false)}.
+ *
+ * <p>For example, an HTML form with 2 fields for username and password:
+ *
+ * <pre class="prettyprint">
+ * &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
+ * &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
+ * </pre>
+ *
+ * <p>Would map to:
+ *
+ * <pre class="prettyprint">
+ * int index = structure.addChildCount(2);
+ * ViewStructure username = structure.newChild(index);
+ * username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child
+ * username.setAutofillHints("username");
+ * username.setHtmlInfo(username.newHtmlInfoBuilder("input")
+ * .addAttribute("type", "text")
+ * .addAttribute("name", "username")
+ * .build());
+ * username.setHint("Email or username");
+ * username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * username.setAutofillValue(AutofillValue.forText("Type your username"));
+ * // Value of the field is not sensitive because it was created statically and not changed.
+ * username.setDataIsSensitive(false);
+ *
+ * ViewStructure password = structure.newChild(index + 1);
+ * username.setAutofillId(structure, 2); // id 2 - second child
+ * password.setAutofillHints("current-password");
+ * password.setHtmlInfo(password.newHtmlInfoBuilder("input")
+ * .addAttribute("type", "password")
+ * .addAttribute("name", "password")
+ * .build());
+ * password.setHint("Password");
+ * password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * </pre>
+ */
+ @Override
+ public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+ mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
+ }
+
+ @Override
+ public void autofill(SparseArray<AutofillValue>values) {
+ mProvider.getViewDelegate().autofill(values);
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
+ mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
+ mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
+ }
+
+ /** @hide */
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
+ }
+
+ /** @hide */
+ @Override
+ protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+ int l, int t, int r, int b) {
+ mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
+ }
+
+ @Override
+ protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
+ mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY);
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ mProvider.getViewDelegate().onWindowVisibilityChanged(visibility);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mProvider.getViewDelegate().onDraw(canvas);
+ }
+
+ @Override
+ public boolean performLongClick() {
+ return mProvider.getViewDelegate().performLongClick();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mProvider.getViewDelegate().onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Creates a new InputConnection for an InputMethod to interact with the WebView.
+ * This is similar to {@link View#onCreateInputConnection} but note that WebView
+ * calls InputConnection methods on a thread other than the UI thread.
+ * If these methods are overridden, then the overriding methods should respect
+ * thread restrictions when calling View methods or accessing data.
+ */
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
+ }
+
+ @Override
+ public boolean onDragEvent(DragEvent event) {
+ return mProvider.getViewDelegate().onDragEvent(event);
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
+ ensureProviderCreated();
+ mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus);
+ super.onWindowFocusChanged(hasWindowFocus);
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect);
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ return mProvider.getViewDelegate().setFrame(left, top, right, bottom);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int ow, int oh) {
+ super.onSizeChanged(w, h, ow, oh);
+ mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh);
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mProvider.getViewDelegate().dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+ return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate);
+ }
+
+ @Override
+ public void setBackgroundColor(int color) {
+ mProvider.getViewDelegate().setBackgroundColor(color);
+ }
+
+ @Override
+ public void setLayerType(int layerType, Paint paint) {
+ super.setLayerType(layerType, paint);
+ mProvider.getViewDelegate().setLayerType(layerType, paint);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mProvider.getViewDelegate().preDispatchDraw(canvas);
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ public void onStartTemporaryDetach() {
+ super.onStartTemporaryDetach();
+ mProvider.getViewDelegate().onStartTemporaryDetach();
+ }
+
+ @Override
+ public void onFinishTemporaryDetach() {
+ super.onFinishTemporaryDetach();
+ mProvider.getViewDelegate().onFinishTemporaryDetach();
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mProvider.getViewDelegate().getHandler(super.getHandler());
+ }
+
+ @Override
+ public View findFocus() {
+ return mProvider.getViewDelegate().findFocus(super.findFocus());
+ }
+
+ /**
+ * If WebView has already been loaded into the current process this method will return the
+ * package that was used to load it. Otherwise, the package that would be used if the WebView
+ * was loaded right now will be returned; this does not cause WebView to be loaded, so this
+ * information may become outdated at any time.
+ * The WebView package changes either when the current WebView package is updated, disabled, or
+ * uninstalled. It can also be changed through a Developer Setting.
+ * If the WebView package changes, any app process that has loaded WebView will be killed. The
+ * next time the app starts and loads WebView it will use the new WebView package instead.
+ * @return the current WebView package, or {@code null} if there is none.
+ */
+ @Nullable
+ public static PackageInfo getCurrentWebViewPackage() {
+ PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
+ if (webviewPackage != null) {
+ return webviewPackage;
+ }
+
+ IWebViewUpdateService service = WebViewFactory.getUpdateService();
+ if (service == null) {
+ return null;
+ }
+ try {
+ return service.getCurrentWebViewPackage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
+ *
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ * @hide
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
+ }
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ checkThread();
+ encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
+ encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
+ encoder.addProperty("webview:scale", mProvider.getScale());
+ encoder.addProperty("webview:title", mProvider.getTitle());
+ encoder.addProperty("webview:url", mProvider.getUrl());
+ encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
}
}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index d4be7e57..afd11881 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.25f;
+ private static final float MAGNIFIER_ZOOM = 1.5f;
@IntDef({MagnifierHandleTrigger.SELECTION_START,
MagnifierHandleTrigger.SELECTION_END,
MagnifierHandleTrigger.INSERTION})
@@ -476,17 +476,6 @@ public class Editor {
stopTextActionModeWithPreservingSelection();
}
- void invalidateMagnifier() {
- final DisplayMetrics dm = mTextView.getResources().getDisplayMetrics();
- invalidateMagnifier(0, 0, dm.widthPixels, dm.heightPixels);
- }
-
- void invalidateMagnifier(final float l, final float t, final float r, final float b) {
- if (mMagnifier != null) {
- mTextView.post(() -> mMagnifier.invalidate(new RectF(l, t, r, b)));
- }
- }
-
private void discardTextDisplayLists() {
if (mTextRenderNodes != null) {
for (int i = 0; i < mTextRenderNodes.length; i++) {
@@ -4556,17 +4545,17 @@ public class Editor {
+ mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
final int[] coordinatesOnScreen = new int[2];
mTextView.getLocationOnScreen(coordinatesOnScreen);
- final float centerXOnScreen = mTextView.convertViewToScreenCoord(xPosInView, true);
- final float centerYOnScreen = mTextView.convertViewToScreenCoord(yPosInView, false);
+ final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft()
+ - mTextView.getScrollX() + coordinatesOnScreen[0];
+ final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop()
+ - mTextView.getScrollY() + coordinatesOnScreen[1];
- suspendBlink();
mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
}
protected final void dismissMagnifier() {
if (mMagnifier != null) {
mMagnifier.dismiss();
- resumeBlink();
}
}
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index 199b596a..1b26f8e2 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -2653,11 +2653,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling
* {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
- * 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.
+ * to launch the provided {@link PendingIntent}.
*
* When setting the on-click action of items within collections (eg. {@link ListView},
* {@link StackView} etc.), this method will not work. Instead, use {@link
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 5e22650a..3be42a5b 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -95,15 +95,11 @@ 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.getSelectionEnd(),
+ mTextView.isTextEditable());
cancelAsyncTask();
if (skipTextClassification()) {
startActionMode(null);
@@ -200,10 +196,7 @@ public final class SelectionActionModeHelper {
private void startActionMode(@Nullable SelectionResult result) {
final CharSequence text = getText(mTextView);
if (result != null && text instanceof Spannable) {
- // Do not change the selection if TextClassifier should be dark launched.
- if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
- Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
- }
+ Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
mTextClassification = result.mClassification;
} else {
mTextClassification = null;
@@ -384,7 +377,7 @@ public final class SelectionActionModeHelper {
}
private void resetTextClassificationHelper() {
- mTextClassificationHelper.init(
+ mTextClassificationHelper.reset(
mTextView.getTextClassifier(),
getText(mTextView),
mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -422,7 +415,8 @@ public final class SelectionActionModeHelper {
/**
* Called when the original selection happens, before smart selection is triggered.
*/
- public void onOriginalSelection(CharSequence text, int selectionStart, int selectionEnd) {
+ public void onOriginalSelection(
+ CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
// If we abandoned a selection and created a new one very shortly after, we may still
// have a pending request to log ABANDON, which we flush here.
mDelayedLogAbandon.flush();
@@ -818,11 +812,11 @@ public final class SelectionActionModeHelper {
TextClassificationHelper(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
- init(textClassifier, text, selectionStart, selectionEnd, locales);
+ reset(textClassifier, text, selectionStart, selectionEnd, locales);
}
@UiThread
- public void init(TextClassifier textClassifier,
+ public void reset(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
mTextClassifier = Preconditions.checkNotNull(textClassifier);
mText = Preconditions.checkNotNull(text).toString();
@@ -845,12 +839,8 @@ public final class SelectionActionModeHelper {
trimText();
final TextSelection selection = mTextClassifier.suggestSelection(
mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
- // 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);
- }
+ 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 ce805526..24ae03c3 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -9219,36 +9219,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- @Override
- public void invalidate() {
- super.invalidate();
-
- if (mEditor != null) {
- mEditor.invalidateMagnifier();
- }
- }
-
- @Override
- public void invalidate(int l, int t, int r, int b) {
- super.invalidate(l, t, r, b);
-
- if (mEditor != null) {
- mEditor.invalidateMagnifier(
- convertViewToScreenCoord(l, true /* isHorizontal */),
- convertViewToScreenCoord(t, false /* isHorizontal */),
- convertViewToScreenCoord(r, true /* isHorizontal */),
- convertViewToScreenCoord(b, false /* isHorizontal */));
- }
- }
-
- float convertViewToScreenCoord(float viewCoord, boolean isHorizontal) {
- final int[] coordinatesOnScreen = new int[2];
- getLocationOnScreen(coordinatesOnScreen);
- return isHorizontal
- ? viewCoord + getTotalPaddingLeft() - getScrollX() + coordinatesOnScreen[0]
- : viewCoord + getTotalPaddingTop() - getScrollY() + coordinatesOnScreen[1];
- }
-
/**
* @return whether or not the cursor is visible (assuming this TextView is editable)
*
@@ -10368,17 +10338,6 @@ 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());