Merge "Update API lint baselines" into androidx-master-dev
diff --git a/appcompat/appcompat/api/res-1.3.0-alpha01.txt b/appcompat/appcompat/api/res-1.3.0-alpha01.txt
index cb8c1e3..cdb62d2 100644
--- a/appcompat/appcompat/api/res-1.3.0-alpha01.txt
+++ b/appcompat/appcompat/api/res-1.3.0-alpha01.txt
@@ -25,6 +25,7 @@
attr actionModeShareDrawable
attr actionModeSplitBackground
attr actionModeStyle
+attr actionModeTheme
attr actionModeWebSearchDrawable
attr actionOverflowButtonStyle
attr actionOverflowMenuStyle
diff --git a/appcompat/appcompat/api/res-current.txt b/appcompat/appcompat/api/res-current.txt
index cb8c1e3..cdb62d2 100644
--- a/appcompat/appcompat/api/res-current.txt
+++ b/appcompat/appcompat/api/res-current.txt
@@ -25,6 +25,7 @@
attr actionModeShareDrawable
attr actionModeSplitBackground
attr actionModeStyle
+attr actionModeTheme
attr actionModeWebSearchDrawable
attr actionOverflowButtonStyle
attr actionOverflowMenuStyle
diff --git a/appcompat/appcompat/src/main/res-public/values/public_attrs.xml b/appcompat/appcompat/src/main/res-public/values/public_attrs.xml
index bc81c6e..c943ed8 100644
--- a/appcompat/appcompat/src/main/res-public/values/public_attrs.xml
+++ b/appcompat/appcompat/src/main/res-public/values/public_attrs.xml
@@ -43,6 +43,7 @@
<public type="attr" name="actionModeShareDrawable"/>
<public type="attr" name="actionModeSplitBackground"/>
<public type="attr" name="actionModeStyle"/>
+ <public type="attr" name="actionModeTheme"/>
<public type="attr" name="actionModeWebSearchDrawable"/>
<public type="attr" name="actionOverflowButtonStyle"/>
<public type="attr" name="actionOverflowMenuStyle"/>
diff --git a/appcompat/appcompat/src/main/res/layout-v26/abc_screen_toolbar.xml b/appcompat/appcompat/src/main/res/layout-v26/abc_screen_toolbar.xml
index 25412ba..8d2ea46 100644
--- a/appcompat/appcompat/src/main/res/layout-v26/abc_screen_toolbar.xml
+++ b/appcompat/appcompat/src/main/res/layout-v26/abc_screen_toolbar.xml
@@ -46,7 +46,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
- android:theme="?attr/actionBarTheme"
+ android:theme="?attr/actionModeTheme"
style="?attr/actionModeStyle"/>
</androidx.appcompat.widget.ActionBarContainer>
diff --git a/appcompat/appcompat/src/main/res/layout/abc_action_mode_bar.xml b/appcompat/appcompat/src/main/res/layout/abc_action_mode_bar.xml
index 76f34f4..96783f8 100644
--- a/appcompat/appcompat/src/main/res/layout/abc_action_mode_bar.xml
+++ b/appcompat/appcompat/src/main/res/layout/abc_action_mode_bar.xml
@@ -21,5 +21,5 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
- android:theme="?attr/actionBarTheme"
+ android:theme="?attr/actionModeTheme"
style="?attr/actionModeStyle"/>
diff --git a/appcompat/appcompat/src/main/res/layout/abc_screen_toolbar.xml b/appcompat/appcompat/src/main/res/layout/abc_screen_toolbar.xml
index 5c0dbcd..64d7321 100644
--- a/appcompat/appcompat/src/main/res/layout/abc_screen_toolbar.xml
+++ b/appcompat/appcompat/src/main/res/layout/abc_screen_toolbar.xml
@@ -45,7 +45,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
- android:theme="?attr/actionBarTheme"
+ android:theme="?attr/actionModeTheme"
style="?attr/actionModeStyle"/>
</androidx.appcompat.widget.ActionBarContainer>
diff --git a/appcompat/appcompat/src/main/res/values/attrs.xml b/appcompat/appcompat/src/main/res/values/attrs.xml
index b1e0492..b4dbd1a 100644
--- a/appcompat/appcompat/src/main/res/values/attrs.xml
+++ b/appcompat/appcompat/src/main/res/values/attrs.xml
@@ -110,6 +110,10 @@
action bar. This will be inherited by any widget inflated
into the action bar. -->
<attr name="actionBarTheme" format="reference" />
+ <!-- Reference to a theme that should be used to inflate the
+ action bar in action mode. This will be inherited by any widget inflated
+ into the action bar. -->
+ <attr name="actionModeTheme" format="reference" />
<!-- Reference to a theme that should be used to inflate widgets
and layouts destined for the action bar. Most of the time
this will be a reference to the current theme, but when
@@ -165,7 +169,6 @@
<!-- PopupWindow style to use for action modes when showing as a window overlay. -->
<attr name="actionModePopupWindowStyle" format="reference"/>
-
<!-- =================== -->
<!-- Text styles -->
<!-- =================== -->
diff --git a/appcompat/appcompat/src/main/res/values/themes_base.xml b/appcompat/appcompat/src/main/res/values/themes_base.xml
index db97f83..dfe7bd4 100644
--- a/appcompat/appcompat/src/main/res/values/themes_base.xml
+++ b/appcompat/appcompat/src/main/res/values/themes_base.xml
@@ -213,6 +213,7 @@
<item name="actionDropDownStyle">@style/Widget.AppCompat.Spinner.DropDown.ActionBar</item>
<!-- Action Mode -->
+ <item name="actionModeTheme">?attr/actionBarTheme</item>
<item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
<item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
<item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
@@ -382,6 +383,7 @@
<item name="actionMenuTextColor">?android:attr/textColorPrimaryDisableOnly</item>
<!-- Action Mode -->
+ <item name="actionModeTheme">?attr/actionBarTheme</item>
<item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
<item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
<item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
@@ -525,6 +527,7 @@
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="actionBarWidgetTheme">@null</item>
<item name="actionBarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
+ <item name="actionModeTheme">?attr/actionBarTheme</item>
<!-- Panel attributes -->
<item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_dark</item>
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index a5da910..7659c54 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -148,6 +148,7 @@
prebuilts(LibraryGroups.WEBKIT, "1.3.0-alpha03")
ignore(LibraryGroups.WINDOW.group, "window-sidecar")
prebuilts(LibraryGroups.WINDOW, "1.0.0-alpha01")
+ .addStubs("window/stubs/window-sidecar-release-0.1.0-alpha01.aar")
ignore(LibraryGroups.WORK.group, "work-gcm")
ignore(LibraryGroups.WORK.group, "work-runtime-lint")
ignore(LibraryGroups.WORK.group, "work-rxjava3")
diff --git a/datastore/datastore-proto/build.gradle b/datastore/datastore-proto/build.gradle
index a84fdf8..6ad99a8 100644
--- a/datastore/datastore-proto/build.gradle
+++ b/datastore/datastore-proto/build.gradle
@@ -15,8 +15,8 @@
*/
import static androidx.build.dependencies.DependenciesKt.*
+
import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
import androidx.build.AndroidXExtension
import androidx.build.Publish
@@ -24,18 +24,36 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
-}
+ id("com.google.protobuf")
-android {
- buildTypes {
- debug {
- testCoverageEnabled = false // Breaks Kotlin compiler.
- }
- }
}
dependencies {
- api(KOTLIN_STDLIB)
+ api(project(":datastore:datastore-core"))
+ api(PROTOBUF_LITE)
+
+ testImplementation(TRUTH)
+ testImplementation(JUNIT)
+ testImplementation(project(":internal-testutils-truth"))
+}
+
+protobuf {
+ protoc {
+ artifact = PROTOBUF_COMPILER
+ }
+
+ // Generates the java proto-lite code for the protos in this project. See
+ // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
+ // for more information.
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ java {
+ option 'lite'
+ }
+ }
+ }
+ }
}
androidx {
@@ -45,4 +63,4 @@
inceptionYear = "2020"
description = "Android Proto DataStore"
url = AndroidXExtension.SUPPORT_URL
-}
+}
\ No newline at end of file
diff --git a/datastore/datastore-proto/src/main/java/androidx/datastore/protos/ProtoSerializer.kt b/datastore/datastore-proto/src/main/java/androidx/datastore/protos/ProtoSerializer.kt
new file mode 100644
index 0000000..29ee671
--- /dev/null
+++ b/datastore/datastore-proto/src/main/java/androidx/datastore/protos/ProtoSerializer.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.datastore.protos
+
+import androidx.datastore.DataStore
+import com.google.protobuf.ExtensionRegistryLite
+import com.google.protobuf.InvalidProtocolBufferException
+import com.google.protobuf.MessageLite
+import java.io.InputStream
+import java.io.OutputStream
+
+/** Serializer for using DataStore with protos. */
+internal class ProtoSerializer<T : MessageLite>(
+ /** The default proto of this type, obtained via {@code T.getDefaultInstance()} */
+ override val defaultValue: T,
+ /**
+ * Set the extensionRegistryLite to use when deserializing T. If no extension registry is
+ * necessary, use {@code ExtensionRegistryLite.getEmptyRegistry()}.
+ */
+ private val extensionRegistryLite: ExtensionRegistryLite
+) : DataStore.Serializer<T> {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun readFrom(input: InputStream): T {
+ try {
+ return defaultValue.parserForType.parseFrom(input, extensionRegistryLite) as T
+ } catch (invalidProtocolBufferException: InvalidProtocolBufferException) {
+ throw DataStore.Serializer.CorruptionException(
+ "Cannot read proto.", invalidProtocolBufferException
+ )
+ }
+ }
+
+ override fun writeTo(t: T, output: OutputStream) {
+ t.writeTo(output)
+ }
+}
\ No newline at end of file
diff --git a/datastore/datastore-proto/src/test/java/androidx/datastore/protos/ProtoSerializerTest.kt b/datastore/datastore-proto/src/test/java/androidx/datastore/protos/ProtoSerializerTest.kt
new file mode 100644
index 0000000..3f74bb0
--- /dev/null
+++ b/datastore/datastore-proto/src/test/java/androidx/datastore/protos/ProtoSerializerTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.datastore.protos
+
+import androidx.datastore.DataStore
+import org.junit.Test
+import androidx.testing.TestMessageProto.FooProto
+import androidx.testing.TestMessageProto.ExtendableProto
+import androidx.testing.TestMessageProto.ExtensionProto
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.ExtensionRegistryLite
+import org.junit.Rule
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ProtoSerializerTest {
+ @get:Rule
+ val temporaryFolder = TemporaryFolder()
+
+ @Test
+ fun testReadWriteProto() {
+ val file = temporaryFolder.newFile()
+
+ val fooProtoWithText = FooProto.newBuilder().setText("abc").build()
+
+ val protoSerializer = ProtoSerializer<FooProto>(
+ FooProto.getDefaultInstance(),
+ ExtensionRegistryLite.getEmptyRegistry()
+ )
+
+ file.outputStream().use {
+ protoSerializer.writeTo(fooProtoWithText, it)
+ }
+
+ val readProto = file.inputStream().use { protoSerializer.readFrom(it) }
+
+ assertThat(readProto).isEqualTo(fooProtoWithText)
+ }
+
+ @Test
+ fun testReadWriteProtoWithExtension() {
+ val file = temporaryFolder.newFile()
+
+ val registry = ExtensionRegistryLite.newInstance()
+ registry.add(ExtensionProto.extension)
+
+ val protoSerializer = ProtoSerializer<ExtendableProto>(
+ ExtendableProto.getDefaultInstance(),
+ registry
+ )
+
+ val extendedProto = ExtendableProto.newBuilder().setExtension(
+ ExtensionProto.extension,
+ ExtensionProto.newBuilder().setFoo(FooProto.newBuilder().setText("abc").build()).build()
+ ).build()
+
+ file.outputStream().use {
+ protoSerializer.writeTo(extendedProto, it)
+ }
+
+ val readProto = file.inputStream().use { protoSerializer.readFrom(it) }
+ assertThat(readProto).isEqualTo(extendedProto)
+ }
+
+ @Test
+ fun testThrowsCorruptionException() {
+ val file = temporaryFolder.newFile()
+ file.writeBytes(byteArrayOf(0x00, 0x02)) // Protos cannot start with 0x00.
+
+ val protoSerializer = ProtoSerializer<FooProto>(
+ FooProto.getDefaultInstance(),
+ ExtensionRegistryLite.getEmptyRegistry()
+ )
+
+ assertThrows<DataStore.Serializer.CorruptionException> {
+ file.inputStream().use { protoSerializer.readFrom(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/datastore/datastore-proto/src/test/proto/test.proto b/datastore/datastore-proto/src/test/proto/test.proto
new file mode 100644
index 0000000..5a79dd2
--- /dev/null
+++ b/datastore/datastore-proto/src/test/proto/test.proto
@@ -0,0 +1,27 @@
+// Protos for use in tests
+syntax = "proto2";
+
+package androidx.testing;
+
+option java_package = "androidx.testing";
+option java_outer_classname = "TestMessageProto";
+
+message FooProto {
+ optional string text = 1;
+ optional bool boolean = 2;
+ optional int32 integer = 3;
+ optional bytes bytes = 4;
+}
+
+message ExtendableProto {
+ extensions 1000 to max;
+}
+
+message ExtensionProto {
+ extend ExtendableProto {
+ optional ExtensionProto extension = 226219688;
+ }
+
+ optional FooProto foo = 1;
+}
+
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
index cc8172e..6490a091 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
@@ -118,39 +118,7 @@
} else {
assertThat(transition.capturedEpicenter).isEqualTo(epicenter)
}
- val targets = transition.trackedTargets
- val sb = StringBuilder()
- sb.append("Expected: [")
- .append(expected.size)
- .append("] {")
- var isFirst = true
- for (view in expected) {
- if (isFirst) {
- isFirst = false
- } else {
- sb.append(", ")
- }
- sb.append(view)
- }
- sb.append("}, but got: [")
- .append(targets.size)
- .append("] {")
- isFirst = true
- for (view in targets) {
- if (isFirst) {
- isFirst = false
- } else {
- sb.append(", ")
- }
- sb.append(view)
- }
- sb.append("}")
- val errorMessage = sb.toString()
-
- assertWithMessage(errorMessage).that(targets.size).isEqualTo(expected.size)
- for (view in expected) {
- assertWithMessage(errorMessage).that(targets.contains(view)).isTrue()
- }
+ assertThat(transition.trackedTargets).containsExactlyElementsIn(expected)
transition.clearTargets()
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
index 10a644c..01fa0cf 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -79,7 +79,7 @@
// enter transition
val fragment = setupInitialFragment()
val blue = activityRule.findBlue()
- val green = activityRule.findBlue()
+ val green = activityRule.findGreen()
// exit transition
fragmentManager.beginTransaction()
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
index 3409abe..16448d8 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
+import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
@@ -27,6 +28,7 @@
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import androidx.core.os.CancellationSignal;
+import androidx.core.view.OneShotPreDrawListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewGroupCompat;
@@ -262,13 +264,16 @@
// Now find the shared element transition if it exists
Object sharedElementTransition = null;
- HashMap<String, String> nameOverrides = new HashMap<>();
+ View firstOutEpicenterView = null;
+ boolean hasLastInEpicenter = false;
+ final Rect lastInEpicenterRect = new Rect();
+ ArrayList<View> sharedElementFirstOutViews = new ArrayList<>();
+ ArrayList<View> sharedElementLastInViews = new ArrayList<>();
for (final TransitionInfo transitionInfo : transitionInfos) {
boolean hasSharedElementTransition = transitionInfo.hasSharedElementTransition();
if (hasSharedElementTransition) {
sharedElementTransition = transitionImpl.cloneTransition(
transitionInfo.getSharedElementTransition());
- transitionImpl.addTarget(sharedElementTransition, nonExistentView);
Fragment sharedElementFragment = transitionInfo.getOperation().getFragment();
// A pop means returning from the target names to the source names
// so we have to swap the source/target sets
@@ -278,9 +283,70 @@
ArrayList<String> exitingNames = isPop
? sharedElementFragment.getSharedElementSourceNames()
: sharedElementFragment.getSharedElementTargetNames();
- for (int index = 0; index < enteringNames.size(); index++) {
- nameOverrides.put(enteringNames.get(index), exitingNames.get(index));
+ if (firstOut != null) {
+ // Capture all of the Views from the firstOut fragment that are
+ // part of the shared element transition
+ ArrayMap<String, View> firstOutViews = new ArrayMap<>();
+ findNamedViews(firstOutViews, firstOut.getFragment().mView);
+ // TODO call through to onMapSharedElements
+ firstOutViews.retainAll(exitingNames);
+
+ // Find all views under the shared element views and add them
+ // to the shared element transition
+ for (View sharedElementView : firstOutViews.values()) {
+ captureTransitioningViews(sharedElementFirstOutViews,
+ sharedElementView);
+ }
+
+ // Compute the epicenter of the firstOut transition
+ if (!exitingNames.isEmpty()) {
+ String epicenterViewName = exitingNames.get(0);
+ firstOutEpicenterView = firstOutViews.get(epicenterViewName);
+ transitionImpl.setEpicenter(sharedElementTransition,
+ firstOutEpicenterView);
+ }
}
+ if (lastIn != null) {
+ // Capture all of the Views from the lastIn fragment that are
+ // part of the shared element transition
+ ArrayMap<String, View> lastInViews = new ArrayMap<>();
+ findNamedViews(lastInViews, lastIn.getFragment().mView);
+ // TODO call through to onMapSharedElements
+ lastInViews.retainAll(enteringNames);
+
+ // Find all views under the shared element views and add them
+ // to the shared element transition
+ for (View sharedElementView : lastInViews.values()) {
+ captureTransitioningViews(sharedElementLastInViews,
+ sharedElementView);
+ }
+
+ // Compute the epicenter of the firstOut transition
+ if (!enteringNames.isEmpty()) {
+ String epicenterViewName = enteringNames.get(0);
+ final View lastInEpicenterView = lastInViews.get(epicenterViewName);
+ if (lastInEpicenterView != null) {
+ hasLastInEpicenter = true;
+ // We can't set the epicenter here directly since the View might
+ // not have been laid out as of yet, so instead we set a Rect as
+ // the epicenter and compute the bounds one frame later
+ final FragmentTransitionImpl impl = transitionImpl;
+ OneShotPreDrawListener.add(getContainer(), new Runnable() {
+ @Override
+ public void run() {
+ impl.getBoundsOnScreen(lastInEpicenterView,
+ lastInEpicenterRect);
+ }
+ });
+ }
+ }
+ }
+ // Now set the transition's targets
+ ArrayList<View> sharedElementTargets = new ArrayList<>();
+ sharedElementTargets.add(nonExistentView);
+ sharedElementTargets.addAll(sharedElementFirstOutViews);
+ sharedElementTargets.addAll(sharedElementLastInViews);
+ transitionImpl.addTargets(sharedElementTransition, sharedElementTargets);
}
}
ArrayList<View> enteringViews = new ArrayList<>();
@@ -309,31 +375,21 @@
captureTransitioningViews(transitioningViews,
transitionInfo.getOperation().getFragment().mView);
if (involvedInSharedElementTransition) {
- ArrayMap<String, View> sharedElementViews = new ArrayMap<>();
- findNamedViews(sharedElementViews,
- transitionInfo.getOperation().getFragment().mView);
- // TODO call through to onMapSharedElements
- if (operation == firstOut) {
- sharedElementViews.retainAll(nameOverrides.values());
- } else {
- sharedElementViews.retainAll(nameOverrides.keySet());
- }
// Remove all of the shared element views from the transition
- // by first finding all of the transitioning views under the
- // selected shared element views
- ArrayList<View> sharedElementTransitioningViews = new ArrayList<>();
- for (View sharedElementView : sharedElementViews.values()) {
- captureTransitioningViews(sharedElementTransitioningViews,
- sharedElementView);
+ if (operation == firstOut) {
+ transitioningViews.removeAll(sharedElementFirstOutViews);
+ } else {
+ transitioningViews.removeAll(sharedElementLastInViews);
}
- transitioningViews.removeAll(sharedElementTransitioningViews);
- // And add them to the shared element transition
- transitionImpl.addTargets(sharedElementTransition,
- new ArrayList<>(sharedElementTransitioningViews));
}
transitionImpl.addTargets(transition, transitioningViews);
if (transitionInfo.getOperation().getType().equals(Operation.Type.ADD)) {
enteringViews.addAll(transitioningViews);
+ if (hasLastInEpicenter) {
+ transitionImpl.setEpicenter(transition, lastInEpicenterRect);
+ }
+ } else {
+ transitionImpl.setEpicenter(transition, firstOutEpicenterView);
}
// Now determine how this transition should be merged together
if (transitionInfo.isOverlapAllowed()) {
diff --git a/samples/Support7Demos/src/main/AndroidManifest.xml b/samples/Support7Demos/src/main/AndroidManifest.xml
index 0433f43..4e19e24 100644
--- a/samples/Support7Demos/src/main/AndroidManifest.xml
+++ b/samples/Support7Demos/src/main/AndroidManifest.xml
@@ -228,7 +228,7 @@
<activity android:name=".app.ActionBarActionMode"
android:label="@string/action_bar_action_mode"
- android:theme="@style/Theme.Custom">
+ android:theme="@style/Theme.Custom.WithActionMode">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.example.android.supportv7.SAMPLE_CODE" />
@@ -331,7 +331,7 @@
<activity android:name=".app.ToolbarActionMode"
android:label="@string/toolbar_action_mode"
- android:theme="@style/Theme.AppCompat.Light.NoActionBar">
+ android:theme="@style/Theme.Custom.NoActionBar.WithActionMode">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.example.android.supportv7.SAMPLE_CODE" />
diff --git a/samples/Support7Demos/src/main/res/values/colors.xml b/samples/Support7Demos/src/main/res/values/colors.xml
index 49cc256..332e515 100644
--- a/samples/Support7Demos/src/main/res/values/colors.xml
+++ b/samples/Support7Demos/src/main/res/values/colors.xml
@@ -27,4 +27,5 @@
<color name="color_sky_day">#90F0FF</color>
<color name="color_sky_night">#3050CF</color>
<color name="color_sky">@color/color_sky_day</color>
+ <color name="color_grass">#20D024</color>
</resources>
diff --git a/samples/Support7Demos/src/main/res/values/styles.xml b/samples/Support7Demos/src/main/res/values/styles.xml
index 0892fd3..7a7c66a 100644
--- a/samples/Support7Demos/src/main/res/values/styles.xml
+++ b/samples/Support7Demos/src/main/res/values/styles.xml
@@ -33,6 +33,20 @@
<item name="android:textColorLink">@color/link_color</item>
</style>
+ <style name="ThemeOverlay.ActionMode" parent="ThemeOverlay.AppCompat.ActionBar">
+ <item name="colorControlNormal">#FF0000</item>
+ </style>
+
+ <style name="Theme.Custom.WithActionMode">
+ <item name="actionModeBackground">@color/color_grass</item>
+ <item name="actionModeTheme">@style/ThemeOverlay.ActionMode</item>
+ </style>
+
+ <style name="Theme.Custom.NoActionBar.WithActionMode" parent="Theme.AppCompat.Light.NoActionBar">
+ <item name="actionModeBackground">@color/color_sky_day</item>
+ <item name="actionModeTheme">@style/ThemeOverlay.ActionMode</item>
+ </style>
+
<style name="Theme.SampleMediaRouter" parent="Theme.AppCompat">
<item name="colorPrimary">#fff44336</item>
<item name="colorPrimaryDark">#d32f2f</item>
diff --git a/ui/ui-core/api/0.1.0-dev12.txt b/ui/ui-core/api/0.1.0-dev12.txt
index 78ceed6..cfca0a1 100644
--- a/ui/ui-core/api/0.1.0-dev12.txt
+++ b/ui/ui-core/api/0.1.0-dev12.txt
@@ -964,6 +964,10 @@
method public default void onStop(androidx.ui.unit.PxPosition velocity);
}
+ public final class DragSlopExceededGestureFilterKt {
+ method public static androidx.ui.core.Modifier dragSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
+ }
+
public final class GestureUtilsKt {
method public static boolean anyPointersInBounds(java.util.List<androidx.ui.core.PointerInputChange>, androidx.ui.unit.IntPxSize bounds);
}
@@ -1029,10 +1033,6 @@
method public static androidx.ui.core.Modifier tapGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.unit.PxPosition,kotlin.Unit> onTap);
}
- public final class TouchSlopExceededGestureFilterKt {
- method public static androidx.ui.core.Modifier touchSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onTouchSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
- }
-
}
package androidx.ui.core.gesture.customevents {
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 78ceed6..cfca0a1 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -964,6 +964,10 @@
method public default void onStop(androidx.ui.unit.PxPosition velocity);
}
+ public final class DragSlopExceededGestureFilterKt {
+ method public static androidx.ui.core.Modifier dragSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
+ }
+
public final class GestureUtilsKt {
method public static boolean anyPointersInBounds(java.util.List<androidx.ui.core.PointerInputChange>, androidx.ui.unit.IntPxSize bounds);
}
@@ -1029,10 +1033,6 @@
method public static androidx.ui.core.Modifier tapGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.unit.PxPosition,kotlin.Unit> onTap);
}
- public final class TouchSlopExceededGestureFilterKt {
- method public static androidx.ui.core.Modifier touchSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onTouchSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
- }
-
}
package androidx.ui.core.gesture.customevents {
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt
index 1ec43c6..b7a6c6c 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt
@@ -966,6 +966,10 @@
method public default void onStop(androidx.ui.unit.PxPosition velocity);
}
+ public final class DragSlopExceededGestureFilterKt {
+ method public static androidx.ui.core.Modifier dragSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
+ }
+
public final class GestureUtilsKt {
method public static boolean anyPointersInBounds(java.util.List<androidx.ui.core.PointerInputChange>, androidx.ui.unit.IntPxSize bounds);
}
@@ -1031,10 +1035,6 @@
method public static androidx.ui.core.Modifier tapGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.unit.PxPosition,kotlin.Unit> onTap);
}
- public final class TouchSlopExceededGestureFilterKt {
- method public static androidx.ui.core.Modifier touchSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onTouchSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
- }
-
}
package androidx.ui.core.gesture.customevents {
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 1ec43c6..b7a6c6c 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -966,6 +966,10 @@
method public default void onStop(androidx.ui.unit.PxPosition velocity);
}
+ public final class DragSlopExceededGestureFilterKt {
+ method public static androidx.ui.core.Modifier dragSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
+ }
+
public final class GestureUtilsKt {
method public static boolean anyPointersInBounds(java.util.List<androidx.ui.core.PointerInputChange>, androidx.ui.unit.IntPxSize bounds);
}
@@ -1031,10 +1035,6 @@
method public static androidx.ui.core.Modifier tapGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.unit.PxPosition,kotlin.Unit> onTap);
}
- public final class TouchSlopExceededGestureFilterKt {
- method public static androidx.ui.core.Modifier touchSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onTouchSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
- }
-
}
package androidx.ui.core.gesture.customevents {
diff --git a/ui/ui-core/api/restricted_0.1.0-dev12.txt b/ui/ui-core/api/restricted_0.1.0-dev12.txt
index 17a1e175..98726d2 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev12.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev12.txt
@@ -1020,6 +1020,10 @@
method public default void onStop(androidx.ui.unit.PxPosition velocity);
}
+ public final class DragSlopExceededGestureFilterKt {
+ method public static androidx.ui.core.Modifier dragSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
+ }
+
public final class GestureUtilsKt {
method public static boolean anyPointersInBounds(java.util.List<androidx.ui.core.PointerInputChange>, androidx.ui.unit.IntPxSize bounds);
}
@@ -1085,10 +1089,6 @@
method public static androidx.ui.core.Modifier tapGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.unit.PxPosition,kotlin.Unit> onTap);
}
- public final class TouchSlopExceededGestureFilterKt {
- method public static androidx.ui.core.Modifier touchSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onTouchSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
- }
-
}
package androidx.ui.core.gesture.customevents {
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 17a1e175..98726d2 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -1020,6 +1020,10 @@
method public default void onStop(androidx.ui.unit.PxPosition velocity);
}
+ public final class DragSlopExceededGestureFilterKt {
+ method public static androidx.ui.core.Modifier dragSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
+ }
+
public final class GestureUtilsKt {
method public static boolean anyPointersInBounds(java.util.List<androidx.ui.core.PointerInputChange>, androidx.ui.unit.IntPxSize bounds);
}
@@ -1085,10 +1089,6 @@
method public static androidx.ui.core.Modifier tapGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.unit.PxPosition,kotlin.Unit> onTap);
}
- public final class TouchSlopExceededGestureFilterKt {
- method public static androidx.ui.core.Modifier touchSlopExceededGestureFilter(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onTouchSlopExceeded, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null);
- }
-
}
package androidx.ui.core.gesture.customevents {
diff --git a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt
index b9a26e3..4b207f9 100644
--- a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt
+++ b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt
@@ -35,7 +35,7 @@
import androidx.ui.core.demos.gestures.RawDragGestureDetectorDemo
import androidx.ui.core.demos.gestures.ScaleGestureDetectorDemo
import androidx.ui.core.demos.gestures.TouchSlopDragGestureDetectorDemo
-import androidx.ui.core.demos.gestures.TouchSlopExceededGestureDetectorDemo
+import androidx.ui.core.demos.gestures.DragSlopExceededGestureFilterDemo
import androidx.ui.core.demos.keyinput.KeyInputDemo
import androidx.ui.core.demos.viewinterop.ViewInComposeDemo
@@ -50,8 +50,8 @@
ComposableDemo("TouchSlopDragGestureDetector") { TouchSlopDragGestureDetectorDemo() },
ComposableDemo("LongPressDragGestureDetector") { LongPressDragGestureDetectorDemo() },
ComposableDemo("RawDragGestureDetector") { RawDragGestureDetectorDemo() },
- ComposableDemo("TouchSlopExceededGestureDetector") {
- TouchSlopExceededGestureDetectorDemo()
+ ComposableDemo("DragSlopExceededGestureFilter") {
+ DragSlopExceededGestureFilterDemo()
},
ComposableDemo("ScaleGestureDetector") { ScaleGestureDetectorDemo() }
)),
diff --git a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/TouchSlopExceededGestureDetectorDemo.kt b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/DragSlopExceededGestureFilterDemo.kt
similarity index 66%
rename from ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/TouchSlopExceededGestureDetectorDemo.kt
rename to ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/DragSlopExceededGestureFilterDemo.kt
index 7a8eb05..82999a0 100644
--- a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/TouchSlopExceededGestureDetectorDemo.kt
+++ b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/DragSlopExceededGestureFilterDemo.kt
@@ -21,19 +21,21 @@
import androidx.ui.core.Alignment
import androidx.ui.core.Direction
import androidx.ui.core.Modifier
-import androidx.ui.core.gesture.touchSlopExceededGestureFilter
+import androidx.ui.core.gesture.dragSlopExceededGestureFilter
import androidx.ui.foundation.Box
+import androidx.ui.foundation.Text
import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
import androidx.ui.layout.fillMaxSize
import androidx.ui.layout.preferredSize
import androidx.ui.layout.wrapContentSize
import androidx.ui.unit.dp
/**
- * Simple demo that shows off TouchSlopExceededGestureDetector.
+ * Simple demo that demonstrates the functionality of [dragSlopExceededGestureFilter].
*/
@Composable
-fun TouchSlopExceededGestureDetectorDemo() {
+fun DragSlopExceededGestureFilterDemo() {
val verticalColor = Color(0xfff44336)
val horizontalColor = Color(0xff2196f3)
@@ -76,11 +78,24 @@
horizontalColor
}
- Box(
- Modifier.fillMaxSize()
- .wrapContentSize(Alignment.Center)
- .touchSlopExceededGestureFilter(onTouchSlopExceeded, canDrag)
- .preferredSize(96.dp),
- backgroundColor = color
- )
+ Column {
+ Text(
+ "Demonstrates functionality of Modifier.dragSlopExceededGestureFilter, which calls " +
+ "its callback when touch slop has been exceeded by the average distance" +
+ " change of all pointers. This also demonstrates controlling which" +
+ " directions can be dragged to exceed touch slop."
+ )
+ Text(
+ "When red, a drag on the box will turn the box blue only when you drag up or down on" +
+ " the screen. When blue, a drag on the box will turn the box red when you" +
+ " drag to the right or left."
+ )
+ Box(
+ Modifier.fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .dragSlopExceededGestureFilter(onTouchSlopExceeded, canDrag)
+ .preferredSize(96.dp),
+ backgroundColor = color
+ )
+ }
}
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/gesture/DragGestureFilter.kt b/ui/ui-core/src/main/java/androidx/ui/core/gesture/DragGestureFilter.kt
index 584dd61..c178e08 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/gesture/DragGestureFilter.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/gesture/DragGestureFilter.kt
@@ -67,7 +67,7 @@
// DragObserver.onStart but if the pointer doesn't move and releases, (or if cancel is called)
// The appropriate callbacks to DragObserver will not be called.
rawDragGestureFilter(glue.rawDragObserver, glue::enabledOrStarted)
- .touchSlopExceededGestureFilter(glue::enableDrag, canDrag)
+ .dragSlopExceededGestureFilter(glue::enableDrag, canDrag)
.rawPressStartGestureFilter(
glue::startDrag,
startDragImmediately,
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/gesture/DragSlopExceededGestureFilter.kt b/ui/ui-core/src/main/java/androidx/ui/core/gesture/DragSlopExceededGestureFilter.kt
new file mode 100644
index 0000000..51cce8f
--- /dev/null
+++ b/ui/ui-core/src/main/java/androidx/ui/core/gesture/DragSlopExceededGestureFilter.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.core.gesture
+
+import androidx.compose.remember
+import androidx.ui.core.DensityAmbient
+import androidx.ui.core.Direction
+import androidx.ui.core.Modifier
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
+import androidx.ui.core.changedToUpIgnoreConsumed
+import androidx.ui.core.composed
+import androidx.ui.core.pointerinput.PointerInputFilter
+import androidx.ui.core.positionChange
+import androidx.ui.geometry.Offset
+import androidx.ui.unit.IntPxSize
+import androidx.ui.unit.Px
+import androidx.ui.unit.PxPosition
+
+/**
+ * This gesture filter detects when the average distance change of all pointers surpasses touch
+ * slop.
+ *
+ * The value of touch slop is currently defined internally as the constant [TouchSlop].
+ *
+ * @param onDragSlopExceeded Called when touch slop is exceeded in a supported direction. See
+ * [canDrag].
+ * @param canDrag Set to limit the directions under which touch slop can be exceeded. Return true
+ * if you want a drag to be started due to the touch slop being surpassed in the given [Direction].
+ * If [canDrag] is not provided, touch slop will be able to be exceeded in all directions.
+ */
+fun Modifier.dragSlopExceededGestureFilter(
+ onDragSlopExceeded: () -> Unit,
+ canDrag: ((Direction) -> Boolean)? = null
+): Modifier = composed {
+ val touchSlop = with(DensityAmbient.current) { TouchSlop.toPx() }
+ val filter = remember { DragSlopExceededGestureFilter(touchSlop) }
+ filter.canDrag = canDrag
+ filter.>
+ PointerInputModifierImpl(filter)
+}
+
+internal class DragSlopExceededGestureFilter(
+ private val touchSlop: Px
+) : PointerInputFilter() {
+ private var dxForPass = 0f
+ private var dyForPass = 0f
+ private var dxUnderSlop = 0f
+ private var dyUnderSlop = 0f
+ private var passedSlop = false
+
+ var canDrag: ((Direction) -> Boolean)? = null
+ var onDragSlopExceeded: () -> Unit = {}
+
+ override fun onPointerInput(
+ changes: List<PointerInputChange>,
+ pass: PointerEventPass,
+ bounds: IntPxSize
+ ): List<PointerInputChange> {
+
+ if (!passedSlop &&
+ (pass == PointerEventPass.PostUp || pass == PointerEventPass.PostDown)
+ ) {
+ // Get current average change.
+ val averagePositionChange = getAveragePositionChange(changes)
+ val dx = averagePositionChange.dx
+ val dy = averagePositionChange.dy
+
+ // Track changes during postUp and during postDown. This allows for fancy dragging
+ // due to a parent being dragged and will likely be removed.
+ // TODO(b/157087973): Likely remove this two pass complexity.
+ if (pass == PointerEventPass.PostUp) {
+ dxForPass = dx
+ dyForPass = dy
+ dxUnderSlop += dx
+ dyUnderSlop += dy
+ } else {
+ dxUnderSlop += dx - dxForPass
+ dyUnderSlop += dy - dyForPass
+ }
+
+ // Map the distance to the direction enum for a call to canDrag.
+ val directionX = averagePositionChange.horizontalDirection()
+ val directionY = averagePositionChange.verticalDirection()
+
+ val canDragX = directionX != null && canDrag?.invoke(directionX) ?: true
+ val canDragY = directionY != null && canDrag?.invoke(directionY) ?: true
+
+ val passedSlopX = canDragX && Math.abs(dxUnderSlop) > touchSlop.value
+ val passedSlopY = canDragY && Math.abs(dyUnderSlop) > touchSlop.value
+
+ if (passedSlopX || passedSlopY) {
+ passedSlop = true
+ onDragSlopExceeded.invoke()
+ } else {
+ // If we have passed slop in a direction that we can't drag in, we should reset
+ // our tracking back to zero so that a user doesn't have to later scroll the slop
+ // + the extra distance they scrolled in the wrong direction.
+ if (!canDragX &&
+ ((directionX == Direction.LEFT && dxUnderSlop < 0) ||
+ (directionX == Direction.RIGHT && dxUnderSlop > 0))
+ ) {
+ dxUnderSlop = 0f
+ }
+ if (!canDragY &&
+ ((directionY == Direction.UP && dyUnderSlop < 0) ||
+ (directionY == Direction.DOWN && dyUnderSlop > 0))
+ ) {
+ dyUnderSlop = 0f
+ }
+ }
+ }
+
+ if (pass == PointerEventPass.PostDown &&
+ changes.all { it.changedToUpIgnoreConsumed() }
+ ) {
+ reset()
+ }
+ return changes
+ }
+
+ override fun onCancel() {
+ reset()
+ }
+
+ private fun reset() {
+ passedSlop = false
+ dxForPass = 0f
+ dyForPass = 0f
+ dxUnderSlop = 0f
+ dyUnderSlop = 0f
+ }
+}
+
+/**
+ * Get's the average distance change of all pointers as an Offset.
+ */
+private fun getAveragePositionChange(changes: List<PointerInputChange>): Offset {
+ val sum = changes.fold(PxPosition.Origin) { sum, change ->
+ sum + change.positionChange()
+ }
+ val sizeAsFloat = changes.size.toFloat()
+ // TODO(b/148980115): Once PxPosition is removed, sum will be an Offset, and this line can
+ // just be straight division.
+ return Offset(sum.x.value / sizeAsFloat, sum.y.value / sizeAsFloat)
+}
+
+/**
+ * Maps an [Offset] value to a horizontal [Direction].
+ */
+private fun Offset.horizontalDirection() =
+ when {
+ this.dx < 0f -> Direction.LEFT
+ this.dx > 0f -> Direction.RIGHT
+ else -> null
+ }
+
+/**
+ * Maps a [Offset] value to a vertical [Direction].
+ */
+private fun Offset.verticalDirection() =
+ when {
+ this.dy < 0f -> Direction.UP
+ this.dy > 0f -> Direction.DOWN
+ else -> null
+ }
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureFilter.kt b/ui/ui-core/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureFilter.kt
deleted file mode 100644
index 88c64ab..0000000
--- a/ui/ui-core/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureFilter.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.ui.core.gesture
-
-import androidx.compose.remember
-import androidx.ui.core.DensityAmbient
-import androidx.ui.core.Direction
-import androidx.ui.core.Modifier
-import androidx.ui.core.PointerEventPass
-import androidx.ui.core.PointerId
-import androidx.ui.core.PointerInputChange
-import androidx.ui.core.changedToDownIgnoreConsumed
-import androidx.ui.core.changedToUpIgnoreConsumed
-import androidx.ui.core.composed
-import androidx.ui.core.pointerinput.PointerInputFilter
-import androidx.ui.core.positionChange
-import androidx.ui.unit.IntPxSize
-import androidx.ui.unit.Px
-import androidx.ui.util.fastForEach
-
-/**
- * This gesture filter detects when at least one pointer has moved far enough to exceed touch slop.
- *
- * The value of touch slop is currently defined internally as the constant [TouchSlop].
- *
- * @param onTouchSlopExceeded Lamba that will be called when touch slop by at least 1 pointer has
- * been exceeded in a supported direction. See [canDrag].
- * @param canDrag Set to limit the directions under which touch slop can be exceeded. Return true
- * if you want a drag to be started due to the touch slop being surpassed in the given [Direction].
- * If [canDrag] is not provided, touch slop will be able to be exceeded in all directions.
- */
-fun Modifier.touchSlopExceededGestureFilter(
- onTouchSlopExceeded: () -> Unit,
- canDrag: ((Direction) -> Boolean)? = null
-): Modifier = composed {
- val touchSlop = with(DensityAmbient.current) { TouchSlop.toPx() }
- val filter = remember { TouchSlopExceededGestureFilter(touchSlop) }
- filter.canDrag = canDrag
- filter.>
- PointerInputModifierImpl(filter)
-}
-
-// TODO(shepshapard): Shouldn't touchSlop be Px and not IntPx? What if the density bucket of the
-// device is not a whole number?
-internal class TouchSlopExceededGestureFilter(
- private val touchSlop: Px
-) : PointerInputFilter() {
- private val pointerTrackers: MutableMap<PointerId, PointerTrackingData> = mutableMapOf()
- private var passedSlop = false
-
- var canDrag: ((Direction) -> Boolean)? = null
- var onTouchSlopExceeded: () -> Unit = {}
-
- override fun onPointerInput(
- changes: List<PointerInputChange>,
- pass: PointerEventPass,
- bounds: IntPxSize
- ): List<PointerInputChange> {
- if (pass == PointerEventPass.PostUp) {
- changes.fastForEach {
- if (it.changedToUpIgnoreConsumed()) {
- pointerTrackers.remove(it.id)
- } else if (it.changedToDownIgnoreConsumed()) {
- pointerTrackers[it.id] = PointerTrackingData()
- }
- }
-
- if (!passedSlop) {
- pointerTrackers.forEach {
- it.value.dxForPass = 0f
- it.value.dyForPass = 0f
- }
- }
- }
-
- if (!passedSlop &&
- (pass == PointerEventPass.PostUp || pass == PointerEventPass.PostDown)
- ) {
-
- changes.filter { it.current.down && !it.changedToDownIgnoreConsumed() }.fastForEach {
-
- if (!passedSlop) {
-
- // TODO(shepshapard): handle the case that the pointerTrackingData is null,
- // either with an exception or a logged error, or something else. It should
- // only ever be able to be null at this point if we received a "move"
- // change for a pointer before we received an change that the pointer
- // became "down".
- val pointerTracker: PointerTrackingData = pointerTrackers[it.id]!!
-
- val positionChanged = it.positionChange()
- val dx = positionChanged.x.value
- val dy = positionChanged.y.value
-
- // TODO(shepshapard): I believe the logic in this block could be simplified
- // to be much more clear. Will need to revisit. The need to make
- // improvements may be rendered obsolete with upcoming changes however.
-
- val directionX = when {
- dx == 0f -> null
- dx < 0f -> Direction.LEFT
- else -> Direction.RIGHT
- }
- val directionY = when {
- dy == 0f -> null
- dy < 0f -> Direction.UP
- else -> Direction.DOWN
- }
-
- val internalCanDrag = canDrag
-
- val canDragX =
- directionX != null &&
- (internalCanDrag == null || internalCanDrag.invoke(directionX))
- val canDragY =
- directionY != null &&
- (internalCanDrag == null || internalCanDrag.invoke(directionY))
-
- if (pass == PointerEventPass.PostUp) {
- pointerTracker.dxForPass = dx
- pointerTracker.dyForPass = dy
- pointerTracker.dxUnderSlop += dx
- pointerTracker.dyUnderSlop += dy
- } else {
- pointerTracker.dxUnderSlop += dx - pointerTracker.dxForPass
- pointerTracker.dyUnderSlop += dy - pointerTracker.dyForPass
- }
-
- val passedSlopX =
- canDragX && Math.abs(pointerTracker.dxUnderSlop) > touchSlop.value
- val passedSlopY =
- canDragY && Math.abs(pointerTracker.dyUnderSlop) > touchSlop.value
-
- if (passedSlopX || passedSlopY) {
- passedSlop = true
- onTouchSlopExceeded.invoke()
- } else {
- if (!canDragX &&
- ((directionX == Direction.LEFT &&
- pointerTracker.dxUnderSlop < 0) ||
- (directionX == Direction.RIGHT &&
- pointerTracker.dxUnderSlop > 0))
- ) {
- pointerTracker.dxUnderSlop = 0f
- }
- if (!canDragY &&
- ((directionY == Direction.LEFT &&
- pointerTracker.dyUnderSlop < 0) ||
- (directionY == Direction.DOWN &&
- pointerTracker.dyUnderSlop > 0))
- ) {
- pointerTracker.dyUnderSlop = 0f
- }
- }
- }
- }
- }
-
- if (passedSlop &&
- pass == PointerEventPass.PostDown &&
- changes.all { it.changedToUpIgnoreConsumed() }
- ) {
- passedSlop = false
- }
- return changes
- }
-
- override fun onCancel() {
- pointerTrackers.clear()
- passedSlop = false
- }
-
- internal data class PointerTrackingData(
- var dxUnderSlop: Float = 0f,
- var dyUnderSlop: Float = 0f,
- var dxForPass: Float = 0f,
- var dyForPass: Float = 0f
- )
-}
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt b/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt
index 768741b..2999035 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt
@@ -411,6 +411,7 @@
}
if (relevantChanges.isEmpty()) {
+ // If there are not relevant changes, there is nothing to process so return.
return false
}
diff --git a/ui/ui-core/src/test/java/androidx/ui/core/gesture/TouchSlopExceededGestureFilterTest.kt b/ui/ui-core/src/test/java/androidx/ui/core/gesture/DragSlopExceededGestureFilterTest.kt
similarity index 86%
rename from ui/ui-core/src/test/java/androidx/ui/core/gesture/TouchSlopExceededGestureFilterTest.kt
rename to ui/ui-core/src/test/java/androidx/ui/core/gesture/DragSlopExceededGestureFilterTest.kt
index 1fb74f0..c5e83ab 100644
--- a/ui/ui-core/src/test/java/androidx/ui/core/gesture/TouchSlopExceededGestureFilterTest.kt
+++ b/ui/ui-core/src/test/java/androidx/ui/core/gesture/DragSlopExceededGestureFilterTest.kt
@@ -46,29 +46,29 @@
private const val TestTouchSlop = 10
@RunWith(JUnit4::class)
-class TouchSlopExceededGestureFilterTest {
+class DragSlopExceededGestureFilterTest {
- private val onTouchSlopExceeded: () -> Unit = { onTouchSlopExceededCallCount++ }
+ private val onDragSlopExceeded: () -> Unit = { onDragSlopExceededCallCount++ }
private val canDrag: (Direction) -> Boolean = { direction ->
canDragDirections.add(direction)
canDragReturn
}
- private var onTouchSlopExceededCallCount: Int = 0
+ private var onDragSlopExceededCallCount: Int = 0
private var canDragReturn = false
private var canDragDirections: MutableList<Direction> = mutableListOf()
- private lateinit var filter: TouchSlopExceededGestureFilter
+ private lateinit var filter: DragSlopExceededGestureFilter
private val TinyNum = .01f
@Before
fun setup() {
- >
+ >
canDragReturn = true
canDragDirections.clear()
filter =
- TouchSlopExceededGestureFilter(TestTouchSlop.px)
+ DragSlopExceededGestureFilter(TestTouchSlop.px)
filter.canDrag = canDrag
- filter.>
+ filter.>
}
// Verify the circumstances under which canDrag should not be called.
@@ -269,7 +269,7 @@
)
filter::onPointerInput.invokeOverAllPasses(move)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(0)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
@@ -286,7 +286,7 @@
)
filter::onPointerInput.invokeOverAllPasses(move)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(0)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
@@ -300,7 +300,7 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(0)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
@@ -327,7 +327,7 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
// TODO(b/129701831): This test assumes that if a pointer moves by slop in both x and y, we are
@@ -379,7 +379,7 @@
change = change.moveTo(90.milliseconds, -slop, -slop)
filter::onPointerInput.invokeOverAllPasses(change)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(0)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
// Verify the circumstances under which onTouchSlopExceeded should be called.
@@ -397,7 +397,7 @@
)
filter::onPointerInput.invokeOverAllPasses(move)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
@@ -418,7 +418,7 @@
)
filter::onPointerInput.invokeOverAllPasses(move2)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
@@ -437,7 +437,7 @@
event = event.moveBy(Duration(milliseconds = 10), 0f, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(event)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
@@ -449,7 +449,7 @@
val move = down.moveBy(Duration(milliseconds = 100), beyondSlop, 0f)
filter::onPointerInput.invokeOverAllPasses(move)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
@@ -465,7 +465,7 @@
beyondSlop
)
// Sanity check that onTouchSlopExceeded has not been called.
- assertThat(onTouchSlopExceededCallCount).isEqualTo(0)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
canDragReturn = true
filter::onPointerInput.invokeOverAllPasses(change)
@@ -476,11 +476,11 @@
)
filter::onPointerInput.invokeOverAllPasses(change)
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
- fun onPointerInputChanges_2PointsMoveInOpposite_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_2PointsMoveInOpposite_onTouchSlopExceededNotCalled() {
// Arrange
@@ -506,11 +506,11 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
- fun onPointerInputChanges_3PointsMoveAverage0_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_3PointsMoveAverage0_onDragSlopExceededNotCalled() {
// Arrange
@@ -544,44 +544,113 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
- fun onPointerInputChanges_5Points1MoveBeyondSlop_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_2Points1MoveJustBeyondSlop_onDragSlopExceededNotCalled() {
// Arrange
val beyondSlop = TestTouchSlop + TinyNum
- val pointers = arrayOf(down(0), down(1), down(2), down(3), down(4))
- filter::onPointerInput.invokeOverAllPasses(*pointers)
+ var pointer1 = down(0)
+ var pointer2 = down(1)
+ filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Act
// These movements average to no movement.
- for (i in 0..3) {
- pointers[i] = pointers[i].moveBy(
+
+ pointer1 =
+ pointer1.moveBy(
Duration(milliseconds = 100),
0f,
0f
)
- }
- pointers[4] =
- pointers[4].moveBy(
+ pointer2 =
+ pointer2.moveBy(
Duration(milliseconds = 100),
beyondSlop * -1,
0f
)
- filter::onPointerInput.invokeOverAllPasses(*pointers)
+ filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
- fun onPointerInputChanges_1PointMovesBeyondSlopAndThenManyTimes_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_2Points1MoveJustUnderTwiceSlop_onDragSlopExceededNotCalled() {
+
+ // Arrange
+
+ val beyondSlop = TestTouchSlop + TinyNum
+
+ var pointer1 = down(0)
+ var pointer2 = down(1)
+ filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
+
+ // Act
+
+ // These movements average to no movement.
+
+ pointer1 =
+ pointer1.moveBy(
+ Duration(milliseconds = 100),
+ 0f,
+ 0f
+ )
+ pointer2 =
+ pointer2.moveBy(
+ Duration(milliseconds = 100),
+ beyondSlop * 2 - 1,
+ 0f
+ )
+ filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
+
+ // Assert
+
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
+ }
+
+ @Test
+ fun onPointerInputChanges_2Points1MoveToTwiceSlop_onDragSlopExceededNotCalled() {
+
+ // Arrange
+
+ val beyondSlop = TestTouchSlop + TinyNum
+
+ var pointer1 = down(0)
+ var pointer2 = down(1)
+ filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
+
+ // Act
+
+ // These movements average to no movement.
+
+ pointer1 =
+ pointer1.moveBy(
+ Duration(milliseconds = 100),
+ 0f,
+ 0f
+ )
+ pointer2 =
+ pointer2.moveBy(
+ Duration(milliseconds = 100),
+ beyondSlop * 2,
+ 0f
+ )
+ filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
+
+ // Assert
+
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
+ }
+
+ @Test
+ fun onPointerInputChanges_1PointMovesBeyondSlopAndThenManyTimes_onDragSlopExceededCallOnce() {
// Arrange
@@ -599,11 +668,11 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
- fun onPointerInputChanges_1ModifiedToMoveBeyondSlopBeforePostUp_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_1ModifiedToMoveBeyondSlopBeforePostUp_onDragSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
@@ -622,11 +691,11 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
- fun onPointerInputChanges_1ModedToMoveBeyondSlopBeforePostDown_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_1ModedToMoveBeyondSlopBeforePostDown_onDragSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
@@ -651,11 +720,11 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
- fun onPointerInputChanges_moveUnderToPostUpThenModOverToPostDown_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_moveUnderToPostUpThenModOverToPostDown_onDragSlopExceededCallOnce() {
val halfSlop = TestTouchSlop / 2
val restOfSlopAndBeyond = TestTouchSlop - halfSlop + TinyNum
@@ -681,11 +750,11 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
- fun onPointerInputChanges_moveBeyondSlopAllPassesUpToPostUp_onTouchSlopExceededCallOnce() {
+ fun onPointerInputChanges_moveBeyondSlopAllPassesUpToPostUp_onDragSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
@@ -704,7 +773,7 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(1)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
// Verification that TouchSlopExceededGestureDetector does not consume any changes.
@@ -796,7 +865,7 @@
// Verification that TouchSlopExceededGestureDetector resets after up correctly.
@Test
- fun onPointerInputChanges_MoveBeyondUpDownMoveBeyond_onTouchSlopExceededCalledTwice() {
+ fun onPointerInputChanges_MoveBeyondUpDownMoveBeyond_onDragSlopExceededCalledTwice() {
val beyondSlop = TestTouchSlop + TinyNum
repeat(2) {
@@ -810,13 +879,13 @@
filter::onPointerInput.invokeOverAllPasses(up)
}
- assertThat(onTouchSlopExceededCallCount).isEqualTo(2)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(2)
}
// Verification that cancellation behavior is correct.
@Test
- fun onCancel_underSlopCancelUnderSlop_onTouchSlopExceededNotCalled() {
+ fun onCancel_underSlopCancelUnderSlop_onDragSlopExceededNotCalled() {
val underSlop = TestTouchSlop - TinyNum
// Arrange
@@ -847,7 +916,7 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(0)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
@@ -882,6 +951,6 @@
// Assert
- assertThat(onTouchSlopExceededCallCount).isEqualTo(2)
+ assertThat(onDragSlopExceededCallCount).isEqualTo(2)
}
}
\ No newline at end of file
diff --git a/window/stubs/window-sidecar-release-0.1.0-alpha01.aar b/window/stubs/window-sidecar-release-0.1.0-alpha01.aar
new file mode 100644
index 0000000..50f101d
--- /dev/null
+++ b/window/stubs/window-sidecar-release-0.1.0-alpha01.aar
Binary files differ