Merge "Improvements to GridView and size adjustments for RowView"
diff --git a/app-toolkit/core-testing/build.gradle b/app-toolkit/core-testing/build.gradle
index 3ee9584..dca8810 100644
--- a/app-toolkit/core-testing/build.gradle
+++ b/app-toolkit/core-testing/build.gradle
@@ -36,11 +36,10 @@
api(MOCKITO_CORE, libs.exclude_bytebuddy)
testImplementation(JUNIT)
- testImplementation libs.support.annotations
androidTestImplementation(JUNIT)
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
}
supportLibrary {
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index ffbc5a8..be135b9 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -29,6 +29,6 @@
build_libs.jarjar_gradle = 'org.anarres.jarjar:jarjar-gradle:1.0.0'
build_libs.error_prone = 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13'
build_libs.jacoco = 'org.jacoco:org.jacoco.core:0.7.8'
-build_libs.kotlin = [gradle_plugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.51"]
+build_libs.kotlin = [gradle_plugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0"]
rootProject.ext['build_libs'] = build_libs
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index 470551b..48d3eb9 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -16,10 +16,6 @@
// Add ext.libs for library versions
def libs = [:]
-libs.exclude_annotations = {
- exclude module: 'support-annotations'
-}
-
libs.exclude_bytebuddy = {
exclude group: 'net.bytebuddy'
}
diff --git a/buildSrc/src/main/java/android/support/LibraryVersions.java b/buildSrc/src/main/java/android/support/LibraryVersions.java
index efa0cba..a96f630 100644
--- a/buildSrc/src/main/java/android/support/LibraryVersions.java
+++ b/buildSrc/src/main/java/android/support/LibraryVersions.java
@@ -43,7 +43,7 @@
/**
* Version code for RecyclerView & Room paging
*/
- public static final Version PAGING = new Version("1.0.0-alpha3");
+ public static final Version PAGING = new Version("1.0.0-alpha4");
private static final Version LIFECYCLES = new Version("1.0.3");
diff --git a/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java b/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java
index de2db91..15e9104 100644
--- a/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java
+++ b/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java
@@ -29,7 +29,6 @@
import java.io.File;
import java.nio.charset.Charset;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
/**
@@ -120,11 +119,6 @@
}
if (mWhitelistErrorsFile != null && !mWhitelistErrors.isEmpty()) {
- if (mWhitelistErrorsFile.exists()) {
- List<String> lines =
- Files.readLines(mWhitelistErrorsFile, Charset.defaultCharset());
- mWhitelistErrors.removeAll(lines);
- }
try (BufferedWriter writer = Files.newWriter(
mWhitelistErrorsFile, Charset.defaultCharset())) {
for (String error : mWhitelistErrors) {
diff --git a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
index 9f0e402..3432cbf 100644
--- a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
@@ -578,12 +578,12 @@
root.subprojects { subProject ->
subProject.afterEvaluate { project ->
if (project.hasProperty("noDocs") && (project.properties["noDocs"] as Boolean)) {
- project.logger.warn("Project $project.name specified noDocs, ignoring API tasks.")
+ project.logger.info("Project $project.name specified noDocs, ignoring API tasks.")
return@afterEvaluate
}
if (project.hasProperty("supportLibrary")
&& !(project.properties["supportLibrary"] as SupportLibraryExtension).publish) {
- project.logger.warn("Project ${project.name} is not published, ignoring API tasks.")
+ project.logger.info("Project ${project.name} is not published, ignoring API tasks.")
return@afterEvaluate
}
val library = project.extensions.findByType(LibraryExtension::class.java)
@@ -595,7 +595,7 @@
return@all
}
if (!hasApiFolder(project)) {
- project.logger.warn("Project ${project.name} doesn't have " +
+ project.logger.info("Project ${project.name} doesn't have " +
"an api folder, ignoring API tasks.")
return@all
}
@@ -609,7 +609,7 @@
val compileJava = project.properties["compileJava"] as JavaCompile
registerJavaProjectForDocsTask(generateDocsTask, compileJava)
if (!hasApiFolder(project)) {
- project.logger.warn("Project $project.name doesn't have an api folder, " +
+ project.logger.info("Project $project.name doesn't have an api folder, " +
"ignoring API tasks.")
return@afterEvaluate
}
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
index c83d740..5eaa7dd 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
@@ -37,6 +37,8 @@
SupportLibraryExtension::class.java, project)
apply(project, supportLibraryExtension)
+ val isCoreSupportLibrary = project.rootProject.name == "support"
+
project.afterEvaluate {
val library = project.extensions.findByType(LibraryExtension::class.java)
?: return@afterEvaluate
@@ -75,6 +77,21 @@
project.apply(mapOf("plugin" to "com.android.library"))
project.apply(mapOf("plugin" to ErrorProneBasePlugin::class.java))
+ project.configurations.all { configuration ->
+ if (isCoreSupportLibrary) {
+ // In projects which compile as part of the "core" support libraries (which include
+ // the annotations), replace any transitive pointer to the deployed Maven
+ // coordinate version of annotations with a reference to the local project. These
+ // usually originate from test dependencies and otherwise cause multiple copies on
+ // the classpath. We do not do this for non-"core" projects as they need to
+ // depend on the Maven coordinate variant.
+ configuration.resolutionStrategy.dependencySubstitution.apply {
+ substitute(module("com.android.support:support-annotations"))
+ .with(project(":support-annotations"))
+ }
+ }
+ }
+
val library = project.extensions.findByType(LibraryExtension::class.java)
?: throw Exception("Failed to find Android extension")
@@ -120,12 +137,15 @@
// Enforce the following checks.
"-Xep:RestrictTo:OFF",
+ "-Xep:ParameterNotNullable:ERROR",
"-Xep:MissingOverride:ERROR",
+ "-Xep:JdkObsolete:ERROR",
"-Xep:NarrowingCompoundAssignment:ERROR",
"-Xep:ClassNewInstance:ERROR",
"-Xep:ClassCanBeStatic:ERROR",
"-Xep:SynchronizeOnNonFinalField:ERROR",
- "-Xep:OperatorPrecedence:ERROR"
+ "-Xep:OperatorPrecedence:ERROR",
+ "-Xep:IntLongMath:ERROR"
)
}
}
diff --git a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
index 2502f77..e32cacd 100644
--- a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
@@ -29,7 +29,7 @@
const val JAVAPOET = "com.squareup:javapoet:1.8.0"
const val JSR250 = "javax.annotation:javax.annotation-api:1.2"
const val JUNIT = "junit:junit:4.12"
-const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:1.1.51"
+const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:1.2.0"
const val MOCKITO_CORE = "org.mockito:mockito-core:2.7.6"
const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.0.6"
diff --git a/car/AndroidManifest.xml b/car/AndroidManifest.xml
index 4e6d80f..854e097 100644
--- a/car/AndroidManifest.xml
+++ b/car/AndroidManifest.xml
@@ -14,5 +14,5 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.car">
+ package="androidx.car">
</manifest>
diff --git a/car/build.gradle b/car/build.gradle
index 487f2bc..3970df9 100644
--- a/car/build.gradle
+++ b/car/build.gradle
@@ -12,8 +12,8 @@
api project(':support-v4')
api project(':recyclerview-v7')
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
diff --git a/car/res/drawable/car_borderless_button_text_color.xml b/car/res/drawable/car_borderless_button_text_color.xml
new file mode 100644
index 0000000..ff27db5
--- /dev/null
+++ b/car/res/drawable/car_borderless_button_text_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<!-- Default text colors for car buttons when enabled/disabled. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/car_grey_700" android:state_enabled="false"/>
+ <item android:color="?android:attr/colorPrimary"/>
+</selector>
diff --git a/car/res/drawable/car_button_background.xml b/car/res/drawable/car_button_background.xml
index 3b139d9..1a8995c 100644
--- a/car/res/drawable/car_button_background.xml
+++ b/car/res/drawable/car_button_background.xml
@@ -18,14 +18,14 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false">
<shape android:shape="rectangle">
- <corners android:radius="@dimen/car_button_radius" />
+ <corners android:radius="@dimen/car_button_radius"/>
<solid android:color="@color/car_grey_300"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
- <corners android:radius="@dimen/car_button_radius" />
- <solid android:color="@color/car_highlight"/>
+ <corners android:radius="@dimen/car_button_radius"/>
+ <solid android:color="?android:attr/colorPrimary"/>
</shape>
</item>
</selector>
diff --git a/car/res/drawable/car_button_text_color.xml b/car/res/drawable/car_button_text_color.xml
index b14ec68..bb8c681 100644
--- a/car/res/drawable/car_button_text_color.xml
+++ b/car/res/drawable/car_button_text_color.xml
@@ -16,6 +16,6 @@
-->
<!-- Default text colors for car buttons when enabled/disabled. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:color="@color/car_grey_700" />
+ <item android:color="@color/car_grey_700" android:state_enabled="false"/>
<item android:color="@color/car_action1"/>
</selector>
diff --git a/car/res/layout/car_drawer.xml b/car/res/layout/car_drawer.xml
index 812acb4..c4ce405 100644
--- a/car/res/layout/car_drawer.xml
+++ b/car/res/layout/car_drawer.xml
@@ -23,7 +23,7 @@
android:background="@color/car_card"
android:paddingTop="@dimen/car_app_bar_height" >
- <android.support.car.widget.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/drawer_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/car/res/layout/car_paged_list_card.xml b/car/res/layout/car_paged_list_card.xml
new file mode 100644
index 0000000..fe5de89
--- /dev/null
+++ b/car/res/layout/car_paged_list_card.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<android.support.v7.widget.CardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_marginBottom="@dimen/car_padding_1"
+ app:cardCornerRadius="@dimen/car_radius_1"
+ app:cardBackgroundColor="@color/car_card">
+
+ <include layout="@layout/car_paged_list_item_content" />
+
+</android.support.v7.widget.CardView>
diff --git a/v14/preference/res/values-v21/styles.xml b/car/res/layout/car_paged_list_item.xml
similarity index 66%
rename from v14/preference/res/values-v21/styles.xml
rename to car/res/layout/car_paged_list_item.xml
index 9a85987..c0861d9 100644
--- a/v14/preference/res/values-v21/styles.xml
+++ b/car/res/layout/car_paged_list_item.xml
@@ -12,9 +12,14 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License
+ ~ limitations under the License.
-->
-<resources>
- <dimen name="preference_no_icon_padding_start">72dp</dimen>
-</resources>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:background="@color/car_card">
+ <include layout="@layout/car_paged_list_item_content" />
+
+</FrameLayout>
diff --git a/car/res/layout/car_paged_list_item_content.xml b/car/res/layout/car_paged_list_item_content.xml
new file mode 100644
index 0000000..0e6b809
--- /dev/null
+++ b/car/res/layout/car_paged_list_item_content.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+
+ <!-- Primary Action. -->
+ <ImageView
+ android:id="@+id/primary_icon"
+ android:layout_width="@dimen/car_single_line_list_item_height"
+ android:layout_height="@dimen/car_single_line_list_item_height"
+ android:layout_centerVertical="true"/>
+
+ <!-- Text. -->
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@id/supplemental_actions"
+ android:singleLine="true"
+ android:ellipsize="end"/>
+ <TextView
+ android:id="@+id/body"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@id/supplemental_actions"/>
+
+ <!-- Supplemental action(s) - supports either 1 supplemental icon or up to 2 action buttons. -->
+ <LinearLayout
+ android:id="@+id/supplemental_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ android:layout_marginEnd="@dimen/car_keyline_1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+ <!-- End icon with divider. -->
+ <View
+ android:id="@+id/supplemental_icon_divider"
+ android:layout_width="@dimen/car_vertical_line_divider_width"
+ android:layout_height="@dimen/car_vertical_line_divider_height"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:background="@color/car_list_divider"/>
+ <ImageView
+ android:id="@+id/supplemental_icon"
+ android:layout_width="@dimen/car_primary_icon_size"
+ android:layout_height="@dimen/car_primary_icon_size"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:scaleType="fitCenter"/>
+
+ <!-- Up to 2 action buttons with dividers. -->
+ <View
+ android:id="@+id/action2_divider"
+ android:layout_width="@dimen/car_vertical_line_divider_width"
+ android:layout_height="@dimen/car_vertical_line_divider_height"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:background="@color/car_list_divider"/>
+ <Button
+ android:id="@+id/action2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:ellipsize="end"
+ android:maxLength="@integer/car_borderless_button_text_length_limit"
+ android:maxLines="1"
+ android:background="@color/car_card"
+ style="@style/CarButton.Borderless"/>
+ <View
+ android:id="@+id/action1_divider"
+ android:layout_width="@dimen/car_vertical_line_divider_width"
+ android:layout_height="@dimen/car_vertical_line_divider_height"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:background="@color/car_list_divider"/>
+ <Button
+ android:id="@+id/action1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:ellipsize="end"
+ android:maxLength="@integer/car_borderless_button_text_length_limit"
+ android:maxLines="1"
+ android:background="@color/car_card"
+ style="@style/CarButton.Borderless"/>
+ </LinearLayout>
+</RelativeLayout>
diff --git a/car/res/layout/car_paged_recycler_view.xml b/car/res/layout/car_paged_recycler_view.xml
index 47a82ff..d3ca4a3 100644
--- a/car/res/layout/car_paged_recycler_view.xml
+++ b/car/res/layout/car_paged_recycler_view.xml
@@ -19,14 +19,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <android.support.car.widget.CarRecyclerView
+ <androidx.car.widget.CarRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Putting this as the last child so that it can intercept any touch events on the
scroll buttons. -->
- <android.support.car.widget.PagedScrollBarView
+ <androidx.car.widget.PagedScrollBarView
android:id="@+id/paged_scroll_view"
android:layout_width="@dimen/car_margin"
android:layout_height="match_parent"
diff --git a/car/res/layout/car_paged_scrollbar_buttons.xml b/car/res/layout/car_paged_scrollbar_buttons.xml
index 7dd213a..d49b532 100644
--- a/car/res/layout/car_paged_scrollbar_buttons.xml
+++ b/car/res/layout/car_paged_scrollbar_buttons.xml
@@ -37,8 +37,8 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/car_paged_list_view_scrollbar_thumb_margin"
- android:layout_marginTop="@dimen/car_paged_list_view_scrollbar_thumb_margin" >
+ android:layout_marginBottom="@dimen/car_scroll_bar_thumb_margin"
+ android:layout_marginTop="@dimen/car_scroll_bar_thumb_margin" >
<ImageView
android:id="@+id/scrollbar_thumb"
diff --git a/car/res/values-h1752dp/dimens.xml b/car/res/values-h1752dp/dimens.xml
index cd2bf46..4b6a23d 100644
--- a/car/res/values-h1752dp/dimens.xml
+++ b/car/res/values-h1752dp/dimens.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<resources>
+ <!-- Car Component Dimensions -->
<!-- Type Sizings -->
<dimen name="car_title2_size">40sp</dimen>
<dimen name="car_headline1_size">56sp</dimen>
@@ -22,17 +23,31 @@
<dimen name="car_body2_size">32sp</dimen>
<dimen name="car_action1_size">32sp</dimen>
- <!-- Car Component Dimensions -->
- <!-- Application Bar Height -->
- <dimen name="car_app_bar_height">112dp</dimen>
-
- <dimen name="car_touch_target">96dp</dimen>
-
- <!-- Icon dimensions -->
+ <!-- Icons and Buttons -->
+ <!-- Icons -->
<dimen name="car_primary_icon_size">56dp</dimen>
<dimen name="car_secondary_icon_size">36dp</dimen>
- <!-- Line heights -->
+ <!-- Avatars -->
+ <dimen name="car_avatar_size">96dp</dimen>
+
+ <!-- Minimum touch target size. -->
+ <dimen name="car_touch_target_size">96dp</dimen>
+
+ <!-- Application Bar -->
+ <dimen name="car_app_bar_height">112dp</dimen>
+
+ <!-- List Items -->
<dimen name="car_single_line_list_item_height">128dp</dimen>
<dimen name="car_double_line_list_item_height">128dp</dimen>
+
+ <!-- Cards -->
+ <dimen name="car_card_header_height">96dp</dimen>
+ <dimen name="car_card_action_bar_height">96dp</dimen>
+
+ <!-- Slide Up Menu -->
+ <dimen name="car_slide_up_menu_initial_height">128dp</dimen>
+
+ <!-- Sub Header -->
+ <dimen name="car_sub_header_height">96dp</dimen>
</resources>
diff --git a/car/res/values-h684dp/dimens.xml b/car/res/values-h684dp/dimens.xml
index a072681..039d377 100644
--- a/car/res/values-h684dp/dimens.xml
+++ b/car/res/values-h684dp/dimens.xml
@@ -15,14 +15,27 @@
-->
<resources>
<!-- Car Component Dimensions -->
+ <!-- Application Bar -->
<dimen name="car_app_bar_height">96dp</dimen>
- <!-- List and Drawer Dimensions -->
+ <!-- List Items -->
+ <dimen name="car_single_line_list_item_height">116dp</dimen>
+ <dimen name="car_double_line_list_item_height">116dp</dimen>
+
+ <!-- Slide Up Menu -->
+ <dimen name="car_slide_up_menu_initial_height">116dp</dimen>
+
+ <!-- Scroll Bar -->
+ <dimen name="car_scroll_bar_padding">@dimen/car_padding_4</dimen>
+
+ <!-- Scroll Bar Thumb -->
+ <dimen name="car_scroll_bar_thumb_margin">@dimen/car_padding_2</dimen>
+
+ <!-- Scroll Bar Buttons -->
+ <dimen name="car_scroll_bar_button_size">76dp</dimen>
+
+ <!-- Drawer Dimensions -->
<dimen name="car_drawer_list_item_icon_size">108dp</dimen>
<dimen name="car_drawer_list_item_small_icon_size">56dp</dimen>
<dimen name="car_drawer_list_item_end_icon_size">56dp</dimen>
-
- <!-- Line heights -->
- <dimen name="car_single_line_list_item_height">116dp</dimen>
- <dimen name="car_double_line_list_item_height">116dp</dimen>
</resources>
diff --git a/car/res/values-w1280dp/dimens.xml b/car/res/values-w1280dp/dimens.xml
index 9837355..418e51f 100644
--- a/car/res/values-w1280dp/dimens.xml
+++ b/car/res/values-w1280dp/dimens.xml
@@ -14,17 +14,11 @@
limitations under the License.
-->
<resources>
- <dimen name="car_screen_margin_size">148dp</dimen>
- <dimen name="car_scroll_bar_button_size">76dp</dimen>
-
- <dimen name="car_keyline_1">32dp</dimen>
- <dimen name="car_keyline_2">108dp</dimen>
- <dimen name="car_keyline_3">128dp</dimen>
- <dimen name="car_keyline_4">182dp</dimen>
- <dimen name="car_keyline_1_neg">32dp</dimen>
- <dimen name="car_keyline_2_neg">108dp</dimen>
- <dimen name="car_keyline_3_neg">128dp</dimen>
-
+ <!-- Framework -->
<!-- Margin -->
<dimen name="car_margin">148dp</dimen>
+
+ <!-- Keylines -->
+ <dimen name="car_keyline_4">182dp</dimen>
+ <dimen name="car_keyline_4_neg">-182dp</dimen>
</resources>
diff --git a/car/res/values-w840dp/integers.xml b/car/res/values-w1280dp/integers.xml
similarity index 84%
rename from car/res/values-w840dp/integers.xml
rename to car/res/values-w1280dp/integers.xml
index 38c0440..62fcf37 100644
--- a/car/res/values-w840dp/integers.xml
+++ b/car/res/values-w1280dp/integers.xml
@@ -14,6 +14,10 @@
limitations under the License.
-->
<resources>
- <integer name="car_screen_num_of_columns">12</integer>
+ <!-- Application Components -->
+ <!-- Cards -->
<integer name="column_card_default_column_span">8</integer>
+
+ <!-- Dialogs -->
+ <integer name="car_dialog_column_number">8</integer>
</resources>
diff --git a/car/res/values-w1920dp/dimens.xml b/car/res/values-w1920dp/dimens.xml
index 52962a1..b02ec00 100644
--- a/car/res/values-w1920dp/dimens.xml
+++ b/car/res/values-w1920dp/dimens.xml
@@ -14,8 +14,16 @@
limitations under the License.
-->
<resources>
- <dimen name="car_keyline_3">152dp</dimen>
-
+ <!-- Framework -->
<!-- Margin -->
<dimen name="car_margin">192dp</dimen>
+
+ <!-- Gutters -->
+ <dimen name="car_gutter_size">32dp</dimen>
+
+ <!-- Keylines -->
+ <dimen name="car_keyline_1">48dp</dimen>
+ <dimen name="car_keyline_3">152dp</dimen>
+ <dimen name="car_keyline_1_neg">-48dp</dimen>
+ <dimen name="car_keyline_3_neg">-152dp</dimen>
</resources>
diff --git a/car/res/values-w1024dp/dimens.xml b/car/res/values-w1920dp/integers.xml
similarity index 86%
rename from car/res/values-w1024dp/dimens.xml
rename to car/res/values-w1920dp/integers.xml
index b1ae5ba..6519af5 100644
--- a/car/res/values-w1024dp/dimens.xml
+++ b/car/res/values-w1920dp/integers.xml
@@ -14,5 +14,7 @@
limitations under the License.
-->
<resources>
- <dimen name="car_screen_margin_size">112dp</dimen>
+ <!-- Framework -->
+ <!-- Columns -->
+ <integer name="car_column_number">16</integer>
</resources>
diff --git a/car/res/values-w480dp/dimens.xml b/car/res/values-w480dp/dimens.xml
deleted file mode 100644
index 4077e0d..0000000
--- a/car/res/values-w480dp/dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<resources>
- <dimen name="car_screen_margin_size">24dp</dimen>
-</resources>
diff --git a/car/res/values-w600dp/integers.xml b/car/res/values-w600dp/integers.xml
deleted file mode 100644
index 5dcd8df..0000000
--- a/car/res/values-w600dp/integers.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<resources>
- <integer name="car_screen_num_of_columns">8</integer>
- <integer name="column_card_default_column_span">6</integer>
-</resources>
diff --git a/car/res/values-w690dp/dimens.xml b/car/res/values-w690dp/dimens.xml
index f797955..2d43ac8 100644
--- a/car/res/values-w690dp/dimens.xml
+++ b/car/res/values-w690dp/dimens.xml
@@ -14,14 +14,7 @@
limitations under the License.
-->
<resources>
- <dimen name="car_keyline_1">24dp</dimen>
- <dimen name="car_keyline_2">96dp</dimen>
- <dimen name="car_keyline_3">112dp</dimen>
- <dimen name="car_keyline_4">148dp</dimen>
- <dimen name="car_keyline_1_neg">-24dp</dimen>
- <dimen name="car_keyline_2_neg">-96dp</dimen>
- <dimen name="car_keyline_3_neg">-112dp</dimen>
-
+ <!-- Framework -->
<!-- Margin -->
<dimen name="car_margin">112dp</dimen>
</resources>
diff --git a/car/res/values-w840dp/integers.xml b/car/res/values-w690dp/integers.xml
similarity index 62%
copy from car/res/values-w840dp/integers.xml
copy to car/res/values-w690dp/integers.xml
index 38c0440..0eb5837 100644
--- a/car/res/values-w840dp/integers.xml
+++ b/car/res/values-w690dp/integers.xml
@@ -14,6 +14,17 @@
limitations under the License.
-->
<resources>
- <integer name="car_screen_num_of_columns">12</integer>
- <integer name="column_card_default_column_span">8</integer>
+ <!-- Framework -->
+ <!-- Columns -->
+ <integer name="car_column_number">12</integer>
+
+ <!-- Application Components -->
+ <!-- Cards -->
+ <integer name="column_card_default_column_span">12</integer>
+
+ <!-- Dialogs -->
+ <integer name="car_dialog_column_number">10</integer>
+
+ <!-- Slide Up Menu -->
+ <integer name="car_slide_up_menu_column_number">12</integer>
</resources>
diff --git a/car/res/values-w720dp/dimens.xml b/car/res/values-w720dp/dimens.xml
deleted file mode 100644
index b1ae5ba..0000000
--- a/car/res/values-w720dp/dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<resources>
- <dimen name="car_screen_margin_size">112dp</dimen>
-</resources>
diff --git a/car/res/values-w840dp/dimens.xml b/car/res/values-w840dp/dimens.xml
deleted file mode 100644
index 8b4d992..0000000
--- a/car/res/values-w840dp/dimens.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<resources>
- <dimen name="car_keyline_1">32dp</dimen>
- <dimen name="car_keyline_2">108dp</dimen>
- <dimen name="car_keyline_3">128dp</dimen>
- <dimen name="car_screen_gutter_size">24dp</dimen>
-</resources>
diff --git a/car/res/values-w930dp/dimens.xml b/car/res/values-w930dp/dimens.xml
index 481480e..6d7714e 100644
--- a/car/res/values-w930dp/dimens.xml
+++ b/car/res/values-w930dp/dimens.xml
@@ -14,6 +14,11 @@
limitations under the License.
-->
<resources>
+ <!-- Framework -->
+ <!-- Gutters -->
+ <dimen name="car_gutter_size">24dp</dimen>
+
+ <!-- Keylines -->
<dimen name="car_keyline_1">32dp</dimen>
<dimen name="car_keyline_2">108dp</dimen>
<dimen name="car_keyline_3">128dp</dimen>
@@ -21,7 +26,5 @@
<dimen name="car_keyline_1_neg">-32dp</dimen>
<dimen name="car_keyline_2_neg">-108dp</dimen>
<dimen name="car_keyline_3_neg">-128dp</dimen>
-
- <!-- Margin -->
- <dimen name="car_margin">112dp</dimen>
+ <dimen name="car_keyline_4_neg">-168dp</dimen>
</resources>
diff --git a/car/res/values-w840dp/integers.xml b/car/res/values-w930dp/integers.xml
similarity index 76%
copy from car/res/values-w840dp/integers.xml
copy to car/res/values-w930dp/integers.xml
index 38c0440..60ddfa1 100644
--- a/car/res/values-w840dp/integers.xml
+++ b/car/res/values-w930dp/integers.xml
@@ -14,6 +14,10 @@
limitations under the License.
-->
<resources>
- <integer name="car_screen_num_of_columns">12</integer>
- <integer name="column_card_default_column_span">8</integer>
+ <!-- Application Components -->
+ <!-- Cards -->
+ <integer name="column_card_default_column_span">10</integer>
+
+ <!-- Dialogs -->
+ <integer name="car_dialog_column_number">10</integer>
</resources>
diff --git a/car/res/values/attrs.xml b/car/res/values/attrs.xml
index 0ba8f55..01ed95f 100644
--- a/car/res/values/attrs.xml
+++ b/car/res/values/attrs.xml
@@ -28,8 +28,24 @@
<!-- Set to true/false to offset rows as they slide off screen. Defaults to true -->
<attr name="offsetRows" format="boolean" />
<!-- Whether or not to offset the list view by the width of scroll bar. Setting this to
- true will ensure that any views within the list will not overlap the scroll bar. -->
+ true will ensure that any views within the list will not overlap the scroll bar.
+ Deprecated: use gutter instead. If gutter is specified, this value is ignored.-->
<attr name="offsetScrollBar" format="boolean" />
+ <!-- Whether to include a gutter to the start, end or both sides of the list view items.
+ The gutter width will be the width of the scrollbar, and by default will be set to
+ both. -->
+ <attr name="gutter" format="enum">
+ <!-- No gutter on either side, the list view items will extend the full width of the
+ PagedListView. -->
+ <enum name="none" value="0" />
+ <!-- Include a gutter on the start side only (i.e. the side with the scrollbar). -->
+ <enum name="start" value="1" />
+ <!-- Include a gutter on the end side only (i.e. the opposite side to the
+ scrollbar). -->
+ <enum name="end" value="2" />
+ <!-- Include a gutter on both sides of the list view items. -->
+ <enum name="both" value="3" />
+ </attr>
<!-- Whether to display the scrollbar or not. Defaults to true. -->
<attr name="scrollBarEnabled" format="boolean" />
<!-- Whether or not to show a diving line between each item of the list. -->
@@ -43,7 +59,8 @@
<!-- A starting margin before the drawing of the dividing line. This margin will be an
offset from the view specified by "alignDividerStartTo" if given. -->
<attr name="dividerStartMargin" format="dimension" />
- <!-- The width of the margin on the right side of the list -->
+ <!-- The width of the margin on the right side of the list.
+ Deprecated: use gutter instead. If gutter is specified, this value is ignored.-->
<attr name="listEndMargin" format="dimension" />
<!-- An optional spacing between items in the list -->
<attr name="itemSpacing" format="dimension" />
diff --git a/car/res/values/dimens.xml b/car/res/values/dimens.xml
index f1761c8..8e8621c 100644
--- a/car/res/values/dimens.xml
+++ b/car/res/values/dimens.xml
@@ -14,15 +14,37 @@
limitations under the License.
-->
<resources>
- <!-- Keylines for content. -->
- <dimen name="car_keyline_1">48dp</dimen>
- <dimen name="car_keyline_2">108dp</dimen>
- <dimen name="car_keyline_3">152dp</dimen>
- <dimen name="car_keyline_4">182dp</dimen>
- <dimen name="car_keyline_1_neg">-48dp</dimen>
- <dimen name="car_keyline_2_neg">-108dp</dimen>
- <dimen name="car_keyline_3_neg">-152dp</dimen>
+ <!-- Framework -->
+ <!-- Margin -->
+ <dimen name="car_margin">20dp</dimen>
+ <!-- Gutters -->
+ <dimen name="car_gutter_size">16dp</dimen>
+
+ <!-- Keylines -->
+ <dimen name="car_keyline_1">24dp</dimen>
+ <dimen name="car_keyline_2">96dp</dimen>
+ <dimen name="car_keyline_3">112dp</dimen>
+ <dimen name="car_keyline_4">148dp</dimen>
+ <dimen name="car_keyline_1_neg">-24dp</dimen>
+ <dimen name="car_keyline_2_neg">-96dp</dimen>
+ <dimen name="car_keyline_3_neg">-112dp</dimen>
+ <dimen name="car_keyline_4_neg">-148dp</dimen>
+
+ <!-- Paddings -->
+ <dimen name="car_padding_1">4dp</dimen>
+ <dimen name="car_padding_2">10dp</dimen>
+ <dimen name="car_padding_3">16dp</dimen>
+ <dimen name="car_padding_4">28dp</dimen>
+ <dimen name="car_padding_5">32dp</dimen>
+
+ <!-- Radii -->
+ <dimen name="car_radius_1">4dp</dimen>
+ <dimen name="car_radius_2">8dp</dimen>
+ <dimen name="car_radius_3">16dp</dimen>
+ <dimen name="car_radius_5">100dp</dimen>
+
+ <!-- Car Component Dimensions -->
<!-- Type Sizings -->
<dimen name="car_title_size">32sp</dimen>
<dimen name="car_title2_size">32sp</dimen>
@@ -37,84 +59,91 @@
<dimen name="car_body5_size">18sp</dimen>
<dimen name="car_action1_size">26sp</dimen>
- <!-- Paddings -->
- <dimen name="car_padding_1">4dp</dimen>
- <dimen name="car_padding_2">10dp</dimen>
- <dimen name="car_padding_3">16dp</dimen>
- <dimen name="car_padding_4">28dp</dimen>
- <dimen name="car_padding_5">32dp</dimen>
-
- <!-- Radius -->
- <dimen name="car_radius_1">4dp</dimen>
- <dimen name="car_radius_2">8dp</dimen>
- <dimen name="car_radius_3">16dp</dimen>
- <dimen name="car_radius_5">100dp</dimen>
-
- <!-- Margin -->
- <dimen name="car_margin">20dp</dimen>
-
- <!-- Car Component Dimensions -->
- <!-- Application Bar Height -->
- <dimen name="car_app_bar_height">80dp</dimen>
-
- <!-- The height of the bar that contains an applications action buttons. -->
- <dimen name="car_action_bar_height">128dp</dimen>
-
- <!-- Minimum touch target size. -->
- <dimen name="car_touch_target">76dp</dimen>
-
- <!-- Button Dimensions -->
- <dimen name="car_button_height">64dp</dimen>
- <dimen name="car_button_min_width">158dp</dimen>
- <dimen name="car_button_horizontal_padding">@dimen/car_padding_4</dimen>
- <dimen name="car_button_radius">@dimen/car_radius_1</dimen>
-
- <!-- Icon dimensions -->
+ <!-- Icons and Buttons -->
+ <!-- Icons -->
<dimen name="car_primary_icon_size">44dp</dimen>
<dimen name="car_secondary_icon_size">24dp</dimen>
- <!-- Line heights -->
+ <!-- Avatars -->
+ <dimen name="car_avatar_size">56dp</dimen>
+
+ <!-- Minimum touch target size. -->
+ <dimen name="car_touch_target_size">76dp</dimen>
+
+ <!-- Buttons -->
+ <dimen name="car_button_height">64dp</dimen>
+ <dimen name="car_button_min_width">158dp</dimen>
+ <dimen name="car_button_horizontal_padding">@dimen/car_padding_4</dimen>
+ <dimen name="car_borderless_button_horizontal_padding">0dp</dimen>
+ <dimen name="car_button_radius">@dimen/car_radius_1</dimen>
+
+ <!-- Application Bar -->
+ <dimen name="car_app_bar_height">80dp</dimen>
+
+ <!-- Action Bars -->
+ <dimen name="car_action_bar_height">128dp</dimen>
+ <dimen name="car_secondary_single_action_bar_height">@dimen/car_action_bar_height</dimen>
+ <dimen name="car_secondary_double_action_bar_height">256dp</dimen>
+
+ <!-- Lists -->
<dimen name="car_single_line_list_item_height">76dp</dimen>
<dimen name="car_double_line_list_item_height">96dp</dimen>
+ <dimen name="car_list_divider_height">1dp</dimen>
+ <!-- The height of a vertical line divider. -->
+ <dimen name="car_vertical_line_divider_height">60dp</dimen>
+ <dimen name="car_vertical_line_divider_width">1dp</dimen>
- <!-- List and Drawer Dimensions -->
- <!-- The margin on both sides of the screen before the contents of the PagedListView. -->
- <dimen name="car_card_margin">96dp</dimen>
+ <!-- Cards -->
+ <dimen name="car_card_header_height">76dp</dimen>
+ <dimen name="car_card_action_bar_height">76dp</dimen>
- <!-- The height of the dividers in the list. -->
- <dimen name="car_divider_height">1dp</dimen>
+ <!-- Dialogs -->
+ <dimen name="car_dialog_header_height">@dimen/car_card_header_height</dimen>
+ <dimen name="car_dialog_action_bar_height">@dimen/car_card_action_bar_height</dimen>
+ <!-- Slide Up Menu -->
+ <dimen name="car_slide_up_menu_initial_height">76dp</dimen>
+
+ <!-- Slide Down Menu -->
+ <dimen name="car_slide_down_menu_initial_height">@dimen/car_slide_up_menu_initial_height</dimen>
+
+ <!-- Sub Header -->
+ <dimen name="car_sub_header_height">76dp</dimen>
+
+ <!-- Slider -->
+ <dimen name="car_slider_height">6dp</dimen>
+ <dimen name="car_slider_knob_size">20dp</dimen>
+
+ <!-- Scroll Bar -->
+ <dimen name="car_scroll_bar_padding">@dimen/car_padding_2</dimen>
+
+ <!-- Scroll Bar Thumb -->
+ <dimen name="car_scroll_bar_thumb_width">@dimen/car_slider_height</dimen>
+ <dimen name="car_min_scroll_bar_thumb_height">48dp</dimen>
+ <dimen name="car_max_scroll_bar_thumb_height">128dp</dimen>
+ <dimen name="car_scroll_bar_thumb_margin">@dimen/car_padding_1</dimen>
+
+ <!-- Scroll Bar and Alpha Jump Buttons -->
+ <dimen name="car_scroll_bar_button_size">56dp</dimen>
+ <dimen name="car_alpha_jump_button_size">@dimen/car_scroll_bar_button_size</dimen>
+
+ <!-- Progress Bar -->
+ <dimen name="car_progress_bar_height">@dimen/car_slider_height</dimen>
+
+ <!-- Text Input -->
+ <dimen name="car_text_input_line_height">2dp</dimen>
+
+ <!-- PagedListView Dimensions -->
<!-- Sample row height used for scroll bar calculations in the off chance that a view hasn't
- been measured. It's highly unlikely that this value will actually be used for more than
- a frame max. The sample row is a 96dp card + 16dp margin on either side. -->
+ been measured. It's highly unlikely that this value will actually be used for more than
+ a frame max. The sample row is a 96dp card + 16dp margin on either side. -->
<dimen name="car_sample_row_height">128dp</dimen>
<!-- The amount of space the LayoutManager will make sure the last item on the screen is
peeking before scrolling down -->
<dimen name="car_last_card_peek_amount">16dp</dimen>
- <!-- The spacing between each column that fits on the screen. The number of columns is
- determined by integer/car_screen_num_of_columns. -->
- <dimen name="car_screen_gutter_size">16dp</dimen>
-
- <!-- The margin on both sizes of the scroll bar thumb. -->
- <dimen name="car_paged_list_view_scrollbar_thumb_margin">8dp</dimen>
-
- <!-- The size of the scroll bar up and down arrows. -->
- <dimen name="car_scroll_bar_button_size">44dp</dimen>
-
- <!-- The padding around the scroll bar. -->
- <dimen name="car_scroll_bar_padding">16dp</dimen>
-
- <!-- The width of the scroll bar thumb. -->
- <dimen name="car_scroll_bar_thumb_width">6dp</dimen>
-
- <!-- The minimum the scrollbar thumb can shrink to -->
- <dimen name="min_thumb_height">48dp</dimen>
-
- <!-- The maximum the scrollbar thumb can grow to -->
- <dimen name="max_thumb_height">128dp</dimen>
-
+ <!-- Drawer Dimensions -->
<!-- Size of progress-bar in Drawer -->
<dimen name="car_drawer_progress_bar_size">48dp</dimen>
diff --git a/car/res/values/integers.xml b/car/res/values/integers.xml
index 575d646..6352d7c 100644
--- a/car/res/values/integers.xml
+++ b/car/res/values/integers.xml
@@ -14,10 +14,28 @@
limitations under the License.
-->
<resources>
- <!-- The number of columns that appear on-screen. -->
- <integer name="car_screen_num_of_columns">4</integer>
+ <!-- Framework -->
+ <!-- Columns -->
+ <integer name="car_column_number">4</integer>
- <!-- The default number of columns that a ColumnCardView will span if columnSpan is not
- specified.-->
+ <!-- Application Components -->
+ <!-- Action Bar -->
+ <integer name="action_bar_column_number">@integer/car_column_number</integer>
+
+ <!-- Cards -->
<integer name="column_card_default_column_span">4</integer>
+
+ <!-- Dialogs -->
+ <integer name="car_dialog_column_number">10</integer>
+
+ <!-- Slide Up Menu -->
+ <integer name="car_slide_up_menu_column_number">4</integer>
+
+ <!-- The length limit of body text in a paged list item. String longer than this limit should be
+ truncated. -->
+ <integer name="car_list_item_text_length_limit">120</integer>
+
+ <!-- The length limit of text in a borderless button. String longer than this limit should be
+ truncated. -->
+ <integer name="car_borderless_button_text_length_limit">20</integer>
</resources>
diff --git a/car/res/values/strings.xml b/car/res/values/strings.xml
index 65f08b6..1fb4cf4 100644
--- a/car/res/values/strings.xml
+++ b/car/res/values/strings.xml
@@ -21,4 +21,5 @@
-->
<string name="car_drawer_open" translatable="false">Open drawer</string>
<string name="car_drawer_close" translatable="false">Close drawer</string>
+ <string name="ellipsis" translatable="false">…</string>
</resources>
diff --git a/car/res/values/styles.xml b/car/res/values/styles.xml
index 61e089b..d84f4c8 100644
--- a/car/res/values/styles.xml
+++ b/car/res/values/styles.xml
@@ -15,25 +15,25 @@
-->
<resources>
<!-- The styling for title text. The color of this text changes based on day/night mode. -->
- <style name="CarTitle" >
+ <style name="CarTitle">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_title_size</item>
<item name="android:textColor">@color/car_title</item>
</style>
<!-- Title text that is permanently a dark color. -->
- <style name="CarTitle.Dark" >
+ <style name="CarTitle.Dark">
<item name="android:textColor">@color/car_title_dark</item>
</style>
<!-- Title text that is permanently a light color. -->
- <style name="CarTitle.Light" >
+ <style name="CarTitle.Light">
<item name="android:textColor">@color/car_title_light</item>
</style>
<!-- The styling for the main headline text. The color of this text changes based on the
day/night mode. -->
- <style name="CarHeadline1" >
+ <style name="CarHeadline1">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_headline1_size</item>
<item name="android:textColor">@color/car_headline1</item>
@@ -41,7 +41,7 @@
<!-- The styling for a sub-headline text. The color of this text changes based on the
day/night mode. -->
- <style name="CarHeadline2" >
+ <style name="CarHeadline2">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_headline2_size</item>
<item name="android:textColor">@color/car_headline2</item>
@@ -49,7 +49,7 @@
<!-- The styling for a smaller alternate headline text. The color of this text changes based on
the day/night mode. -->
- <style name="CarHeadline3" >
+ <style name="CarHeadline3">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_headline3_size</item>
<item name="android:textColor">@color/car_headline3</item>
@@ -57,14 +57,14 @@
<!-- The styling for the smallest headline text. The color of this text changes based on the
day/night mode. -->
- <style name="CarHeadline4" >
+ <style name="CarHeadline4">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_headline4_size</item>
<item name="android:textColor">@color/car_headline4</item>
</style>
<!-- The styling for body text. The color of this text changes based on the day/night mode. -->
- <style name="CarBody1" >
+ <style name="CarBody1">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_body1_size</item>
<item name="android:textColor">@color/car_body1</item>
@@ -72,7 +72,7 @@
<!-- An alternate styling for body text that is both a different color and size than
CarBody1. -->
- <style name="CarBody2" >
+ <style name="CarBody2">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_body2_size</item>
<item name="android:textColor">@color/car_body2</item>
@@ -80,7 +80,7 @@
<!-- A smaller styling for body text. The color of this text changes based on the day/night
mode. -->
- <style name="CarBody3" >
+ <style name="CarBody3">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_body3_size</item>
<item name="android:textColor">@color/car_body3</item>
@@ -88,7 +88,7 @@
<!-- The smallest styling for body text. The color of this text changes based on the day/night
mode. -->
- <style name="CarBody4" >
+ <style name="CarBody4">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_body4_size</item>
<item name="android:textColor">@color/car_body4</item>
@@ -119,14 +119,17 @@
<item name="android:background">@drawable/car_button_background</item>
</style>
- <style name="CarBorderlessButton" parent="android:Widget.Material.Button.Borderless">
+ <style name="CarButton.Borderless" parent="android:Widget.Material.Button.Borderless">
<item name="android:layout_height">@dimen/car_button_height</item>
- <item name="android:minWidth">@dimen/car_button_min_width</item>
- <item name="android:paddingStart">@dimen/car_button_horizontal_padding</item>
- <item name="android:paddingEnd">@dimen/car_button_horizontal_padding</item>
+ <item name="android:paddingStart">@dimen/car_borderless_button_horizontal_padding</item>
+ <item name="android:paddingEnd">@dimen/car_borderless_button_horizontal_padding</item>
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_action1_size</item>
- <item name="android:textColor">@drawable/car_button_text_color</item>
+ <item name="android:textColor">@drawable/car_borderless_button_text_color</item>
<item name="android:textAllCaps">true</item>
</style>
+
+ <!-- Style for the progress bars -->
+ <style name="CarProgressBar.Horizontal"
+ parent="android:Widget.Material.ProgressBar.Horizontal"/>
</resources>
diff --git a/car/src/main/java/android/support/car/drawer/CarDrawerActivity.java b/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
similarity index 98%
rename from car/src/main/java/android/support/car/drawer/CarDrawerActivity.java
rename to car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
index f46c652..55bb23c 100644
--- a/car/src/main/java/android/support/car/drawer/CarDrawerActivity.java
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package android.support.car.drawer;
+package androidx.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;
@@ -30,6 +29,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.car.R;
+
/**
* Common base Activity for car apps that need to present a Drawer.
*
diff --git a/car/src/main/java/android/support/car/drawer/CarDrawerAdapter.java b/car/src/main/java/androidx/car/drawer/CarDrawerAdapter.java
similarity index 98%
rename from car/src/main/java/android/support/car/drawer/CarDrawerAdapter.java
rename to car/src/main/java/androidx/car/drawer/CarDrawerAdapter.java
index b0fd965..ca16413 100644
--- a/car/src/main/java/android/support/car/drawer/CarDrawerAdapter.java
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerAdapter.java
@@ -14,20 +14,21 @@
* limitations under the License.
*/
-package android.support.car.drawer;
+package androidx.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;
+import androidx.car.R;
+import androidx.car.widget.PagedListView;
+
/**
* Base adapter for displaying items in the car navigation drawer, which uses a
* {@link PagedListView}.
diff --git a/car/src/main/java/android/support/car/drawer/CarDrawerController.java b/car/src/main/java/androidx/car/drawer/CarDrawerController.java
similarity index 97%
rename from car/src/main/java/android/support/car/drawer/CarDrawerController.java
rename to car/src/main/java/androidx/car/drawer/CarDrawerController.java
index 7b23714..e26054f 100644
--- a/car/src/main/java/android/support/car/drawer/CarDrawerController.java
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.drawer;
+package androidx.car.drawer;
import android.content.Context;
import android.content.res.Configuration;
@@ -22,8 +22,6 @@
import android.support.annotation.AnimRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.car.R;
-import android.support.car.widget.PagedListView;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.RecyclerView;
@@ -34,7 +32,10 @@
import android.view.animation.AnimationUtils;
import android.widget.ProgressBar;
-import java.util.Stack;
+import java.util.ArrayDeque;
+
+import androidx.car.R;
+import androidx.car.widget.PagedListView;
/**
* A controller that will handle the set up of the navigation drawer. It will hook up the
@@ -58,7 +59,7 @@
* this stack is the order that the user has visited each level. When the user navigates up,
* the adapters are popped from this list.
*/
- private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
+ private final ArrayDeque<CarDrawerAdapter> mAdapterStack = new ArrayDeque<>();
private final Context mContext;
@@ -114,11 +115,10 @@
}
// The root adapter is always the last item in the stack.
- if (mAdapterStack.size() > 0) {
- mAdapterStack.set(0, rootAdapter);
- } else {
- mAdapterStack.push(rootAdapter);
+ if (!mAdapterStack.isEmpty()) {
+ mAdapterStack.removeLast();
}
+ mAdapterStack.addLast(rootAdapter);
setToolbarTitleFrom(rootAdapter);
mDrawerList.setAdapter(rootAdapter);
diff --git a/car/src/main/java/android/support/car/drawer/DrawerItemClickListener.java b/car/src/main/java/androidx/car/drawer/DrawerItemClickListener.java
similarity index 95%
rename from car/src/main/java/android/support/car/drawer/DrawerItemClickListener.java
rename to car/src/main/java/androidx/car/drawer/DrawerItemClickListener.java
index d707dbd..4c0c7a2 100644
--- a/car/src/main/java/android/support/car/drawer/DrawerItemClickListener.java
+++ b/car/src/main/java/androidx/car/drawer/DrawerItemClickListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.drawer;
+package androidx.car.drawer;
/**
* Listener for handling clicks on items/views managed by {@link DrawerItemViewHolder}.
diff --git a/car/src/main/java/android/support/car/drawer/DrawerItemViewHolder.java b/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
similarity index 95%
rename from car/src/main/java/android/support/car/drawer/DrawerItemViewHolder.java
rename to car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
index d016b2d..8bbded9 100644
--- a/car/src/main/java/android/support/car/drawer/DrawerItemViewHolder.java
+++ b/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
@@ -14,19 +14,20 @@
* limitations under the License.
*/
-package android.support.car.drawer;
+package androidx.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;
+import androidx.car.R;
+
/**
* Re-usable {@link RecyclerView.ViewHolder} for displaying items in the
- * {@link android.support.car.drawer.CarDrawerAdapter}.
+ * {@link androidx.car.drawer.CarDrawerAdapter}.
*/
public class DrawerItemViewHolder extends RecyclerView.ViewHolder {
private final ImageView mIcon;
diff --git a/car/src/main/java/android/support/car/utils/ColumnCalculator.java b/car/src/main/java/androidx/car/utils/ColumnCalculator.java
similarity index 95%
rename from car/src/main/java/android/support/car/utils/ColumnCalculator.java
rename to car/src/main/java/androidx/car/utils/ColumnCalculator.java
index fa5dd43..35b1a91 100644
--- a/car/src/main/java/android/support/car/utils/ColumnCalculator.java
+++ b/car/src/main/java/androidx/car/utils/ColumnCalculator.java
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-package android.support.car.utils;
+package androidx.car.utils;
import android.content.Context;
import android.content.res.Resources;
-import android.support.car.R;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
+import androidx.car.R;
+
/**
* Utility class that calculates the size of the columns that will fit on the screen. A column's
* width is determined by the size of the margins and gutters (space between the columns) that fit
@@ -66,8 +67,8 @@
private ColumnCalculator(Context context) {
Resources res = context.getResources();
int marginSize = res.getDimensionPixelSize(R.dimen.car_margin);
- mGutterSize = res.getDimensionPixelSize(R.dimen.car_screen_gutter_size);
- mNumOfColumns = res.getInteger(R.integer.car_screen_num_of_columns);
+ mGutterSize = res.getDimensionPixelSize(R.dimen.car_gutter_size);
+ mNumOfColumns = res.getInteger(R.integer.car_column_number);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, String.format("marginSize: %d; numOfColumns: %d; gutterSize: %d",
diff --git a/car/src/main/java/android/support/car/widget/CarItemAnimator.java b/car/src/main/java/androidx/car/widget/CarItemAnimator.java
similarity index 98%
rename from car/src/main/java/android/support/car/widget/CarItemAnimator.java
rename to car/src/main/java/androidx/car/widget/CarItemAnimator.java
index ef22c48..e6bfd05 100644
--- a/car/src/main/java/android/support/car/widget/CarItemAnimator.java
+++ b/car/src/main/java/androidx/car/widget/CarItemAnimator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
diff --git a/car/src/main/java/android/support/car/widget/CarRecyclerView.java b/car/src/main/java/androidx/car/widget/CarRecyclerView.java
similarity index 98%
rename from car/src/main/java/android/support/car/widget/CarRecyclerView.java
rename to car/src/main/java/androidx/car/widget/CarRecyclerView.java
index bb9cb71..1d89ed1 100644
--- a/car/src/main/java/android/support/car/widget/CarRecyclerView.java
+++ b/car/src/main/java/androidx/car/widget/CarRecyclerView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.content.Context;
import android.graphics.Canvas;
diff --git a/car/src/main/java/android/support/car/widget/ColumnCardView.java b/car/src/main/java/androidx/car/widget/ColumnCardView.java
similarity index 95%
rename from car/src/main/java/android/support/car/widget/ColumnCardView.java
rename to car/src/main/java/androidx/car/widget/ColumnCardView.java
index 06f8553..9ec2bb6 100644
--- a/car/src/main/java/android/support/car/widget/ColumnCardView.java
+++ b/car/src/main/java/androidx/car/widget/ColumnCardView.java
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.content.Context;
import android.content.res.TypedArray;
-import android.support.car.R;
-import android.support.car.utils.ColumnCalculator;
import android.support.v7.widget.CardView;
import android.util.AttributeSet;
import android.util.Log;
+import androidx.car.R;
+import androidx.car.utils.ColumnCalculator;
+
/**
* A {@link CardView} whose width can be specified by the number of columns that it will span.
*
@@ -34,7 +35,7 @@
* a default span value that it uses.
*
* <pre>
- * <android.support.car.widget.ColumnCardView
+ * <androidx.car.widget.ColumnCardView
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* app:columnSpan="4" />
diff --git a/car/src/main/java/android/support/car/widget/DayNightStyle.java b/car/src/main/java/androidx/car/widget/DayNightStyle.java
similarity index 98%
rename from car/src/main/java/android/support/car/widget/DayNightStyle.java
rename to car/src/main/java/androidx/car/widget/DayNightStyle.java
index ff5a1b3..6e3ecbe 100644
--- a/car/src/main/java/android/support/car/widget/DayNightStyle.java
+++ b/car/src/main/java/androidx/car/widget/DayNightStyle.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.support.annotation.IntDef;
diff --git a/car/src/main/java/androidx/car/widget/ListItem.java b/car/src/main/java/androidx/car/widget/ListItem.java
new file mode 100644
index 0000000..c5b93d9
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ListItem.java
@@ -0,0 +1,708 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.car.R;
+
+/**
+ * Class to build a list item.
+ *
+ * <p>An item supports primary action and supplemental action(s).
+ *
+ * <p>An item visually composes of 3 parts; each part may contain multiple views.
+ * <ul>
+ * <li>{@code Primary Action}: represented by an icon of following types.
+ * <ul>
+ * <li>Primary Icon - icon size could be large or small.
+ * <li>No Icon
+ * <li>Empty Icon - different from No Icon by how much margin {@code Text} offsets
+ * </ul>
+ * <li>{@code Text}: supports any combination of the follow text views.
+ * <ul>
+ * <li>Title
+ * <li>Body
+ * </ul>
+ * <li>{@code Supplemental Action(s)}: represented by one of the following types; aligned toward
+ * the end of item.
+ * <ul>
+ * <li>Supplemental Icon
+ * <li>One Action Button
+ * <li>Two Action Buttons
+ * </ul>
+ * </ul>
+ *
+ * {@link ListItem} can be built through its {@link ListItem.Builder}. It binds data
+ * to {@link ListItemAdapter.ViewHolder} based on components selected.
+ */
+public class ListItem {
+
+ private Builder mBuilder;
+
+ private ListItem(Builder builder) {
+ mBuilder = builder;
+ }
+
+ /**
+ * Applies all {@link ViewBinder} to {@code viewHolder}.
+ */
+ void bind(ListItemAdapter.ViewHolder viewHolder) {
+ setAllSubViewsGone(viewHolder);
+ for (ViewBinder binder : mBuilder.mBinders) {
+ binder.bind(viewHolder);
+ }
+ }
+
+ void setAllSubViewsGone(ListItemAdapter.ViewHolder vh) {
+ View[] subviews = new View[] {
+ vh.getPrimaryIcon(),
+ vh.getTitle(), vh.getBody(),
+ vh.getSupplementalIcon(), vh.getSupplementalIconDivider(),
+ vh.getAction1(), vh.getAction1Divider(), vh.getAction2(), vh.getAction2Divider()};
+ for (View v : subviews) {
+ v.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Used by {@link ListItemAdapter} to choose layout to inflate for view holder.
+ * New view type needs support in {@link ListItemAdapter}.
+ */
+ protected int getViewType() {
+ return mBuilder.mIsCard
+ ? ListItemAdapter.CAR_PAGED_LIST_CARD
+ : ListItemAdapter.CAR_PAGED_LIST_ITEM;
+ }
+
+ /**
+ * Functional interface to provide a way to interact with views in
+ * {@link ListItemAdapter.ViewHolder}. {@code ViewBinder}s added to a
+ * {@code ListItem} will be called when {@code ListItem} {@code bind}s to
+ * {@link ListItemAdapter.ViewHolder}.
+ */
+ public interface ViewBinder {
+ /**
+ * Provides a way to interact with views in view holder.
+ */
+ void bind(ListItemAdapter.ViewHolder viewHolder);
+ }
+
+ /**
+ * Builds a {@link ListItem}.
+ *
+ * <p>With conflicting methods are called, e.g. setting primary action to both primary icon and
+ * no icon, the last called method wins.
+ */
+ public static class Builder {
+
+ @Retention(SOURCE)
+ @IntDef({
+ PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON,
+ PRIMARY_ACTION_TYPE_LARGE_ICON, PRIMARY_ACTION_TYPE_SMALL_ICON})
+ private @interface PrimaryActionType {}
+
+ private static final int PRIMARY_ACTION_TYPE_NO_ICON = 0;
+ private static final int PRIMARY_ACTION_TYPE_EMPTY_ICON = 1;
+ private static final int PRIMARY_ACTION_TYPE_LARGE_ICON = 2;
+ private static final int PRIMARY_ACTION_TYPE_SMALL_ICON = 3;
+
+ @Retention(SOURCE)
+ @IntDef({SUPPLEMENTAL_ACTION_NO_ACTION, SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON,
+ SUPPLEMENTAL_ACTION_ONE_ACTION, SUPPLEMENTAL_ACTION_TWO_ACTIONS})
+ private @interface SupplementalActionType {}
+
+ private static final int SUPPLEMENTAL_ACTION_NO_ACTION = 0;
+ private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON = 1;
+ private static final int SUPPLEMENTAL_ACTION_ONE_ACTION = 2;
+ private static final int SUPPLEMENTAL_ACTION_TWO_ACTIONS = 3;
+
+ private final Context mContext;
+ private final List<ViewBinder> mBinders = new ArrayList<>();
+ // Store custom binders separately so they will bind after binders are created in build().
+ private final List<ViewBinder> mCustomBinders = new ArrayList<>();
+
+ private boolean mIsCard;
+
+ private View.OnClickListener mOnClickListener;
+
+ @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
+ private int mPrimaryActionIconResId;
+ private Drawable mPrimaryActionIconDrawable;
+
+ private String mTitle;
+ private String mBody;
+ private boolean mIsBodyPrimary;
+
+ @SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION;
+ private int mSupplementalIconResId;
+ private View.OnClickListener mSupplementalIconOnClickListener;
+ private boolean mShowSupplementalIconDivider;
+
+ private String mAction1Text;
+ private View.OnClickListener mAction1OnClickListener;
+ private boolean mShowAction1Divider;
+ private String mAction2Text;
+ private View.OnClickListener mAction2OnClickListener;
+ private boolean mShowAction2Divider;
+
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Builds a {@link ListItem}. Adds {@link ViewBinder}s that will adjust layout in
+ * {@link ListItemAdapter.ViewHolder} depending on sub-views used.
+ */
+ public ListItem build() {
+ setItemLayoutHeight();
+ setPrimaryAction();
+ setText();
+ setSupplementalActions();
+ setOnClickListener();
+
+ mBinders.addAll(mCustomBinders);
+
+ return new ListItem(this);
+ }
+
+ /**
+ * Sets the height of item depending on which text field is set.
+ */
+ private void setItemLayoutHeight() {
+ if (TextUtils.isEmpty(mBody)) {
+ // If the item only has title or no text, it uses fixed-height as single line.
+ int height = (int) mContext.getResources().getDimension(
+ R.dimen.car_single_line_list_item_height);
+ mBinders.add((vh) -> {
+ RecyclerView.LayoutParams layoutParams =
+ (RecyclerView.LayoutParams) vh.itemView.getLayoutParams();
+ layoutParams.height = height;
+ vh.itemView.setLayoutParams(layoutParams);
+ });
+ } else {
+ // If body is present, the item should be at least as tall as min height, and wraps
+ // content.
+ int minHeight = (int) mContext.getResources().getDimension(
+ R.dimen.car_double_line_list_item_height);
+ mBinders.add((vh) -> {
+ vh.itemView.setMinimumHeight(minHeight);
+ vh.getContainerLayout().setMinimumHeight(minHeight);
+
+ RecyclerView.LayoutParams layoutParams =
+ (RecyclerView.LayoutParams) vh.itemView.getLayoutParams();
+ layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT;
+ vh.itemView.setLayoutParams(layoutParams);
+ });
+ }
+ }
+
+ private void setPrimaryAction() {
+ setPrimaryIconContent();
+ setPrimaryIconLayout();
+ }
+
+ private void setText() {
+ setTextContent();
+ setTextVerticalMargin();
+ // Only setting start margin because text end is relative to the start of supplemental
+ // actions.
+ setTextStartMargin();
+ }
+
+ private void setOnClickListener() {
+ if (mOnClickListener != null) {
+ mBinders.add(vh -> vh.itemView.setOnClickListener(mOnClickListener));
+ }
+ }
+
+ private void setPrimaryIconContent() {
+ switch (mPrimaryActionType) {
+ case PRIMARY_ACTION_TYPE_SMALL_ICON:
+ case PRIMARY_ACTION_TYPE_LARGE_ICON:
+ mBinders.add((vh) -> {
+ vh.getPrimaryIcon().setVisibility(View.VISIBLE);
+
+ if (mPrimaryActionIconDrawable != null) {
+ vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
+ } else if (mPrimaryActionIconResId != 0) {
+ vh.getPrimaryIcon().setImageResource(mPrimaryActionIconResId);
+ }
+ });
+ break;
+ case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+ case PRIMARY_ACTION_TYPE_NO_ICON:
+ // Do nothing.
+ break;
+ default:
+ throw new IllegalStateException("Unrecognizable primary action type.");
+ }
+ }
+
+ /**
+ * Sets layout params of primary icon.
+ *
+ * <p>Large icon will have no start margin, and always align center vertically.
+ *
+ * <p>Small icon will have start margin. When body text is present small icon uses a top
+ * margin otherwise align center vertically.
+ */
+ private void setPrimaryIconLayout() {
+ switch (mPrimaryActionType) {
+ case PRIMARY_ACTION_TYPE_SMALL_ICON:
+ mBinders.add(vh -> {
+ int iconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_primary_icon_size);
+ // Icon size.
+ RelativeLayout.LayoutParams layoutParams =
+ (RelativeLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
+ layoutParams.height = iconSize;
+ layoutParams.width = iconSize;
+
+ // Start margin.
+ layoutParams.setMarginStart(mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_keyline_1));
+
+ if (!TextUtils.isEmpty(mBody)) {
+ // Set top margin.
+ layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_padding_4);
+ } else {
+ // Centered vertically.
+ layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+ }
+ vh.getPrimaryIcon().setLayoutParams(layoutParams);
+ });
+ break;
+ case PRIMARY_ACTION_TYPE_LARGE_ICON:
+ mBinders.add(vh -> {
+ int iconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_single_line_list_item_height);
+ // Icon size.
+ RelativeLayout.LayoutParams layoutParams =
+ (RelativeLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
+ layoutParams.height = iconSize;
+ layoutParams.width = iconSize;
+
+ // No start margin.
+ layoutParams.setMarginStart(0);
+
+ // Always centered vertically.
+ layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+
+ vh.getPrimaryIcon().setLayoutParams(layoutParams);
+ });
+ break;
+ case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+ case PRIMARY_ACTION_TYPE_NO_ICON:
+ // Do nothing.
+ break;
+ default:
+ throw new IllegalStateException("Unrecognizable primary action type.");
+ }
+ }
+
+ private void setTextContent() {
+ if (!TextUtils.isEmpty(mTitle)) {
+ mBinders.add(vh -> {
+ vh.getTitle().setVisibility(View.VISIBLE);
+ vh.getTitle().setText(mTitle);
+ });
+ }
+ if (!TextUtils.isEmpty(mBody)) {
+ mBinders.add(vh -> {
+ vh.getBody().setVisibility(View.VISIBLE);
+ vh.getBody().setText(mBody);
+ });
+ }
+
+ if (mIsBodyPrimary) {
+ mBinders.add((vh) -> {
+ vh.getTitle().setTextAppearance(R.style.CarBody2);
+ vh.getBody().setTextAppearance(R.style.CarBody1);
+ });
+ } else {
+ mBinders.add((vh) -> {
+ vh.getTitle().setTextAppearance(R.style.CarBody1);
+ vh.getBody().setTextAppearance(R.style.CarBody2);
+ });
+ }
+ }
+
+ /**
+ * Sets start margin of text view depending on icon type.
+ */
+ private void setTextStartMargin() {
+ final int startMarginResId;
+ switch (mPrimaryActionType) {
+ case PRIMARY_ACTION_TYPE_NO_ICON:
+ startMarginResId = R.dimen.car_keyline_1;
+ break;
+ case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+ startMarginResId = R.dimen.car_keyline_3;
+ break;
+ case PRIMARY_ACTION_TYPE_SMALL_ICON:
+ startMarginResId = R.dimen.car_keyline_3;
+ break;
+ case PRIMARY_ACTION_TYPE_LARGE_ICON:
+ startMarginResId = R.dimen.car_keyline_4;
+ break;
+ default:
+ throw new IllegalStateException("Unrecognizable primary action type.");
+ }
+ int startMargin = mContext.getResources().getDimensionPixelSize(startMarginResId);
+ mBinders.add(vh -> {
+ RelativeLayout.LayoutParams titleLayoutParams =
+ (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
+ titleLayoutParams.setMarginStart(startMargin);
+ vh.getTitle().setLayoutParams(titleLayoutParams);
+
+ RelativeLayout.LayoutParams bodyLayoutParams =
+ (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
+ bodyLayoutParams.setMarginStart(startMargin);
+ vh.getBody().setLayoutParams(bodyLayoutParams);
+ });
+ }
+
+ /**
+ * Sets top/bottom margins of {@code Title} and {@code Body}.
+ */
+ private void setTextVerticalMargin() {
+ if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mBody)) {
+ // Title only - view is aligned center vertically by itself.
+ mBinders.add(vh -> {
+ RelativeLayout.LayoutParams layoutParams =
+ (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
+ layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+ vh.getTitle().setLayoutParams(layoutParams);
+ });
+ } else if (TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mBody)) {
+ mBinders.add(vh -> {
+ // Body uses top and bottom margin.
+ int margin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_padding_3);
+ RelativeLayout.LayoutParams layoutParams =
+ (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
+ layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+ layoutParams.topMargin = margin;
+ layoutParams.bottomMargin = margin;
+ vh.getBody().setLayoutParams(layoutParams);
+ });
+ } else {
+ mBinders.add(vh -> {
+ // Title has a top margin
+ Resources resources = mContext.getResources();
+ int padding1 = resources.getDimensionPixelSize(R.dimen.car_padding_1);
+ int padding3 = resources.getDimensionPixelSize(R.dimen.car_padding_3);
+ RelativeLayout.LayoutParams titleLayoutParams =
+ (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
+ titleLayoutParams.topMargin = padding3;
+ vh.getTitle().setLayoutParams(titleLayoutParams);
+ // Body is below title with a margin, and has bottom margin.
+ RelativeLayout.LayoutParams bodyLayoutParams =
+ (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
+ bodyLayoutParams.addRule(RelativeLayout.BELOW, R.id.title);
+ bodyLayoutParams.topMargin = padding1;
+ bodyLayoutParams.bottomMargin = padding3;
+ vh.getBody().setLayoutParams(bodyLayoutParams);
+ });
+ }
+ }
+
+ /**
+ * Sets up view(s) for supplemental action.
+ */
+ private void setSupplementalActions() {
+ switch (mSupplementalActionType) {
+ case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON:
+ mBinders.add((vh) -> {
+ vh.getSupplementalIcon().setVisibility(View.VISIBLE);
+ if (mShowSupplementalIconDivider) {
+ vh.getSupplementalIconDivider().setVisibility(View.VISIBLE);
+ }
+
+ vh.getSupplementalIcon().setImageResource(mSupplementalIconResId);
+ vh.getSupplementalIcon().setOnClickListener(
+ mSupplementalIconOnClickListener);
+ });
+ break;
+ case SUPPLEMENTAL_ACTION_TWO_ACTIONS:
+ mBinders.add((vh) -> {
+ vh.getAction2().setVisibility(View.VISIBLE);
+ if (mShowAction2Divider) {
+ vh.getAction2Divider().setVisibility(View.VISIBLE);
+ }
+
+ vh.getAction2().setText(mAction2Text);
+ vh.getAction2().setOnClickListener(mAction2OnClickListener);
+ });
+ // Fall through
+ case SUPPLEMENTAL_ACTION_ONE_ACTION:
+ mBinders.add((vh) -> {
+ vh.getAction1().setVisibility(View.VISIBLE);
+ if (mShowAction1Divider) {
+ vh.getAction1Divider().setVisibility(View.VISIBLE);
+ }
+
+ vh.getAction1().setText(mAction1Text);
+ vh.getAction1().setOnClickListener(mAction1OnClickListener);
+ });
+ break;
+ case SUPPLEMENTAL_ACTION_NO_ACTION:
+ // Do nothing
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized supplemental action type.");
+ }
+ }
+
+ /**
+ * Builds the item in a {@link android.support.v7.widget.CardView}.
+ *
+ * <p>Each item will have rounded corner, margin between items, and elevation.
+ *
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withCardLook() {
+ mIsCard = true;
+ return this;
+ }
+
+ /**
+ * Sets {@link View.OnClickListener} of {@code ListItem}.
+ *
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withOnClickListener(View.OnClickListener listener) {
+ mOnClickListener = listener;
+ return this;
+ }
+
+ /**
+ * Sets {@code Primary Action} to be represented by an icon.
+ *
+ * @param iconResId the resource identifier of the drawable.
+ * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item
+ * with only title set; useful for album cover art.
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withPrimaryActionIcon(@DrawableRes int iconResId, boolean useLargeIcon) {
+ return withPrimaryActionIcon(null, iconResId, useLargeIcon);
+ }
+
+ /**
+ * Sets {@code Primary Action} to be represented by an icon.
+ *
+ * @param drawable the Drawable to set, or null to clear the content.
+ * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item
+ * with only title set; useful for album cover art.
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withPrimaryActionIcon(Drawable drawable, boolean useLargeIcon) {
+ return withPrimaryActionIcon(drawable, 0, useLargeIcon);
+ }
+
+ private Builder withPrimaryActionIcon(Drawable drawable, @DrawableRes int iconResId,
+ boolean useLargeIcon) {
+ mPrimaryActionType = useLargeIcon
+ ? PRIMARY_ACTION_TYPE_LARGE_ICON
+ : PRIMARY_ACTION_TYPE_SMALL_ICON;
+ mPrimaryActionIconResId = iconResId;
+ mPrimaryActionIconDrawable = drawable;
+ return this;
+ }
+
+ /**
+ * Sets {@code Primary Action} to be empty icon.
+ *
+ * {@code Text} would have a start margin as if {@code Primary Action} were set to
+ * primary icon.
+ *
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withPrimaryActionEmptyIcon() {
+ mPrimaryActionType = PRIMARY_ACTION_TYPE_EMPTY_ICON;
+ return this;
+ }
+
+ /**
+ * Sets {@code Primary Action} to have no icon. Text would align to the start of item.
+ *
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withPrimaryActionNoIcon() {
+ mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
+ return this;
+ }
+
+ /**
+ * Sets the title of item.
+ *
+ * <p>Primary text is {@code title} by default. It can be set by
+ * {@link #withBody(String, boolean)}
+ *
+ * @param title text to display as title.
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets the body text of item.
+ *
+ * <p>Text beyond length required by regulation will be truncated. Defaults {@code Title}
+ * text as the primary.
+ *
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withBody(String body) {
+ return withBody(body, false);
+ }
+
+ /**
+ * Sets the body text of item.
+ *
+ * <p>Text beyond length required by regulation will be truncated.
+ *
+ * @param asPrimary sets {@code Body Text} as primary text of item.
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withBody(String body, boolean asPrimary) {
+ int limit = mContext.getResources().getInteger(
+ R.integer.car_list_item_text_length_limit);
+ if (body.length() < limit) {
+ mBody = body;
+ } else {
+ mBody = body.substring(0, limit) + mContext.getString(R.string.ellipsis);
+ }
+ mIsBodyPrimary = asPrimary;
+ return this;
+ }
+
+ /**
+ * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
+ *
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withSupplementalIcon(int iconResId, boolean showDivider) {
+ return withSupplementalIcon(iconResId, showDivider, null);
+ }
+
+ /**
+ * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
+ *
+ * @param iconResId drawable resource id.
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withSupplementalIcon(int iconResId, boolean showDivider,
+ View.OnClickListener listener) {
+ mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
+
+ mSupplementalIconResId = iconResId;
+ mSupplementalIconOnClickListener = listener;
+ mShowSupplementalIconDivider = showDivider;
+ return this;
+ }
+
+ /**
+ * Sets {@code Supplemental Action} to be represented by an {@code Action Button}.
+ *
+ * @param text button text to display.
+ * @return This Builder object to allow for chaining calls to set methods.
+ */
+ public Builder withAction(String text, boolean showDivider, View.OnClickListener listener) {
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Action text cannot be empty.");
+ }
+ mSupplementalActionType = SUPPLEMENTAL_ACTION_ONE_ACTION;
+
+ mAction1Text = text;
+ mAction1OnClickListener = listener;
+ mShowAction1Divider = showDivider;
+ return this;
+ }
+
+ /**
+ * Sets {@code Supplemental Action} to be represented by two {@code Action Button}s.
+ *
+ * <p>These two action buttons will be aligned towards item end.
+ *
+ * @param action1Text button text to display - this button will be closer to item end.
+ * @param action2Text button text to display.
+ */
+ public Builder withActions(String action1Text, boolean showAction1Divider,
+ View.OnClickListener action1OnClickListener,
+ String action2Text, boolean showAction2Divider,
+ View.OnClickListener action2OnClickListener) {
+ if (TextUtils.isEmpty(action1Text)) {
+ throw new IllegalArgumentException("Action1 text cannot be empty.");
+ }
+ if (TextUtils.isEmpty(action2Text)) {
+ throw new IllegalArgumentException("Action2 text cannot be empty.");
+ }
+ mSupplementalActionType = SUPPLEMENTAL_ACTION_TWO_ACTIONS;
+
+ mAction1Text = action1Text;
+ mAction1OnClickListener = action1OnClickListener;
+ mShowAction1Divider = showAction1Divider;
+ mAction2Text = action2Text;
+ mAction2OnClickListener = action2OnClickListener;
+ mShowAction2Divider = showAction2Divider;
+ return this;
+ }
+
+ /**
+ * Adds {@link ViewBinder} to interact with sub-views in
+ * {@link ListItemAdapter.ViewHolder}. These ViewBinders will always bind after
+ * other {@link Builder} methods have bond.
+ *
+ * <p>Make sure to call with...() method on the intended sub-view first.
+ *
+ * <p>Example:
+ * <pre>
+ * {@code
+ * new Builder()
+ * .withTitle("title")
+ * .withViewBinder((viewHolder) -> {
+ * viewHolder.getTitle().doMoreStuff();
+ * })
+ * .build();
+ * }
+ * </pre>
+ */
+ public Builder withViewBinder(ViewBinder binder) {
+ mCustomBinders.add(binder);
+ return this;
+ }
+ }
+}
diff --git a/car/src/main/java/androidx/car/widget/ListItemAdapter.java b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
new file mode 100644
index 0000000..2bdfae6
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.lang.annotation.Retention;
+
+import androidx.car.R;
+
+/**
+ * Adapter for {@link PagedListView} to display {@link ListItem}.
+ *
+ * Implements {@link PagedListView.ItemCap} - defaults to unlimited item count.
+ */
+public class ListItemAdapter extends
+ RecyclerView.Adapter<ListItemAdapter.ViewHolder> implements PagedListView.ItemCap {
+ @Retention(SOURCE)
+ @IntDef({CAR_PAGED_LIST_ITEM, CAR_PAGED_LIST_CARD})
+ public @interface PagedListItemType {}
+ public static final int CAR_PAGED_LIST_ITEM = 0;
+ public static final int CAR_PAGED_LIST_CARD = 1;
+
+ private final Context mContext;
+ private final ListItemProvider mItemProvider;
+
+ private int mMaxItems = PagedListView.ItemCap.UNLIMITED;
+
+ public ListItemAdapter(Context context, ListItemProvider itemProvider) {
+ mContext = context;
+ mItemProvider = itemProvider;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mItemProvider.get(position).getViewType();
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, @PagedListItemType int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ int layoutId;
+ switch (viewType) {
+ case CAR_PAGED_LIST_ITEM:
+ layoutId = R.layout.car_paged_list_item;
+ break;
+ case CAR_PAGED_LIST_CARD:
+ layoutId = R.layout.car_paged_list_card;
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognizable view type: " + viewType);
+ }
+ View itemView = inflater.inflate(layoutId, parent, false);
+ return new ViewHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ ListItem item = mItemProvider.get(position);
+ item.bind(holder);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mMaxItems == PagedListView.ItemCap.UNLIMITED
+ ? mItemProvider.size()
+ : Math.min(mItemProvider.size(), mMaxItems);
+ }
+
+ @Override
+ public void setMaxItems(int maxItems) {
+ mMaxItems = maxItems;
+ }
+
+ /**
+ * Holds views of an item in PagedListView.
+ *
+ * <p>This ViewHolder maps to views in layout car_paged_list_item_content.xml.
+ */
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+
+ private RelativeLayout mContainerLayout;
+
+ private ImageView mPrimaryIcon;
+
+ private TextView mTitle;
+ private TextView mBody;
+
+ private View mSupplementalIconDivider;
+ private ImageView mSupplementalIcon;
+
+ private Button mAction1;
+ private View mAction1Divider;
+
+ private Button mAction2;
+ private View mAction2Divider;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+
+ mContainerLayout = itemView.findViewById(R.id.container);
+
+ mPrimaryIcon = itemView.findViewById(R.id.primary_icon);
+
+ mTitle = itemView.findViewById(R.id.title);
+ mBody = itemView.findViewById(R.id.body);
+
+ mSupplementalIcon = itemView.findViewById(R.id.supplemental_icon);
+ mSupplementalIconDivider = itemView.findViewById(R.id.supplemental_icon_divider);
+
+ mAction1 = itemView.findViewById(R.id.action1);
+ mAction1Divider = itemView.findViewById(R.id.action1_divider);
+ mAction2 = itemView.findViewById(R.id.action2);
+ mAction2Divider = itemView.findViewById(R.id.action2_divider);
+ }
+
+ public RelativeLayout getContainerLayout() {
+ return mContainerLayout;
+ }
+
+ public ImageView getPrimaryIcon() {
+ return mPrimaryIcon;
+ }
+
+ public TextView getTitle() {
+ return mTitle;
+ }
+
+ public TextView getBody() {
+ return mBody;
+ }
+
+ public ImageView getSupplementalIcon() {
+ return mSupplementalIcon;
+ }
+
+ public View getSupplementalIconDivider() {
+ return mSupplementalIconDivider;
+ }
+
+ public Button getAction1() {
+ return mAction1;
+ }
+
+ public View getAction1Divider() {
+ return mAction1Divider;
+ }
+
+ public Button getAction2() {
+ return mAction2;
+ }
+
+ public View getAction2Divider() {
+ return mAction2Divider;
+ }
+ }
+}
diff --git a/car/src/main/java/androidx/car/widget/ListItemProvider.java b/car/src/main/java/androidx/car/widget/ListItemProvider.java
new file mode 100644
index 0000000..5a3edbd
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ListItemProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import java.util.List;
+
+/**
+ * Supplies data as {@link ListItem}.
+ */
+public abstract class ListItemProvider {
+
+ /**
+ * Returns {@link ListItem} at requested position.
+ */
+ public abstract ListItem get(int position);
+
+ /**
+ * @return number of total items.
+ */
+ public abstract int size();
+
+ /**
+ * A simple provider that wraps around a list.
+ */
+ public static class ListProvider extends ListItemProvider {
+ private final List<ListItem> mItems;
+
+ public ListProvider(List<ListItem> items) {
+ mItems = items;
+ }
+
+ @Override
+ public ListItem get(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public int size() {
+ return mItems.size();
+ }
+ }
+}
diff --git a/car/src/main/java/android/support/car/widget/PagedLayoutManager.java b/car/src/main/java/androidx/car/widget/PagedLayoutManager.java
similarity index 99%
rename from car/src/main/java/android/support/car/widget/PagedLayoutManager.java
rename to car/src/main/java/androidx/car/widget/PagedLayoutManager.java
index c4f469a..cf3b75d 100644
--- a/car/src/main/java/android/support/car/widget/PagedLayoutManager.java
+++ b/car/src/main/java/androidx/car/widget/PagedLayoutManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.content.Context;
import android.graphics.PointF;
@@ -23,7 +23,6 @@
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
-import android.support.car.R;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Recycler;
@@ -42,6 +41,8 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import androidx.car.R;
+
/**
* Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that
* it has a few tricks up its sleeve.
diff --git a/car/src/main/java/android/support/car/widget/PagedListView.java b/car/src/main/java/androidx/car/widget/PagedListView.java
similarity index 93%
rename from car/src/main/java/android/support/car/widget/PagedListView.java
rename to car/src/main/java/androidx/car/widget/PagedListView.java
index 67a6247..16593b9 100644
--- a/car/src/main/java/android/support/car/widget/PagedListView.java
+++ b/car/src/main/java/androidx/car/widget/PagedListView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
@@ -29,11 +29,11 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.UiThread;
-import android.support.car.R;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
@@ -43,6 +43,8 @@
import android.view.View;
import android.widget.FrameLayout;
+import androidx.car.R;
+
/**
* Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that
* resembles a {@link android.widget.ListView} but also has page up and page down arrows on the
@@ -119,6 +121,39 @@
}
/**
+ * The possible values for @{link #setGutter}. The default value is actually
+ * {@link Gutter#BOTH}.
+ */
+ @IntDef({
+ Gutter.NONE,
+ Gutter.START,
+ Gutter.END,
+ Gutter.BOTH,
+ })
+ public @interface Gutter {
+ /**
+ * No gutter on either side of the list items. The items will span the full width of the
+ * {@link PagedListView}.
+ */
+ int NONE = 0;
+
+ /**
+ * Include a gutter only on the start side (that is, the same side as the scroll bar).
+ */
+ int START = 1;
+
+ /**
+ * Include a gutter only on the end side (that is, the opposite side of the scroll bar).
+ */
+ int END = 2;
+
+ /**
+ * Include a gutter on both sides of the list items. This is the default behaviour.
+ */
+ int BOTH = 3;
+ }
+
+ /**
* Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to set the position
* offset for the adapter to load the data.
*
@@ -167,14 +202,17 @@
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_margin));
- params.setMarginEnd(
- a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0));
- mRecyclerView.setLayoutParams(params);
+ if (a.hasValue(R.styleable.PagedListView_gutter)) {
+ int gutter = a.getInt(R.styleable.PagedListView_gutter, Gutter.BOTH);
+ setGutter(gutter);
+ } else if (a.hasValue(R.styleable.PagedListView_offsetScrollBar)) {
+ boolean offsetScrollBar =
+ a.getBoolean(R.styleable.PagedListView_offsetScrollBar, false);
+ if (offsetScrollBar) {
+ setGutter(Gutter.START);
+ }
+ } else {
+ setGutter(Gutter.BOTH);
}
if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) {
@@ -286,6 +324,31 @@
return mLayoutManager.getPosition(v);
}
+ /**
+ * Set the gutter to the specified value.
+ *
+ * The gutter is the space to the start/end of the list view items and will be equal in size
+ * to the scroll bars. By default, there is a gutter to both the left and right of the list
+ * view items, to account for the scroll bar.
+ *
+ * @param gutter A {@link Gutter} value that identifies which sides to apply the gutter to.
+ */
+ public void setGutter(@Gutter int gutter) {
+ int startPadding = 0;
+ int endPadding = 0;
+ if ((gutter & Gutter.START) != 0) {
+ startPadding = getResources().getDimensionPixelSize(R.dimen.car_margin);
+ }
+ if ((gutter & Gutter.END) != 0) {
+ endPadding = getResources().getDimensionPixelSize(R.dimen.car_margin);
+ }
+ mRecyclerView.setPaddingRelative(startPadding, 0, endPadding, 0);
+
+ // If there's a gutter, set ClipToPadding to false so that CardView's shadow will still
+ // appear outside of the padding.
+ mRecyclerView.setClipToPadding(startPadding == 0 && endPadding == 0);
+ }
+
@NonNull
public CarRecyclerView getRecyclerView() {
return mRecyclerView;
@@ -940,7 +1003,7 @@
Resources res = context.getResources();
mPaint = new Paint();
mPaint.setColor(res.getColor(R.color.car_list_divider));
- mDividerHeight = res.getDimensionPixelSize(R.dimen.car_divider_height);
+ mDividerHeight = res.getDimensionPixelSize(R.dimen.car_list_divider_height);
}
/** Updates the list divider color which may have changed due to a day night transition. */
diff --git a/car/src/main/java/android/support/car/widget/PagedScrollBarView.java b/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
similarity index 96%
rename from car/src/main/java/android/support/car/widget/PagedScrollBarView.java
rename to car/src/main/java/androidx/car/widget/PagedScrollBarView.java
index 1c46b5d..6843fb6 100644
--- a/car/src/main/java/android/support/car/widget/PagedScrollBarView.java
+++ b/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
-import android.support.car.R;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -30,6 +29,8 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.car.R;
+
/** A custom view to provide list scroll behaviour -- up/down buttons and scroll indicator. */
public class PagedScrollBarView extends FrameLayout
implements View.OnClickListener, View.OnLongClickListener {
@@ -84,8 +85,10 @@
mScrollThumb = (ImageView) findViewById(R.id.scrollbar_thumb);
mFiller = findViewById(R.id.filler);
- mMinThumbLength = getResources().getDimensionPixelSize(R.dimen.min_thumb_height);
- mMaxThumbLength = getResources().getDimensionPixelSize(R.dimen.max_thumb_height);
+ mMinThumbLength = getResources()
+ .getDimensionPixelSize(R.dimen.car_min_scroll_bar_thumb_height);
+ mMaxThumbLength = getResources()
+ .getDimensionPixelSize(R.dimen.car_max_scroll_bar_thumb_height);
}
@Override
diff --git a/car/tests/AndroidManifest.xml b/car/tests/AndroidManifest.xml
index 949e85a..8e5422d 100644
--- a/car/tests/AndroidManifest.xml
+++ b/car/tests/AndroidManifest.xml
@@ -15,12 +15,12 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.car.widget.test">
+ package="androidx.car.widget.test">
<uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
<application android:supportsRtl="true">
- <activity android:name="android.support.car.widget.ColumnCardViewTestActivity"/>
- <activity android:name="android.support.car.widget.PagedListViewSavedStateActivity"/>
- <activity android:name="android.support.car.widget.PagedListViewTestActivity"/>
+ <activity android:name="androidx.car.widget.ColumnCardViewTestActivity"/>
+ <activity android:name="androidx.car.widget.PagedListViewSavedStateActivity"/>
+ <activity android:name="androidx.car.widget.PagedListViewTestActivity"/>
</application>
</manifest>
diff --git a/car/tests/res/layout/activity_column_card_view.xml b/car/tests/res/layout/activity_column_card_view.xml
index ad9c5e1..a6acc57 100644
--- a/car/tests/res/layout/activity_column_card_view.xml
+++ b/car/tests/res/layout/activity_column_card_view.xml
@@ -21,13 +21,13 @@
android:layout_height="match_parent"
android:orientation="vertical">
- <android.support.car.widget.ColumnCardView
+ <androidx.car.widget.ColumnCardView
android:id="@+id/default_width_column_card"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
- <android.support.car.widget.ColumnCardView
+ <androidx.car.widget.ColumnCardView
car:columnSpan="2"
android:id="@+id/span_2_column_card"
android:layout_gravity="center"
diff --git a/car/tests/res/layout/activity_paged_list_view.xml b/car/tests/res/layout/activity_paged_list_view.xml
index d14eb96..d7c8946 100644
--- a/car/tests/res/layout/activity_paged_list_view.xml
+++ b/car/tests/res/layout/activity_paged_list_view.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <android.support.car.widget.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/paged_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/car/tests/res/layout/activity_two_paged_list_view.xml b/car/tests/res/layout/activity_two_paged_list_view.xml
index 588071f..457cb95 100644
--- a/car/tests/res/layout/activity_two_paged_list_view.xml
+++ b/car/tests/res/layout/activity_two_paged_list_view.xml
@@ -21,7 +21,7 @@
android:layout_height="match_parent"
android:orientation="horizontal">
- <android.support.car.widget.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/paged_list_view_1"
android:layout_weight="1"
android:layout_width="0dp"
@@ -29,7 +29,7 @@
app:showPagedListViewDivider="false"
app:offsetScrollBar="true"/>
- <android.support.car.widget.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/paged_list_view_2"
android:layout_weight="1"
android:layout_width="0dp"
diff --git a/car/tests/res/layout/paged_list_item_column_card.xml b/car/tests/res/layout/paged_list_item_column_card.xml
index 2bc5cd0..5028abf 100644
--- a/car/tests/res/layout/paged_list_item_column_card.xml
+++ b/car/tests/res/layout/paged_list_item_column_card.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:orientation="vertical">
- <android.support.car.widget.ColumnCardView
+ <androidx.car.widget.ColumnCardView
android:id="@+id/column_card"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -30,6 +30,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
- </android.support.car.widget.ColumnCardView>
+ </androidx.car.widget.ColumnCardView>
</FrameLayout>
diff --git a/car/res/values-w840dp/integers.xml b/car/tests/res/values/strings.xml
similarity index 78%
copy from car/res/values-w840dp/integers.xml
copy to car/tests/res/values/strings.xml
index 38c0440..478abc4 100644
--- a/car/res/values-w840dp/integers.xml
+++ b/car/tests/res/values/strings.xml
@@ -14,6 +14,5 @@
limitations under the License.
-->
<resources>
- <integer name="car_screen_num_of_columns">12</integer>
- <integer name="column_card_default_column_span">8</integer>
+ <string name="over_120_chars">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</string>
</resources>
diff --git a/car/tests/src/android/support/car/widget/ColumnCardViewTest.java b/car/tests/src/androidx/car/widget/ColumnCardViewTest.java
similarity index 96%
rename from car/tests/src/android/support/car/widget/ColumnCardViewTest.java
rename to car/tests/src/androidx/car/widget/ColumnCardViewTest.java
index cb61caf..1963629 100644
--- a/car/tests/src/android/support/car/widget/ColumnCardViewTest.java
+++ b/car/tests/src/androidx/car/widget/ColumnCardViewTest.java
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.support.car.test.R;
-import android.support.car.utils.ColumnCalculator;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
@@ -32,6 +30,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.car.test.R;
+import androidx.car.utils.ColumnCalculator;
+
/** Instrumentation unit tests for {@link ColumnCardView}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/car/tests/src/android/support/car/widget/ColumnCardViewTestActivity.java b/car/tests/src/androidx/car/widget/ColumnCardViewTestActivity.java
similarity index 92%
rename from car/tests/src/android/support/car/widget/ColumnCardViewTestActivity.java
rename to car/tests/src/androidx/car/widget/ColumnCardViewTestActivity.java
index 693e4a1..3f42d62 100644
--- a/car/tests/src/android/support/car/widget/ColumnCardViewTestActivity.java
+++ b/car/tests/src/androidx/car/widget/ColumnCardViewTestActivity.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.app.Activity;
import android.os.Bundle;
-import android.support.car.test.R;
+
+import androidx.car.test.R;
public class ColumnCardViewTestActivity extends Activity {
@Override
diff --git a/car/tests/src/androidx/car/widget/ListItemTest.java b/car/tests/src/androidx/car/widget/ListItemTest.java
new file mode 100644
index 0000000..2b974c3
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/ListItemTest.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
+import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static junit.framework.TestCase.assertFalse;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.number.IsCloseTo.closeTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.drawable.Drawable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.CardView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.car.test.R;
+
+/**
+* Tests the layout configuration in {@link ListItem}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ListItemTest {
+
+ @Rule
+ public ActivityTestRule<PagedListViewTestActivity> mActivityRule =
+ new ActivityTestRule<>(PagedListViewTestActivity.class);
+
+ private PagedListViewTestActivity mActivity;
+ private PagedListView mPagedListView;
+
+ @Before
+ public void setUp() {
+ mActivity = mActivityRule.getActivity();
+ mPagedListView = mActivity.findViewById(R.id.paged_list_view);
+ }
+
+ private void setupPagedListView(List<ListItem> items) {
+ ListItemProvider provider = new ListItemProvider.ListProvider(
+ new ArrayList<>(items));
+ try {
+ mActivityRule.runOnUiThread(() -> {
+ mPagedListView.setAdapter(new ListItemAdapter(mActivity, provider));
+ });
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ throw new RuntimeException(throwable);
+ }
+ // Wait for paged list view to layout by using espresso to scroll to a position.
+ onView(withId(R.id.recycler_view)).perform(scrollToPosition(0));
+ }
+
+ private static void verifyViewIsHidden(View view) {
+ if (view instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) view;
+ final int childCount = viewGroup.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ verifyViewIsHidden(viewGroup.getChildAt(i));
+ }
+ } else {
+ assertThat(view.getVisibility(), is(equalTo(View.GONE)));
+ }
+ }
+
+ private ListItemAdapter.ViewHolder getViewHolderAtPosition(int position) {
+ return (ListItemAdapter.ViewHolder) mPagedListView.getRecyclerView()
+ .findViewHolderForAdapterPosition(
+ position);
+ }
+
+ @Test
+ public void testEmptyItemHidesAllViews() {
+ ListItem item = new ListItem.Builder(mActivity).build();
+ setupPagedListView(Arrays.asList(item));
+ verifyViewIsHidden(mPagedListView.findViewByPosition(0));
+ }
+
+ @Test
+ public void testPrimaryActionVisible() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+ .build());
+ setupPagedListView(items);
+
+ assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getVisibility(),
+ is(equalTo(View.VISIBLE)));
+ assertThat(getViewHolderAtPosition(1).getPrimaryIcon().getVisibility(),
+ is(equalTo(View.VISIBLE)));
+ }
+
+ @Test
+ public void testTextVisible() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withTitle("title")
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withBody("body")
+ .build());
+ setupPagedListView(items);
+
+ assertThat(getViewHolderAtPosition(0).getTitle().getVisibility(),
+ is(equalTo(View.VISIBLE)));
+ assertThat(getViewHolderAtPosition(1).getBody().getVisibility(),
+ is(equalTo(View.VISIBLE)));
+ }
+
+ @Test
+ public void testSupplementalActionVisible() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true)
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withAction("text", true, null)
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withActions("text", true, null,
+ "text", true, null)
+ .build());
+ setupPagedListView(items);
+
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
+ is(equalTo(View.VISIBLE)));
+
+ viewHolder = getViewHolderAtPosition(1);
+ assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.VISIBLE)));
+
+ viewHolder = getViewHolderAtPosition(2);
+ assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.VISIBLE)));
+ }
+
+ @Test
+ public void testDividersAreOptional() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, false)
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withAction("text", false, null)
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withActions("text", false, null,
+ "text", false, null)
+ .build());
+ setupPagedListView(items);
+
+ setupPagedListView(items);
+
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
+ is(equalTo(View.GONE)));
+
+ viewHolder = getViewHolderAtPosition(1);
+ assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
+
+ viewHolder = getViewHolderAtPosition(2);
+ assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
+ assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
+ assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.GONE)));
+ }
+
+ @Test
+ public void testTextStartMarginMatchesPrimaryActionType() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionEmptyIcon()
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionNoIcon()
+ .build());
+ List<Integer> expectedStartMargin = Arrays.asList(R.dimen.car_keyline_4,
+ R.dimen.car_keyline_3, R.dimen.car_keyline_3, R.dimen.car_keyline_1);
+ setupPagedListView(items);
+
+ for (int i = 0; i < items.size(); i++) {
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(i);
+
+ int expected = InstrumentationRegistry.getContext().getResources()
+ .getDimensionPixelSize(expectedStartMargin.get(i));
+ assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getTitle().getLayoutParams())
+ .getMarginStart(), is(equalTo(expected)));
+ assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getBody().getLayoutParams())
+ .getMarginStart(), is(equalTo(expected)));
+ }
+ }
+
+ @Test
+ public void testItemWithOnlyTitleIsSingleLine() {
+ List<ListItem> items = Arrays.asList(
+ // Only space
+ new ListItem.Builder(mActivity)
+ .withTitle(" ")
+ .build(),
+ // Underscore
+ new ListItem.Builder(mActivity)
+ .withTitle("______")
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withTitle("ALL UPPER CASE")
+ .build(),
+ // String wouldn't fit in one line
+ new ListItem.Builder(mActivity)
+ .withTitle(InstrumentationRegistry.getContext().getResources().getString(
+ R.string.over_120_chars))
+ .build());
+ setupPagedListView(items);
+
+ double singleLineHeight = InstrumentationRegistry.getContext().getResources().getDimension(
+ R.dimen.car_single_line_list_item_height);
+
+ for (int i = 0; i < items.size(); i++) {
+ assertThat((double) mPagedListView.findViewByPosition(i).getHeight(),
+ is(closeTo(singleLineHeight, 1.0d)));
+ }
+ }
+
+ @Test
+ public void testItemWithBodyTextIsAtLeastDoubleLine() {
+ List<ListItem> items = Arrays.asList(
+ // Only space
+ new ListItem.Builder(mActivity)
+ .withBody(" ")
+ .build(),
+ // Underscore
+ new ListItem.Builder(mActivity)
+ .withBody("____")
+ .build(),
+ // String wouldn't fit in one line
+ new ListItem.Builder(mActivity)
+ .withBody(InstrumentationRegistry.getContext().getResources().getString(
+ R.string.over_120_chars))
+ .build());
+ setupPagedListView(items);
+
+ final int doubleLineHeight =
+ (int) InstrumentationRegistry.getContext().getResources().getDimension(
+ R.dimen.car_double_line_list_item_height);
+
+ for (int i = 0; i < items.size(); i++) {
+ assertThat(mPagedListView.findViewByPosition(i).getHeight(),
+ is(greaterThanOrEqualTo(doubleLineHeight)));
+ }
+ }
+
+ @Test
+ public void testBodyTextLengthLimit() {
+ final String longText = InstrumentationRegistry.getContext().getResources().getString(
+ R.string.over_120_chars);
+ final int limit = InstrumentationRegistry.getContext().getResources().getInteger(
+ R.integer.car_list_item_text_length_limit);
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withBody(longText)
+ .build());
+ setupPagedListView(items);
+
+ // + 1 for appended ellipsis.
+ assertThat(getViewHolderAtPosition(0).getBody().getText().length(),
+ is(equalTo(limit + 1)));
+ }
+
+ @Test
+ public void testPrimaryIconDrawable() {
+ Drawable drawable = InstrumentationRegistry.getContext().getResources().getDrawable(
+ android.R.drawable.sym_def_app_icon, null);
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionIcon(drawable, true)
+ .build());
+ setupPagedListView(items);
+
+ assertTrue(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable().getConstantState()
+ .equals(drawable.getConstantState()));
+ }
+
+ @Test
+ public void testLargePrimaryIconHasNoStartMargin() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+ .build());
+ setupPagedListView(items);
+
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
+ .getMarginStart(), is(equalTo(0)));
+ }
+
+ @Test
+ public void testSmallPrimaryIconStartMargin() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+ .build());
+ setupPagedListView(items);
+
+ int expected = InstrumentationRegistry.getContext().getResources().getDimensionPixelSize(
+ R.dimen.car_keyline_1);
+
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
+ .getMarginStart(), is(equalTo(expected)));
+ }
+
+ @Test
+ public void testClickingPrimaryActionIsSeparateFromSupplementalAction() {
+ final boolean[] clicked = {false, false};
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withOnClickListener((v) -> clicked[0] = true)
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+ (v) -> clicked[1] = true)
+ .build());
+ setupPagedListView(items);
+
+ onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
+ assertTrue(clicked[0]);
+ assertFalse(clicked[1]);
+
+ onView(withId(R.id.recycler_view)).perform(
+ actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
+ assertTrue(clicked[1]);
+ }
+
+ @Test
+ public void testClickingSupplementalIcon() {
+ final boolean[] clicked = {false};
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+ (v) -> clicked[0] = true)
+ .build());
+ setupPagedListView(items);
+
+ onView(withId(R.id.recycler_view)).perform(
+ actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
+ assertTrue(clicked[0]);
+ }
+
+ @Test
+ public void testClickingSupplementalAction() {
+ final boolean[] clicked = {false};
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withAction("action", true, (v) -> clicked[0] = true)
+ .build());
+ setupPagedListView(items);
+
+ onView(withId(R.id.recycler_view)).perform(
+ actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
+ assertTrue(clicked[0]);
+ }
+
+ @Test
+ public void testClickingBothSupplementalActions() {
+ final boolean[] clicked = {false, false};
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withActions("action 1", true, (v) -> clicked[0] = true,
+ "action 2", true, (v) -> clicked[1] = true)
+ .build());
+ setupPagedListView(items);
+
+ onView(withId(R.id.recycler_view)).perform(
+ actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
+ assertTrue(clicked[0]);
+ assertFalse(clicked[1]);
+
+ onView(withId(R.id.recycler_view)).perform(
+ actionOnItemAtPosition(0, clickChildViewWithId(R.id.action2)));
+ assertTrue(clicked[1]);
+ }
+
+ @Test
+ public void testCustomViewBinderAreCalledLast() {
+ final String updatedTitle = "updated title";
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withTitle("original title")
+ .withViewBinder((viewHolder) -> viewHolder.getTitle().setText(updatedTitle))
+ .build());
+ setupPagedListView(items);
+
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ assertThat(viewHolder.getTitle().getText(), is(equalTo(updatedTitle)));
+ }
+
+ @Test
+ public void testCustomViewBinderOnUnusedViewsHasNoEffect() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withViewBinder((viewHolder) -> viewHolder.getBody().setText("text"))
+ .build());
+ setupPagedListView(items);
+
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ assertThat(viewHolder.getBody().getVisibility(), is(equalTo(View.GONE)));
+ // Custom binder interacts with body but has no effect.
+ // Expect card height to remain single line.
+ assertThat((double) viewHolder.itemView.getHeight(), is(closeTo(
+ InstrumentationRegistry.getContext().getResources().getDimension(
+ R.dimen.car_single_line_list_item_height), 1.0d)));
+ }
+
+ @Test
+ public void testCardLookUsesCardView() {
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withCardLook()
+ .build());
+ setupPagedListView(items);
+
+ ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ assertThat(viewHolder.itemView, is(instanceOf(CardView.class)));
+ }
+
+ @Test
+ public void testSettingTitleOrBodyAsPrimaryText() {
+ // Create 2 items, one with Title as primary (default) and one with Body.
+ // The primary text, regardless of view, should have consistent look (as primary).
+ List<ListItem> items = Arrays.asList(
+ new ListItem.Builder(mActivity)
+ .withTitle("title")
+ .withBody("body")
+ .build(),
+ new ListItem.Builder(mActivity)
+ .withTitle("title")
+ .withBody("body", true)
+ .build());
+ setupPagedListView(items);
+
+ ListItemAdapter.ViewHolder titlePrimary = getViewHolderAtPosition(0);
+ ListItemAdapter.ViewHolder bodyPrimary = getViewHolderAtPosition(1);
+ assertThat(titlePrimary.getTitle().getTextSize(),
+ is(equalTo(bodyPrimary.getBody().getTextSize())));
+ assertThat(titlePrimary.getTitle().getTextColors(),
+ is(equalTo(bodyPrimary.getBody().getTextColors())));
+ }
+
+ private static ViewAction clickChildViewWithId(final int id) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Click on a child view with specific id.";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ View v = view.findViewById(id);
+ v.performClick();
+ }
+ };
+ }
+}
diff --git a/car/tests/src/android/support/car/widget/PagedListViewSavedStateActivity.java b/car/tests/src/androidx/car/widget/PagedListViewSavedStateActivity.java
similarity index 93%
rename from car/tests/src/android/support/car/widget/PagedListViewSavedStateActivity.java
rename to car/tests/src/androidx/car/widget/PagedListViewSavedStateActivity.java
index 8cb976c..d9eecd5 100644
--- a/car/tests/src/android/support/car/widget/PagedListViewSavedStateActivity.java
+++ b/car/tests/src/androidx/car/widget/PagedListViewSavedStateActivity.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.app.Activity;
import android.os.Bundle;
-import android.support.car.test.R;
+
+import androidx.car.test.R;
/**
* Test Activity for testing the saving of state for the {@link PagedListView}. It will inflate
diff --git a/car/tests/src/android/support/car/widget/PagedListViewSavedStateTest.java b/car/tests/src/androidx/car/widget/PagedListViewSavedStateTest.java
similarity index 99%
rename from car/tests/src/android/support/car/widget/PagedListViewSavedStateTest.java
rename to car/tests/src/androidx/car/widget/PagedListViewSavedStateTest.java
index 9b871b3..b134e10 100644
--- a/car/tests/src/android/support/car/widget/PagedListViewSavedStateTest.java
+++ b/car/tests/src/androidx/car/widget/PagedListViewSavedStateTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
@@ -26,7 +26,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.support.car.test.R;
import android.support.test.espresso.IdlingRegistry;
import android.support.test.espresso.IdlingResource;
import android.support.test.filters.SmallTest;
@@ -50,6 +49,8 @@
import java.util.List;
import java.util.Random;
+import androidx.car.test.R;
+
/** Unit tests for the ability of the {@link PagedListView} to save state. */
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/car/tests/src/android/support/car/widget/PagedListViewTest.java b/car/tests/src/androidx/car/widget/PagedListViewTest.java
similarity index 99%
rename from car/tests/src/android/support/car/widget/PagedListViewTest.java
rename to car/tests/src/androidx/car/widget/PagedListViewTest.java
index 0d07fbd..e924f4b 100644
--- a/car/tests/src/android/support/car/widget/PagedListViewTest.java
+++ b/car/tests/src/androidx/car/widget/PagedListViewTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
@@ -36,7 +36,6 @@
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
-import android.support.car.test.R;
import android.support.test.annotation.UiThreadTest;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.IdlingResource;
@@ -61,6 +60,8 @@
import java.util.ArrayList;
import java.util.List;
+import androidx.car.test.R;
+
/** Unit tests for {@link PagedListView}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/car/tests/src/android/support/car/widget/PagedListViewTestActivity.java b/car/tests/src/androidx/car/widget/PagedListViewTestActivity.java
similarity index 93%
rename from car/tests/src/android/support/car/widget/PagedListViewTestActivity.java
rename to car/tests/src/androidx/car/widget/PagedListViewTestActivity.java
index 6371374..741cadd 100644
--- a/car/tests/src/android/support/car/widget/PagedListViewTestActivity.java
+++ b/car/tests/src/androidx/car/widget/PagedListViewTestActivity.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package android.support.car.widget;
+package androidx.car.widget;
import android.app.Activity;
import android.os.Bundle;
-import android.support.car.test.R;
+
+import androidx.car.test.R;
/**
* Simple test activity for {@link PagedListView} class.
diff --git a/compat/build.gradle b/compat/build.gradle
index b3fa298..a26eac8 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -13,8 +13,8 @@
transitive = true
}
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation project(':support-testutils'), {
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompat.java b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
index 1077b1f..80c7757 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
@@ -32,6 +32,7 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
@@ -116,7 +117,6 @@
* default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
*/
public static final int STREAM_DEFAULT = -1;
-
/**
* Bit set in the Notification flags field when LEDs should be turned on
* for this notification.
@@ -439,6 +439,14 @@
public static final int COLOR_DEFAULT = Color.TRANSPARENT;
/** @hide */
+ @RestrictTo(LIBRARY_GROUP)
+ @IntDef({AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING,
+ AudioManager.STREAM_MUSIC, AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_DTMF, AudioManager.STREAM_ACCESSIBILITY})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StreamType {}
+
+ /** @hide */
@Retention(SOURCE)
@IntDef({VISIBILITY_PUBLIC, VISIBILITY_PRIVATE, VISIBILITY_SECRET})
public @interface NotificationVisibility {}
@@ -957,6 +965,12 @@
public Builder setSound(Uri sound) {
mNotification.sound = sound;
mNotification.audioStreamType = Notification.STREAM_DEFAULT;
+ if (Build.VERSION.SDK_INT >= 21) {
+ mNotification.audioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build();
+ }
return this;
}
@@ -971,9 +985,15 @@
* @see Notification#STREAM_DEFAULT
* @see AudioManager for the <code>STREAM_</code> constants.
*/
- public Builder setSound(Uri sound, int streamType) {
+ public Builder setSound(Uri sound, @StreamType int streamType) {
mNotification.sound = sound;
mNotification.audioStreamType = streamType;
+ if (Build.VERSION.SDK_INT >= 21) {
+ mNotification.audioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setLegacyStreamType(streamType)
+ .build();
+ }
return this;
}
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java b/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
index 71f4160..db775a5 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
@@ -28,6 +28,7 @@
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
import android.util.SparseArray;
import android.widget.RemoteViews;
@@ -69,7 +70,6 @@
.setSmallIcon(n.icon, n.iconLevel)
.setContent(n.contentView)
.setTicker(n.tickerText, b.mTickerView)
- .setSound(n.sound, n.audioStreamType)
.setVibrate(n.vibrate)
.setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
.setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
@@ -86,6 +86,9 @@
.setLargeIcon(b.mLargeIcon)
.setNumber(b.mNumber)
.setProgress(b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
+ if (Build.VERSION.SDK_INT < 21) {
+ mBuilder.setSound(n.sound, n.audioStreamType);
+ }
if (Build.VERSION.SDK_INT >= 16) {
mBuilder.setSubText(b.mSubText)
.setUsesChronometer(b.mUseChronometer)
@@ -141,7 +144,8 @@
mBuilder.setCategory(b.mCategory)
.setColor(b.mColor)
.setVisibility(b.mVisibility)
- .setPublicVersion(b.mPublicVersion);
+ .setPublicVersion(b.mPublicVersion)
+ .setSound(n.sound, n.audioAttributes);
for (String person: b.mPeople) {
mBuilder.addPerson(person);
@@ -169,6 +173,13 @@
if (b.mColorizedSet) {
mBuilder.setColorized(b.mColorized);
}
+
+ if (!TextUtils.isEmpty(b.mChannelId)) {
+ mBuilder.setSound(null)
+ .setDefaults(0)
+ .setLights(0, 0, 0)
+ .setVibrate(null);
+ }
}
}
diff --git a/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java b/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java
index 1a0f1bc..07fcb6c 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java
@@ -43,10 +43,10 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -560,7 +560,7 @@
/** The service stub provided by onServiceConnected */
public INotificationSideChannel service;
/** Queue of pending tasks to send to this listener service */
- public LinkedList<Task> taskQueue = new LinkedList<Task>();
+ public ArrayDeque<Task> taskQueue = new ArrayDeque<>();
/** Number of retries attempted while connecting to this listener service */
public int retryCount = 0;
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index f23ac0d..4260c55 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -233,6 +233,9 @@
final ContentResolver resolver = context.getContentResolver();
try (ParcelFileDescriptor pfd =
resolver.openFileDescriptor(bestFont.getUri(), "r", cancellationSignal)) {
+ if (pfd == null) {
+ return null;
+ }
return new Typeface.Builder(pfd.getFileDescriptor())
.setWeight(bestFont.getWeight())
.setItalic(bestFont.isItalic())
diff --git a/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java b/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java
index 0926186..39acf68 100644
--- a/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java
+++ b/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java
@@ -274,7 +274,10 @@
: new ReplyCallback<TypefaceResult>() {
@Override
public void onReply(final TypefaceResult typeface) {
- if (typeface.mResult == FontFamilyResult.STATUS_OK) {
+ if (typeface == null) {
+ fontCallback.callbackFailAsync(
+ FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND, handler);
+ } else if (typeface.mResult == FontFamilyResult.STATUS_OK) {
fontCallback.callbackSuccessAsync(typeface.mTypeface, handler);
} else {
fontCallback.callbackFailAsync(typeface.mResult, handler);
diff --git a/compat/src/main/java/android/support/v4/util/ArraySet.java b/compat/src/main/java/android/support/v4/util/ArraySet.java
index ab080fa..a9a8806 100644
--- a/compat/src/main/java/android/support/v4/util/ArraySet.java
+++ b/compat/src/main/java/android/support/v4/util/ArraySet.java
@@ -16,9 +16,6 @@
package android.support.v4.util;
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.support.annotation.RestrictTo;
import android.util.Log;
import java.lang.reflect.Array;
@@ -74,7 +71,6 @@
static Object[] sTwiceBaseCache;
static int sTwiceBaseCacheSize;
- final boolean mIdentityHashCode;
int[] mHashes;
Object[] mArray;
int mSize;
@@ -238,19 +234,13 @@
* will grow once items are added to it.
*/
public ArraySet() {
- this(0, false);
+ this(0);
}
/**
* Create a new ArraySet with a given initial capacity.
*/
public ArraySet(int capacity) {
- this(capacity, false);
- }
-
- /** {@hide} */
- public ArraySet(int capacity, boolean identityHashCode) {
- mIdentityHashCode = identityHashCode;
if (capacity == 0) {
mHashes = INT;
mArray = OBJECT;
@@ -270,14 +260,6 @@
}
}
- /** {@hide} */
- public ArraySet(Collection<E> set) {
- this();
- if (set != null) {
- addAll(set);
- }
- }
-
/**
* Make the array map empty. All storage is released.
*/
@@ -326,8 +308,7 @@
* @return Returns the index of the value if it exists, else a negative integer.
*/
public int indexOf(Object key) {
- return key == null ? indexOfNull()
- : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
+ return key == null ? indexOfNull() : indexOf(key, key.hashCode());
}
/**
@@ -364,7 +345,7 @@
hash = 0;
index = indexOfNull();
} else {
- hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode();
+ hash = value.hashCode();
index = indexOf(value, hash);
}
if (index >= 0) {
@@ -406,36 +387,6 @@
}
/**
- * Special fast path for appending items to the end of the array without validation.
- * The array must already be large enough to contain the item.
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- public void append(E value) {
- final int index = mSize;
- final int hash = value == null ? 0
- : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
- if (index >= mHashes.length) {
- throw new IllegalStateException("Array is full");
- }
- if (index > 0 && mHashes[index - 1] > hash) {
- // Cannot optimize since it would break the sorted order - fallback to add()
- if (DEBUG) {
- RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
- Log.w(TAG, "New hash " + hash
- + " is before end of array hash " + mHashes[index - 1]
- + " at index " + index, e);
- }
- add(value);
- return;
- }
- mSize = index + 1;
- mHashes[index] = hash;
- mArray[index] = value;
- }
-
- /**
* Perform a {@link #add(Object)} of all values in <var>array</var>
* @param array The array whose contents are to be retrieved.
*/
diff --git a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
index dd870dd..7a5a57f 100644
--- a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
@@ -34,6 +34,9 @@
import android.annotation.TargetApi;
import android.app.Notification;
import android.content.Context;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -424,6 +427,71 @@
}
}
+ @Test
+ @SdkSuppress(minSdkVersion = 21)
+ public void testHasAudioAttributesFrom21() throws Throwable {
+ Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+ .setSound(Uri.EMPTY)
+ .build();
+ assertNotNull(n.audioAttributes);
+ assertEquals(-1, n.audioStreamType);
+ assertEquals(Uri.EMPTY, n.sound);
+
+ n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+ .setSound(Uri.EMPTY, AudioManager.STREAM_RING)
+ .build();
+ assertNotNull(n.audioAttributes);
+ assertEquals(AudioAttributes.CONTENT_TYPE_SONIFICATION,
+ n.audioAttributes.getContentType());
+ assertEquals(-1, n.audioStreamType);
+ assertEquals(Uri.EMPTY, n.sound);
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = 20)
+ public void testHasStreamTypePre21() throws Throwable {
+ Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+ .setSound(Uri.EMPTY, 34)
+ .build();
+ assertEquals(34, n.audioStreamType);
+ assertEquals(Uri.EMPTY, n.sound);
+ }
+
+ @SdkSuppress(minSdkVersion = 26)
+ @Test
+ public void testClearAlertingFieldsIfUsingChannels() throws Throwable {
+ long[] vibration = new long[]{100};
+
+ // stripped if using channels
+ Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity(), "test")
+ .setSound(Uri.EMPTY)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setVibrate(vibration)
+ .setLights(Color.BLUE, 100, 100)
+ .build();
+ assertNull(n.sound);
+ assertEquals(0, n.defaults);
+ assertNull(n.vibrate);
+ assertEquals(0, n.ledARGB);
+ assertEquals(0, n.ledOnMS);
+ assertEquals(0, n.ledOffMS);
+
+ // left intact if not using channels
+ n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+ .setSound(Uri.EMPTY)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setVibrate(vibration)
+ .setLights(Color.BLUE, 100, 100)
+ .build();
+ assertEquals(Uri.EMPTY, n.sound);
+ assertNotNull(n.audioAttributes);
+ assertEquals(Notification.DEFAULT_ALL, n.defaults);
+ assertEquals(vibration, n.vibrate);
+ assertEquals(Color.BLUE, n.ledARGB);
+ assertEquals(100, n.ledOnMS);
+ assertEquals(100, n.ledOffMS);
+ }
+
private static RemoteInput newDataOnlyRemoteInput() {
return new RemoteInput.Builder(DATA_RESULT_KEY)
.setAllowFreeFormInput(false)
diff --git a/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java b/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java
index 66bdd50..4edab02 100644
--- a/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java
+++ b/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java
@@ -40,9 +40,11 @@
import android.content.pm.ProviderInfo;
import android.content.pm.Signature;
import android.graphics.Typeface;
+import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.provider.FontsContractCompat.FontFamilyResult;
import android.support.v4.provider.FontsContractCompat.FontInfo;
import android.util.Base64;
@@ -56,6 +58,8 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link FontsContractCompat}.
@@ -111,34 +115,6 @@
InstrumentationRegistry.getInstrumentation().getTargetContext());
}
- private static class TestCallback extends FontsContractCompat.FontRequestCallback {
- private Typeface mTypeface;
-
- private int mSuccessCallCount;
- private int mFailedCallCount;
-
- public void onTypefaceRetrieved(Typeface typeface) {
- mTypeface = typeface;
- mSuccessCallCount++;
- }
-
- public void onTypefaceRequestFailed(int reason) {
- mFailedCallCount++;
- }
-
- public Typeface getTypeface() {
- return mTypeface;
- }
-
- public int getSuccessCallCount() {
- return mSuccessCallCount;
- }
-
- public int getFailedCallCount() {
- return mFailedCallCount;
- }
- }
-
@Test
public void typefaceNotCacheTest() throws NameNotFoundException {
FontRequest request = new FontRequest(
@@ -413,4 +389,43 @@
when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
return info;
}
+
+ @Test
+ public void testGetFontSync_invalidUri() throws InterruptedException {
+ final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ final FontRequest request = new FontRequest(
+ AUTHORITY, PACKAGE, MockFontProvider.INVALID_URI, SIGNATURE);
+ final CountDownLatch latch = new CountDownLatch(1);
+ final FontCallback callback = new FontCallback(latch);
+
+ inst.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ FontsContractCompat.getFontSync(mContext, request, callback, null,
+ false /* isBlockingFetch */, 300 /* timeout */, Typeface.NORMAL);
+ }
+ });
+ assertTrue(latch.await(5L, TimeUnit.SECONDS));
+ assertNull(callback.mTypeface);
+ }
+
+ public static class FontCallback extends ResourcesCompat.FontCallback {
+ private final CountDownLatch mLatch;
+ Typeface mTypeface;
+
+ FontCallback(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ @Override
+ public void onFontRetrieved(@NonNull Typeface typeface) {
+ mTypeface = typeface;
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onFontRetrievalFailed(int reason) {
+ mLatch.countDown();
+ }
+ }
}
diff --git a/compat/tests/java/android/support/v4/provider/MockFontProvider.java b/compat/tests/java/android/support/v4/provider/MockFontProvider.java
index f07d92d..f584d68 100644
--- a/compat/tests/java/android/support/v4/provider/MockFontProvider.java
+++ b/compat/tests/java/android/support/v4/provider/MockFontProvider.java
@@ -43,6 +43,7 @@
static final String[] FONT_FILES = {
"samplefont.ttf", "large_a.ttf", "large_b.ttf", "large_c.ttf", "large_d.ttf"
};
+ private static final int INVALID_FONT_FILE_ID = -1;
private static final int SAMPLE_FONT_FILE_0_ID = 0;
private static final int LARGE_A_FILE_ID = 1;
private static final int LARGE_B_FILE_ID = 2;
@@ -59,6 +60,7 @@
static final String NEGATIVE_ERROR_CODE_QUERY = "negativeCode";
static final String MANDATORY_FIELDS_ONLY_QUERY = "mandatoryFields";
static final String STYLE_TEST_QUERY = "styleTest";
+ static final String INVALID_URI = "invalidURI";
static class Font {
Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic,
@@ -176,6 +178,11 @@
Columns.RESULT_CODE_OK, true),
});
+ map.put(INVALID_URI, new Font[] {
+ new Font(id++, INVALID_FONT_FILE_ID, 0, null, 400, 0,
+ Columns.RESULT_CODE_OK, true),
+ });
+
QUERY_MAP = Collections.unmodifiableMap(map);
}
@@ -260,6 +267,9 @@
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) {
final int id = (int) ContentUris.parseId(uri);
+ if (id < 0) {
+ return null;
+ }
final File targetFile = getCopiedFile(getContext(), FONT_FILES[id]);
try {
return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
diff --git a/content/build.gradle b/content/build.gradle
index 15f96c4..9091053 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -27,8 +27,8 @@
api(project(":support-compat"))
androidTestImplementation(JUNIT)
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
}
android {
diff --git a/core-ui/build.gradle b/core-ui/build.gradle
index 8bab1cb..f7cd2d7 100644
--- a/core-ui/build.gradle
+++ b/core-ui/build.gradle
@@ -11,8 +11,8 @@
api(project(":support-compat"))
api project(':support-core-utils')
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation project(':support-testutils'), {
diff --git a/core-ui/tests/AndroidManifest.xml b/core-ui/tests/AndroidManifest.xml
index 0185f72..904111e 100644
--- a/core-ui/tests/AndroidManifest.xml
+++ b/core-ui/tests/AndroidManifest.xml
@@ -38,6 +38,8 @@
<activity android:name="android.support.v4.view.ViewPagerTest$ViewPagerActivity"/>
+ <activity android:name="android.support.design.widget.CoordinatorLayoutActivity"/>
+
</application>
</manifest>
diff --git a/design/tests/src/android/support/design/testutils/CoordinatorLayoutUtils.java b/core-ui/tests/java/android/support/design/testutils/CoordinatorLayoutUtils.java
similarity index 100%
rename from design/tests/src/android/support/design/testutils/CoordinatorLayoutUtils.java
rename to core-ui/tests/java/android/support/design/testutils/CoordinatorLayoutUtils.java
diff --git a/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutActivity.java b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutActivity.java
new file mode 100644
index 0000000..b7fe740
--- /dev/null
+++ b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.coreui.test.R;
+import android.support.v4.BaseTestActivity;
+import android.widget.FrameLayout;
+
+public class CoordinatorLayoutActivity extends BaseTestActivity {
+
+ FrameLayout mContainer;
+ CoordinatorLayout mCoordinatorLayout;
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.activity_coordinator_layout;
+ }
+
+ @Override
+ protected void onContentViewSet() {
+ mContainer = findViewById(R.id.container);
+ mCoordinatorLayout = findViewById(R.id.coordinator);
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutSortTest.java b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutSortTest.java
similarity index 91%
rename from design/tests/src/android/support/design/widget/CoordinatorLayoutSortTest.java
rename to core-ui/tests/java/android/support/design/widget/CoordinatorLayoutSortTest.java
index 0a407b7..4e0ccfc 100644
--- a/design/tests/src/android/support/design/widget/CoordinatorLayoutSortTest.java
+++ b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutSortTest.java
@@ -22,8 +22,10 @@
import android.support.design.testutils.CoordinatorLayoutUtils;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
import android.view.View;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -35,8 +37,9 @@
@RunWith(Parameterized.class)
@MediumTest
-public class CoordinatorLayoutSortTest
- extends BaseInstrumentationTestCase<CoordinatorLayoutActivity> {
+public class CoordinatorLayoutSortTest {
+ @Rule
+ public final ActivityTestRule<CoordinatorLayoutActivity> mActivityTestRule;
private static final int NUMBER_VIEWS_DEPENDENCY_SORT = 4;
@@ -60,7 +63,7 @@
public CoordinatorLayoutSortTest(int firstIndex, int secondIndex, int thirdIndex,
int fourthIndex) {
- super(CoordinatorLayoutActivity.class);
+ mActivityTestRule = new ActivityTestRule<>(CoordinatorLayoutActivity.class);
mFirstAddIndex = firstIndex;
mSecondAddIndex = secondIndex;
mThirdAddIndex = thirdIndex;
@@ -86,8 +89,8 @@
// Create a Behavior which depends on the previously added view
View dependency = i > 0 ? views.get(i - 1) : null;
- final CoordinatorLayout.Behavior<View> behavior
- = new CoordinatorLayoutUtils.DependentBehavior(dependency);
+ final CoordinatorLayout.Behavior<View> behavior =
+ new CoordinatorLayoutUtils.DependentBehavior(dependency);
// And set its LayoutParams to use the Behavior
CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutTest.java
similarity index 97%
rename from design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
rename to core-ui/tests/java/android/support/design/widget/CoordinatorLayoutTest.java
index 3588043..4062efb 100644
--- a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
+++ b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutTest.java
@@ -42,13 +42,15 @@
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.NonNull;
-import android.support.design.test.R;
+import android.support.coreui.test.R;
import android.support.design.testutils.CoordinatorLayoutUtils;
import android.support.design.testutils.CoordinatorLayoutUtils.DependentBehavior;
import android.support.design.widget.CoordinatorLayout.Behavior;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SdkSuppress;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
@@ -59,19 +61,24 @@
import android.widget.ImageView;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@MediumTest
-public class CoordinatorLayoutTest extends BaseInstrumentationTestCase<CoordinatorLayoutActivity> {
+@RunWith(AndroidJUnit4.class)
+public class CoordinatorLayoutTest {
+ @Rule
+ public final ActivityTestRule<CoordinatorLayoutActivity> mActivityTestRule;
private Instrumentation mInstrumentation;
public CoordinatorLayoutTest() {
- super(CoordinatorLayoutActivity.class);
+ mActivityTestRule = new ActivityTestRule<>(CoordinatorLayoutActivity.class);
}
@Before
@@ -383,14 +390,14 @@
final View viewA = new View(col.getContext());
final View viewB = new View(col.getContext());
final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
- final CoordinatorLayout.Behavior behavior
- = new CoordinatorLayoutUtils.DependentBehavior(viewA) {
- @Override
- public void onDependentViewRemoved(CoordinatorLayout parent, View child,
- View dependency) {
- parent.getDependencies(child);
- }
- };
+ final CoordinatorLayout.Behavior behavior =
+ new CoordinatorLayoutUtils.DependentBehavior(viewA) {
+ @Override
+ public void onDependentViewRemoved(
+ CoordinatorLayout parent, View child, View dependency) {
+ parent.getDependencies(child);
+ }
+ };
lpB.setBehavior(behavior);
// Now add views
diff --git a/core-ui/tests/res/layout/activity_coordinator_layout.xml b/core-ui/tests/res/layout/activity_coordinator_layout.xml
new file mode 100644
index 0000000..93bf947
--- /dev/null
+++ b/core-ui/tests/res/layout/activity_coordinator_layout.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.design.widget.CoordinatorLayout
+ android:id="@+id/coordinator"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/include_nestedscrollview.xml b/core-ui/tests/res/layout/include_nestedscrollview.xml
similarity index 100%
rename from design/tests/res/layout/include_nestedscrollview.xml
rename to core-ui/tests/res/layout/include_nestedscrollview.xml
diff --git a/core-ui/tests/res/values/ids.xml b/core-ui/tests/res/values/ids.xml
index e5fcf63..f3ac9b4 100644
--- a/core-ui/tests/res/values/ids.xml
+++ b/core-ui/tests/res/values/ids.xml
@@ -24,4 +24,5 @@
<item name="page_7" type="id"/>
<item name="page_8" type="id"/>
<item name="page_9" type="id"/>
+ <item name="anchor" type="id"/>
</resources>
\ No newline at end of file
diff --git a/core-utils/build.gradle b/core-utils/build.gradle
index de4f67c..3f1efa1 100644
--- a/core-utils/build.gradle
+++ b/core-utils/build.gradle
@@ -10,8 +10,8 @@
api(project(":support-annotations"))
api(project(":support-compat"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
}
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index abcca93..75e28f7 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -10,8 +10,8 @@
api(project(":support-compat"))
api(project(":support-annotations"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(project(":support-testutils"))
}
diff --git a/design/build.gradle b/design/build.gradle
index 804ed16..e7ebc91 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -12,8 +12,8 @@
api(project(":recyclerview-v7"))
api(project(":transition"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
diff --git a/design/tests/res/values/ids.xml b/design/tests/res/values/ids.xml
index 73540b7..52b8356 100644
--- a/design/tests/res/values/ids.xml
+++ b/design/tests/res/values/ids.xml
@@ -26,5 +26,4 @@
<item name="page_9" type="id"/>
<item name="textinputlayout" type="id"/>
<item name="textinputedittext" type="id"/>
- <item name="anchor" type="id"/>
</resources>
\ No newline at end of file
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index 21dbfb3..ac5623d 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -9,8 +9,8 @@
dependencies {
api(project(":support-core-utils"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
}
diff --git a/emoji/core/api/current.txt b/emoji/core/api/current.txt
index d5ce666..40bd7d8 100644
--- a/emoji/core/api/current.txt
+++ b/emoji/core/api/current.txt
@@ -32,6 +32,8 @@
method public android.support.text.emoji.EmojiCompat.Config setEmojiSpanIndicatorColor(int);
method public android.support.text.emoji.EmojiCompat.Config setEmojiSpanIndicatorEnabled(boolean);
method public android.support.text.emoji.EmojiCompat.Config setReplaceAll(boolean);
+ method public android.support.text.emoji.EmojiCompat.Config setUseEmojiAsDefaultStyle(boolean);
+ method public android.support.text.emoji.EmojiCompat.Config setUseEmojiAsDefaultStyle(boolean, java.util.List<java.lang.Integer>);
method public android.support.text.emoji.EmojiCompat.Config unregisterInitCallback(android.support.text.emoji.EmojiCompat.InitCallback);
}
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index 42bbf52..a311f25 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -24,8 +24,8 @@
api(project(":support-compat"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation project(':support-testutils')
@@ -97,12 +97,12 @@
if (i > 0) {
// If we get here, we know this is some timing issues. The jar file is
// properly created, but only if we wait.
- log.error("Succeeded in opening jar file '$f' after $i retries. Failing build.")
+ logger.error("Succeeded in opening jar file '$f' after $i retries. Failing build.")
throw new RuntimeException("Failed opening zip file earlier.")
}
break
} catch (ZipException e) {
- log.error("Error opening jar file '$f' (attempt: $i): $e.message")
+ logger.error("Error opening jar file '$f' (attempt: $i): $e.message")
sleep(1000)
} finally {
if (zip != null) {
diff --git a/emoji/core/src/main/java/android/support/text/emoji/EmojiCompat.java b/emoji/core/src/main/java/android/support/text/emoji/EmojiCompat.java
index 5436aa2..413a9dd 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/EmojiCompat.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/EmojiCompat.java
@@ -184,6 +184,16 @@
private final boolean mReplaceAll;
/**
+ * @see Config#setUseEmojiAsDefaultStyle(boolean)
+ */
+ private final boolean mUseEmojiAsDefaultStyle;
+
+ /**
+ * @see Config#setUseEmojiAsDefaultStyle(boolean, List)
+ */
+ private final int[] mEmojiAsDefaultStyleExceptions;
+
+ /**
* @see Config#setEmojiSpanIndicatorEnabled(boolean)
*/
private final boolean mEmojiSpanIndicatorEnabled;
@@ -201,6 +211,8 @@
private EmojiCompat(@NonNull final Config config) {
mInitLock = new ReentrantReadWriteLock();
mReplaceAll = config.mReplaceAll;
+ mUseEmojiAsDefaultStyle = config.mUseEmojiAsDefaultStyle;
+ mEmojiAsDefaultStyleExceptions = config.mEmojiAsDefaultStyleExceptions;
mEmojiSpanIndicatorEnabled = config.mEmojiSpanIndicatorEnabled;
mEmojiSpanIndicatorColor = config.mEmojiSpanIndicatorColor;
mMetadataLoader = config.mMetadataLoader;
@@ -787,6 +799,8 @@
public abstract static class Config {
private final MetadataRepoLoader mMetadataLoader;
private boolean mReplaceAll;
+ private boolean mUseEmojiAsDefaultStyle;
+ private int[] mEmojiAsDefaultStyleExceptions;
private Set<InitCallback> mInitCallbacks;
private boolean mEmojiSpanIndicatorEnabled;
private int mEmojiSpanIndicatorColor = Color.GREEN;
@@ -849,6 +863,56 @@
}
/**
+ * Determines whether EmojiCompat should use the emoji presentation style for emojis
+ * that have text style as default. By default, the text style would be used, unless these
+ * are followed by the U+FE0F variation selector.
+ * Details about emoji presentation and text presentation styles can be found here:
+ * http://unicode.org/reports/tr51/#Presentation_Style
+ * If useEmojiAsDefaultStyle is true, the emoji presentation style will be used for all
+ * emojis, including potentially unexpected ones (such as digits or other keycap emojis). If
+ * this is not the expected behaviour, method
+ * {@link #setUseEmojiAsDefaultStyle(boolean, List)} can be used to specify the
+ * exception emojis that should be still presented as text style.
+ *
+ * @param useEmojiAsDefaultStyle whether to use the emoji style presentation for all emojis
+ * that would be presented as text style by default
+ */
+ public Config setUseEmojiAsDefaultStyle(final boolean useEmojiAsDefaultStyle) {
+ return setUseEmojiAsDefaultStyle(useEmojiAsDefaultStyle, null);
+ }
+
+ /**
+ * @see #setUseEmojiAsDefaultStyle(boolean)
+ *
+ * @param emojiAsDefaultStyleExceptions Contains the exception emojis which will be still
+ * presented as text style even if the
+ * useEmojiAsDefaultStyle flag is set to {@code true}.
+ * This list will be ignored if useEmojiAsDefaultStyle
+ * is {@code false}. Note that emojis with default
+ * emoji style presentation will remain emoji style
+ * regardless the value of useEmojiAsDefaultStyle or
+ * whether they are included in the exceptions list or
+ * not. When no exception is wanted, the method
+ * {@link #setUseEmojiAsDefaultStyle(boolean)} should
+ * be used instead.
+ */
+ public Config setUseEmojiAsDefaultStyle(final boolean useEmojiAsDefaultStyle,
+ @Nullable final List<Integer> emojiAsDefaultStyleExceptions) {
+ mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle;
+ if (mUseEmojiAsDefaultStyle && emojiAsDefaultStyleExceptions != null) {
+ mEmojiAsDefaultStyleExceptions = new int[emojiAsDefaultStyleExceptions.size()];
+ int i = 0;
+ for (Integer exception : emojiAsDefaultStyleExceptions) {
+ mEmojiAsDefaultStyleExceptions[i++] = exception;
+ }
+ Arrays.sort(mEmojiAsDefaultStyleExceptions);
+ } else {
+ mEmojiAsDefaultStyleExceptions = null;
+ }
+ return this;
+ }
+
+ /**
* Determines whether a background will be drawn for the emojis that are found and
* replaced by EmojiCompat. Should be used only for debugging purposes. The indicator color
* can be set using {@link #setEmojiSpanIndicatorColor(int)}.
@@ -1020,7 +1084,9 @@
}
mMetadataRepo = metadataRepo;
- mProcessor = new EmojiProcessor(mMetadataRepo, new SpanFactory());
+ mProcessor = new EmojiProcessor(mMetadataRepo, new SpanFactory(),
+ mEmojiCompat.mUseEmojiAsDefaultStyle,
+ mEmojiCompat.mEmojiAsDefaultStyleExceptions);
mEmojiCompat.onMetadataLoadSuccess();
}
diff --git a/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java b/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java
index 488dcf9..7d495ed 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java
@@ -26,12 +26,13 @@
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
-import android.support.text.emoji.flatbuffer.MetadataItem;
-import android.support.text.emoji.flatbuffer.MetadataList;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import androidx.text.emoji.flatbuffer.MetadataItem;
+import androidx.text.emoji.flatbuffer.MetadataList;
+
/**
* Information about a single emoji.
*
diff --git a/emoji/core/src/main/java/android/support/text/emoji/EmojiProcessor.java b/emoji/core/src/main/java/android/support/text/emoji/EmojiProcessor.java
index 3feb36d..f711704 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/EmojiProcessor.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/EmojiProcessor.java
@@ -22,6 +22,7 @@
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
import android.support.text.emoji.widget.SpannableBuilder;
@@ -40,6 +41,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
/**
* Processes the CharSequence and adds the emojis.
@@ -90,14 +93,29 @@
*/
private GlyphChecker mGlyphChecker = new GlyphChecker();
+ /**
+ * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean)
+ */
+ private final boolean mUseEmojiAsDefaultStyle;
+
+ /**
+ * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean, List)
+ */
+ private final int[] mEmojiAsDefaultStyleExceptions;
+
EmojiProcessor(@NonNull final MetadataRepo metadataRepo,
- @NonNull final EmojiCompat.SpanFactory spanFactory) {
+ @NonNull final EmojiCompat.SpanFactory spanFactory,
+ final boolean useEmojiAsDefaultStyle,
+ @Nullable final int[] emojiAsDefaultStyleExceptions) {
mSpanFactory = spanFactory;
mMetadataRepo = metadataRepo;
+ mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle;
+ mEmojiAsDefaultStyleExceptions = emojiAsDefaultStyleExceptions;
}
EmojiMetadata getEmojiMetadata(@NonNull final CharSequence charSequence) {
- final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
+ final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode(),
+ mUseEmojiAsDefaultStyle, mEmojiAsDefaultStyleExceptions);
final int end = charSequence.length();
int currentOffset = 0;
@@ -189,7 +207,8 @@
}
// add new ones
int addedCount = 0;
- final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
+ final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode(),
+ mUseEmojiAsDefaultStyle, mEmojiAsDefaultStyleExceptions);
int currentOffset = start;
int codePoint = Character.codePointAt(charSequence, currentOffset);
@@ -483,9 +502,22 @@
*/
private int mCurrentDepth;
- ProcessorSm(MetadataRepo.Node rootNode) {
+ /**
+ * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean)
+ */
+ private final boolean mUseEmojiAsDefaultStyle;
+
+ /**
+ * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean, List)
+ */
+ private final int[] mEmojiAsDefaultStyleExceptions;
+
+ ProcessorSm(MetadataRepo.Node rootNode, boolean useEmojiAsDefaultStyle,
+ int[] emojiAsDefaultStyleExceptions) {
mRootNode = rootNode;
mCurrentNode = rootNode;
+ mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle;
+ mEmojiAsDefaultStyleExceptions = emojiAsDefaultStyleExceptions;
}
@Action
@@ -505,8 +537,7 @@
action = ACTION_ADVANCE_END;
} else if (mCurrentNode.getData() != null) {
if (mCurrentDepth == 1) {
- if (mCurrentNode.getData().isDefaultEmoji()
- || isEmojiStyle(mLastCodepoint)) {
+ if (shouldUseEmojiPresentationStyleForSingleCodepoint()) {
mFlushNode = mCurrentNode;
action = ACTION_FLUSH;
reset();
@@ -571,9 +602,32 @@
*/
boolean isInFlushableState() {
return mState == STATE_WALKING && mCurrentNode.getData() != null
- && (mCurrentNode.getData().isDefaultEmoji()
- || isEmojiStyle(mLastCodepoint)
- || mCurrentDepth > 1);
+ && (mCurrentDepth > 1 || shouldUseEmojiPresentationStyleForSingleCodepoint());
+ }
+
+ private boolean shouldUseEmojiPresentationStyleForSingleCodepoint() {
+ if (mCurrentNode.getData().isDefaultEmoji()) {
+ // The codepoint is emoji style by default.
+ return true;
+ }
+ if (isEmojiStyle(mLastCodepoint)) {
+ // The codepoint was followed by the emoji style variation selector.
+ return true;
+ }
+ if (mUseEmojiAsDefaultStyle) {
+ // Emoji presentation style for text style default emojis is enabled. We have
+ // to check that the current codepoint is not an exception.
+ if (mEmojiAsDefaultStyleExceptions == null) {
+ return true;
+ }
+ final int codepoint = mCurrentNode.getData().getCodepointAt(0);
+ final int index = Arrays.binarySearch(mEmojiAsDefaultStyleExceptions, codepoint);
+ if (index < 0) {
+ // Index is negative, so the codepoint was not found in the array of exceptions.
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java b/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java
index 1008c17..02856cb 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java
@@ -22,13 +22,14 @@
import android.support.annotation.IntRange;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
-import android.support.text.emoji.flatbuffer.MetadataList;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import androidx.text.emoji.flatbuffer.MetadataList;
+
/**
* Reads the emoji metadata from a given InputStream or ByteBuffer.
*
diff --git a/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java b/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java
index e86277e..f5afec8 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java
@@ -24,7 +24,6 @@
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
-import android.support.text.emoji.flatbuffer.MetadataList;
import android.support.v4.util.Preconditions;
import android.util.SparseArray;
@@ -32,6 +31,8 @@
import java.io.InputStream;
import java.nio.ByteBuffer;
+import androidx.text.emoji.flatbuffer.MetadataList;
+
/**
* Class to hold the emoji metadata required to process and draw emojis.
*/
diff --git a/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java b/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
index 0ce26e4..724f8a0 100644
--- a/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
@@ -87,8 +87,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
@SmallTest
@@ -729,6 +731,58 @@
assertFalse(EmojiCompat.handleOnKeyDown(editable, event.getKeyCode(), event));
}
+ @Test
+ @SdkSuppress(minSdkVersion = 19)
+ public void testUseEmojiAsDefaultStyle_whenEmojiInTheMiddle() {
+ final Config config = new TestConfig().setReplaceAll(true);
+ EmojiCompat.reset(config);
+ String s = new TestString(0x0061, CHAR_DEFAULT_TEXT_STYLE, 0x0062).toString();
+ // no span should be added as the emoji is text style presented by default
+ assertThat(EmojiCompat.get().process(s), not(hasEmoji()));
+ // a span should be added when we use the emoji style presentation as default
+ EmojiCompat.reset(config.setUseEmojiAsDefaultStyle(true));
+ assertThat(EmojiCompat.get().process(s), hasEmojiAt(DEFAULT_TEXT_STYLE, 1, 2));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 19)
+ public void testUseEmojiAsDefaultStyle_whenEmojiAtTheEnd() {
+ final Config config = new TestConfig().setReplaceAll(true);
+ EmojiCompat.reset(config);
+ String s = new TestString(0x0061, CHAR_DEFAULT_TEXT_STYLE).toString();
+ // no span should be added as the emoji is text style presented by default
+ assertThat(EmojiCompat.get().process(s), not(hasEmoji()));
+ // a span should be added when we use the emoji style presentation as default
+ EmojiCompat.reset(config.setUseEmojiAsDefaultStyle(true));
+ assertThat(EmojiCompat.get().process(s), hasEmojiAt(DEFAULT_TEXT_STYLE, 1, 2));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 19)
+ public void testUseEmojiAsDefaultStyle_noEmojisAdded_whenMarkedAsException() {
+ final String s = new TestString(CHAR_DEFAULT_TEXT_STYLE).toString();
+ final List<Integer> exceptions =
+ Arrays.asList(CHAR_DEFAULT_TEXT_STYLE + 1, CHAR_DEFAULT_TEXT_STYLE);
+ final Config config = new TestConfig().setReplaceAll(true)
+ .setUseEmojiAsDefaultStyle(true, exceptions);
+ EmojiCompat.reset(config);
+ // no span should be added as the text style codepoint is marked as exception
+ assertThat(EmojiCompat.get().process(s), not(hasEmoji()));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 19)
+ public void testUseEmojiAsDefaultStyle_emojisAdded_whenNotMarkedAsException() {
+ final String s = new TestString(CHAR_DEFAULT_TEXT_STYLE).toString();
+ final List<Integer> exceptions =
+ Arrays.asList(CHAR_DEFAULT_TEXT_STYLE - 1, CHAR_DEFAULT_TEXT_STYLE + 1);
+ final Config config = new TestConfig().setReplaceAll(true)
+ .setUseEmojiAsDefaultStyle(true, exceptions);
+ EmojiCompat.reset(config);
+ // a span should be added as the codepoint is not included in the set of exceptions
+ assertThat(EmojiCompat.get().process(s), hasEmojiAt(DEFAULT_TEXT_STYLE, 0, 1));
+ }
+
private void assertCodePointMatch(EmojiMapping emoji) {
assertCodePointMatch(emoji.id(), emoji.codepoints());
}
diff --git a/exifinterface/build.gradle b/exifinterface/build.gradle
index f5e63f6..fa4d7b4 100644
--- a/exifinterface/build.gradle
+++ b/exifinterface/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api(project(":support-annotations"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
}
android {
diff --git a/exifinterface/src/main/java/android/support/media/ExifInterface.java b/exifinterface/src/main/java/android/support/media/ExifInterface.java
index 72b61cb..eea69ab 100644
--- a/exifinterface/src/main/java/android/support/media/ExifInterface.java
+++ b/exifinterface/src/main/java/android/support/media/ExifInterface.java
@@ -4678,9 +4678,7 @@
private int getMimeType(BufferedInputStream in) throws IOException {
in.mark(SIGNATURE_CHECK_SIZE);
byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE];
- if (in.read(signatureCheckBytes) != SIGNATURE_CHECK_SIZE) {
- throw new EOFException();
- }
+ in.read(signatureCheckBytes);
in.reset();
if (isJpegFormat(signatureCheckBytes)) {
return IMAGE_TYPE_JPEG;
@@ -5333,7 +5331,7 @@
int dataFormat = dataInputStream.readUnsignedShort();
int numberOfComponents = dataInputStream.readInt();
// Next four bytes is for data offset or value.
- long nextEntryOffset = dataInputStream.peek() + 4;
+ long nextEntryOffset = dataInputStream.peek() + 4L;
// Look up a corresponding tag from tag number
ExifTag tag = (ExifTag) sExifTagMapsForReading[ifdType].get(tagNumber);
diff --git a/fragment/build.gradle b/fragment/build.gradle
index 720a68e..b1cc47e 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -12,8 +12,8 @@
api(project(":support-core-utils"))
api(project(":support-annotations"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation project(':support-testutils'), {
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index ffa850f..e76f846 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -10,8 +10,8 @@
api(project(":support-vector-drawable"))
api(project(":support-core-ui"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
}
android {
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 8e498fe..8575d6a 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -10,7 +10,7 @@
api(project(":support-annotations"))
api(project(":support-compat"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
}
android {
diff --git a/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java
index a34fe2b..943f1aa 100644
--- a/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -56,8 +56,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Stack;
/**
* For API 24 and above, this class is delegating to the framework's {@link VectorDrawable}.
@@ -730,7 +730,7 @@
// Use a stack to help to build the group tree.
// The top of the stack is always the current group.
- final Stack<VGroup> groupStack = new Stack<VGroup>();
+ final ArrayDeque<VGroup> groupStack = new ArrayDeque<>();
groupStack.push(pathRenderer.mRootGroup);
int eventType = parser.getEventType();
@@ -785,14 +785,7 @@
}
if (noPathTag) {
- final StringBuffer tag = new StringBuffer();
-
- if (tag.length() > 0) {
- tag.append(" or ");
- }
- tag.append(SHAPE_PATH);
-
- throw new XmlPullParserException("no " + tag + " defined");
+ throw new XmlPullParserException("no " + SHAPE_PATH + " defined");
}
}
diff --git a/jetifier/jetifier/build.gradle b/jetifier/jetifier/build.gradle
index e873ec3..c817220 100644
--- a/jetifier/jetifier/build.gradle
+++ b/jetifier/jetifier/build.gradle
@@ -18,7 +18,7 @@
ext.supportRootFolder = "${project.projectDir}/../../"
apply from: "$supportRootFolder/buildSrc/repos.gradle"
- ext.kotlin_version = '1.1.3'
+ ext.kotlin_version = '1.2.0'
repos.addMavenRepositories(repositories)
diff --git a/leanback/build.gradle b/leanback/build.gradle
index c07bc76..036f08f 100644
--- a/leanback/build.gradle
+++ b/leanback/build.gradle
@@ -13,8 +13,8 @@
api(project(":support-fragment"))
api(project(":recyclerview-v7"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
}
diff --git a/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
index 55fa758..c6589d4 100644
--- a/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -390,11 +390,7 @@
@Override
public String toString() {
- return new StringBuffer().append("horizontal=")
- .append(horizontal.toString())
- .append("; vertical=")
- .append(vertical.toString())
- .toString();
+ return "horizontal=" + horizontal + "; vertical=" + vertical;
}
}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java
index c0a2090..e04cdb4 100644
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java
@@ -108,6 +108,7 @@
* @return The current state of the Lifecycle.
*/
@MainThread
+ @NonNull
public abstract State getCurrentState();
@SuppressWarnings("WeakerAccess")
diff --git a/lifecycle/compiler/build.gradle b/lifecycle/compiler/build.gradle
index 211c65f..534d1ec 100644
--- a/lifecycle/compiler/build.gradle
+++ b/lifecycle/compiler/build.gradle
@@ -41,7 +41,7 @@
supportLibrary {
name = "Android Lifecycles Compiler"
publish = true
- mavenVersion = LibraryVersions.LIFECYCLES_CORE
+ mavenVersion = LibraryVersions.LIFECYCLES_EXT
mavenGroup = LibraryGroups.LIFECYCLE
inceptionYear = "2017"
description = "Android Lifecycles annotation processor"
diff --git a/lifecycle/extensions/build.gradle b/lifecycle/extensions/build.gradle
index b7166ea..38640b6 100644
--- a/lifecycle/extensions/build.gradle
+++ b/lifecycle/extensions/build.gradle
@@ -44,8 +44,8 @@
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation libs.support.app_compat, libs.support_exclude_config
}
diff --git a/lifecycle/integration-tests/testapp/build.gradle b/lifecycle/integration-tests/testapp/build.gradle
index c640eeb..19d556b 100644
--- a/lifecycle/integration-tests/testapp/build.gradle
+++ b/lifecycle/integration-tests/testapp/build.gradle
@@ -68,8 +68,8 @@
annotationProcessor(project(":lifecycle:compiler"))
androidTestAnnotationProcessor(project(":lifecycle:compiler"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
diff --git a/lifecycle/reactivestreams/build.gradle b/lifecycle/reactivestreams/build.gradle
index 76e9bc8..511ad7a 100644
--- a/lifecycle/reactivestreams/build.gradle
+++ b/lifecycle/reactivestreams/build.gradle
@@ -29,21 +29,19 @@
}
}
-allprojects {
- dependencies {
- api(project(":arch:common"))
- api(project(":lifecycle:common"))
- api(project(":lifecycle:extensions"))
- api(project(":lifecycle:runtime"))
- api libs.support.annotations
- api(REACTIVE_STREAMS)
+dependencies {
+ api(project(":arch:common"))
+ api(project(":lifecycle:common"))
+ api(project(":lifecycle:extensions"))
+ api(project(":lifecycle:runtime"))
+ api libs.support.annotations
+ api(REACTIVE_STREAMS)
- testImplementation(JUNIT)
- testImplementation(RX_JAVA)
- testImplementation(TEST_RUNNER, libs.exclude_annotations)
+ testImplementation(JUNIT)
+ testImplementation(RX_JAVA)
+ testImplementation(TEST_RUNNER)
- androidTestImplementation libs.support.app_compat, libs.support_exclude_config
- }
+ androidTestImplementation libs.support.app_compat, libs.support_exclude_config
}
supportLibrary {
diff --git a/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java b/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
index ba76f8e..ed3c57c 100644
--- a/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -50,8 +50,9 @@
* will buffer the latest item and emit it to the subscriber when data is again requested. Any
* other items emitted during the time there was no backpressure requested will be dropped.
*/
+ @NonNull
public static <T> Publisher<T> toPublisher(
- final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+ @NonNull LifecycleOwner lifecycle, @NonNull LiveData<T> liveData) {
return new LiveDataPublisher<>(lifecycle, liveData);
}
@@ -60,7 +61,7 @@
final LifecycleOwner mLifecycle;
final LiveData<T> mLiveData;
- LiveDataPublisher(final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+ LiveDataPublisher(LifecycleOwner lifecycle, LiveData<T> liveData) {
this.mLifecycle = lifecycle;
this.mLiveData = liveData;
}
@@ -91,7 +92,7 @@
}
@Override
- public void onChanged(T t) {
+ public void onChanged(@Nullable T t) {
if (mCanceled) {
return;
}
@@ -183,7 +184,8 @@
*
* @param <T> The type of data hold by this instance.
*/
- public static <T> LiveData<T> fromPublisher(final Publisher<T> publisher) {
+ @NonNull
+ public static <T> LiveData<T> fromPublisher(@NonNull Publisher<T> publisher) {
return new PublisherLiveData<>(publisher);
}
@@ -209,10 +211,10 @@
* @param <T> The type of data hold by this instance.
*/
private static class PublisherLiveData<T> extends LiveData<T> {
- private final Publisher mPublisher;
+ private final Publisher<T> mPublisher;
final AtomicReference<LiveDataSubscriber> mSubscriber;
- PublisherLiveData(@NonNull final Publisher publisher) {
+ PublisherLiveData(@NonNull Publisher<T> publisher) {
mPublisher = publisher;
mSubscriber = new AtomicReference<>();
}
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
index 96f9a6f..44e992b 100644
--- a/lifecycle/runtime/build.gradle
+++ b/lifecycle/runtime/build.gradle
@@ -27,7 +27,7 @@
testImplementation(MOCKITO_CORE)
androidTestImplementation(JUNIT)
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
}
supportLibrary {
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
index bf8aff7..eff946b 100644
--- a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
+++ b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
@@ -225,6 +225,7 @@
return mObserverMap.size();
}
+ @NonNull
@Override
public State getCurrentState() {
return mState;
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index b832de6..18ff5a3 100644
--- a/media-compat/build.gradle
+++ b/media-compat/build.gradle
@@ -10,8 +10,8 @@
api(project(":support-annotations"))
api(project(":support-compat"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation project(':support-testutils')
diff --git a/media-compat/version-compat-tests/current/client/build.gradle b/media-compat/version-compat-tests/current/client/build.gradle
index 24e46d4..aeb82c1 100644
--- a/media-compat/version-compat-tests/current/client/build.gradle
+++ b/media-compat/version-compat-tests/current/client/build.gradle
@@ -24,7 +24,7 @@
androidTestImplementation(project(":support-media-compat"))
androidTestImplementation(project(":support-media-compat-test-lib"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
}
android {
diff --git a/media-compat/version-compat-tests/current/service/build.gradle b/media-compat/version-compat-tests/current/service/build.gradle
index 1318d33..cf82918 100644
--- a/media-compat/version-compat-tests/current/service/build.gradle
+++ b/media-compat/version-compat-tests/current/service/build.gradle
@@ -24,7 +24,7 @@
androidTestImplementation(project(":support-media-compat"))
androidTestImplementation(project(":support-media-compat-test-lib"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
}
android {
diff --git a/media-compat/version-compat-tests/runtest.sh b/media-compat/version-compat-tests/runtest.sh
index d1a3c3a..817cd33 100755
--- a/media-compat/version-compat-tests/runtest.sh
+++ b/media-compat/version-compat-tests/runtest.sh
@@ -19,17 +19,28 @@
# - Exactly one test device should be connected.
#
# TODO:
-# - The test result should be easily seen. (Can we report the results to the Sponge?)
-# - Run specific combination of the test (e.g. Only want to test ToT-ToT)
-# - Run specific test class / method by using argument.
# - Support simultaneous multiple device connection
-# Usage './runtest.sh'
+# Usage './runtest.sh <version_combination_number> [option]'
CLIENT_MODULE_NAME_BASE="support-media-compat-test-client"
SERVICE_MODULE_NAME_BASE="support-media-compat-test-service"
CLIENT_VERSION=""
SERVICE_VERSION=""
+OPTION_TEST_TARGET=""
+
+function printRunTestUsage() {
+ echo "Usage: ./runtest.sh <version_combination_number> [option]"
+ echo ""
+ echo "Version combination number:"
+ echo " 1. Client-ToT / Service-ToT"
+ echo " 2. Client-ToT / Service-Latest release"
+ echo " 3. Client-Latest release / Service-ToT"
+ echo " 4. Run all of the above"
+ echo ""
+ echo "Option:"
+ echo " -t <class/method>: Only run the specific test class/method."
+}
function runTest() {
echo "Running test: Client-$CLIENT_VERSION / Service-$SERVICE_VERSION"
@@ -38,42 +49,59 @@
local SERVICE_MODULE_NAME="$SERVICE_MODULE_NAME_BASE$([ "$SERVICE_VERSION" = "tot" ] || echo "-previous")"
# Build test apks
- ./gradlew $CLIENT_MODULE_NAME:assembleDebugAndroidTest || (echo "Build failed. Aborting."; return 1)
- ./gradlew $SERVICE_MODULE_NAME:assembleDebugAndroidTest || (echo "Build failed. Aborting."; return 1)
+ ./gradlew $CLIENT_MODULE_NAME:assembleDebugAndroidTest || { echo "Build failed. Aborting."; exit 1; }
+ ./gradlew $SERVICE_MODULE_NAME:assembleDebugAndroidTest || { echo "Build failed. Aborting."; exit 1; }
# Install the apks
- adb install -r -d "../../out/dist/$CLIENT_MODULE_NAME.apk" || (echo "Apk installation failed. Aborting."; return 1)
- adb install -r -d "../../out/dist/$SERVICE_MODULE_NAME.apk" || (echo "Apk installation failed. Aborting."; return 1)
+ adb install -r -d "../../out/dist/$CLIENT_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
+ adb install -r -d "../../out/dist/$SERVICE_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
# Run the tests
+ local test_command="adb shell am instrument -w -e debug false -e client_version $CLIENT_VERSION -e service_version $SERVICE_VERSION"
+ local client_test_runner="android.support.mediacompat.client.test/android.support.test.runner.AndroidJUnitRunner"
+ local service_test_runner="android.support.mediacompat.service.test/android.support.test.runner.AndroidJUnitRunner"
+
echo ">>>>>>>>>>>>>>>>>>>>>>>> Test Started: Client-$CLIENT_VERSION & Service-$SERVICE_VERSION <<<<<<<<<<<<<<<<<<<<<<<<"
- adb shell am instrument -w -r -e package android.support.mediacompat.client -e debug false -e client_version $CLIENT_VERSION \
- -e service_version $SERVICE_VERSION android.support.mediacompat.client.test/android.support.test.runner.AndroidJUnitRunner
- adb shell am instrument -w -r -e package android.support.mediacompat.service -e debug false -e client_version $CLIENT_VERSION \
- -e service_version $SERVICE_VERSION android.support.mediacompat.service.test/android.support.test.runner.AndroidJUnitRunner
+
+ if [[ $OPTION_TEST_TARGET == *"client"* ]]; then
+ ${test_command} $OPTION_TEST_TARGET ${client_test_runner}
+ elif [[ $OPTION_TEST_TARGET == *"service"* ]]; then
+ ${test_command} $OPTION_TEST_TARGET ${service_test_runner}
+ else
+ ${test_command} ${client_test_runner}
+ ${test_command} ${service_test_runner}
+ fi
+
echo ">>>>>>>>>>>>>>>>>>>>>>>> Test Ended: Client-$CLIENT_VERSION & Service-$SERVICE_VERSION <<<<<<<<<<<<<<<<<<<<<<<<<<"
}
OLD_PWD=$(pwd)
-if [[ $OLD_PWD != *"frameworks/support"* ]]; then
- echo "Current working directory is" $OLD_PWD.
+
+if ! cd "$(echo $OLD_PWD | awk -F'frameworks/support' '{print $1}')"/frameworks/support &> /dev/null
+then
+ echo "Current working directory is $OLD_PWD"
echo "Please re-run this script in any folder under frameworks/support."
exit 1;
-else
- # Change working directory to frameworks/support
- cd "$(echo $OLD_PWD | awk -F'frameworks/support' '{print $1}')"/frameworks/support
fi
-echo "Choose the support library versions of the test you want to run:"
-echo " 1. Client-ToT / Service-ToT"
-echo " 2. Client-ToT / Service-Latest release"
-echo " 3. Client-Latest release / Service-ToT"
-echo " 4. Run all of the above"
-printf "Pick one of them: "
+if [[ $# -eq 0 || $1 -le 0 || $1 -gt 4 ]]
+then
+ printRunTestUsage
+ exit 1;
+fi
-read ANSWER
-case $ANSWER in
+if [[ ${2} == "-t" ]]; then
+ if [[ ${3} == *"client"* || ${3} == *"service"* ]]; then
+ OPTION_TEST_TARGET="-e class ${3}"
+ else
+ echo "Wrong test class/method name. Aborting."
+ echo "It should be in the form of \"<FULL_CLASS_NAME>[#METHOD_NAME]\"."
+ exit 1;
+ fi
+fi
+
+case ${1} in
1)
CLIENT_VERSION="tot"
SERVICE_VERSION="tot"
diff --git a/paging/common/src/main/java/android/arch/paging/DataSource.java b/paging/common/src/main/java/android/arch/paging/DataSource.java
index 3cf35c4..23f8154 100644
--- a/paging/common/src/main/java/android/arch/paging/DataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/DataSource.java
@@ -30,11 +30,8 @@
* Base class for loading pages of snapshot data into a {@link PagedList}.
* <p>
* DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as
- * it loads more data, but the data loaded cannot be updated.
- * <p>
- * A PagedList / DataSource pair serve as a snapshot of the data set being loaded. If the
- * underlying data set is modified, a new PagedList / DataSource pair must be created to represent
- * the new data.
+ * it loads more data, but the data loaded cannot be updated. If the underlying data set is
+ * modified, a new PagedList / DataSource pair must be created to represent the new data.
* <h4>Loading Pages</h4>
* PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter}
* calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
@@ -68,18 +65,23 @@
* copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
* snapshot can be created.
* <h4>Implementing a DataSource</h4>
- * To implement, extend either the {@link KeyedDataSource}, or {@link PositionalDataSource}
- * subclass. Choose based on whether each load operation is based on the position of the data in the
- * list.
+ * To implement, extend one of the subclasses: {@link PageKeyedDataSource},
+ * {@link ItemKeyedDataSource}, or {@link PositionalDataSource}.
* <p>
- * Use {@link KeyedDataSource} if you need to use data from item {@code N-1} to load item
+ * Use {@link PageKeyedDataSource} if pages you load embed keys for loading adjacent pages. For
+ * example a network response that returns some items, and a next/previous page links.
+ * <p>
+ * Use {@link ItemKeyedDataSource} if you need to use data from item {@code N-1} to load item
* {@code N}. For example, if requesting the backend for the next comments in the list
* requires the ID or timestamp of the most recent loaded comment, or if querying the next users
* from a name-sorted database query requires the name and unique ID of the previous.
* <p>
- * Use {@link PositionalDataSource} if you can load arbitrary pages based solely on position
- * information, and can provide a fixed item count. PositionalDataSource supports querying pages at
- * arbitrary positions, so can provide data to PagedLists in arbitrary order.
+ * Use {@link PositionalDataSource} if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. PositionalDataSource supports querying pages at
+ * arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that
+ * PositionalDataSource is required to respect page size for efficient tiling. If you want to
+ * override page size (e.g. when network page size constraints are only known at runtime), use one
+ * of the other DataSource classes.
* <p>
* Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not
* return {@code null} items in lists that it loads. This is so that users of the PagedList
@@ -115,8 +117,13 @@
/**
* Create a DataSource.
* <p>
- * The DataSource should invalidate itself if the snapshot is no longer valid, and a new
- * DataSource should be queried from the Factory.
+ * The DataSource should invalidate itself if the snapshot is no longer valid. If a
+ * DataSource becomes invalid, the only way to query more data is to create a new DataSource
+ * from the Factory.
+ * <p>
+ * {@link LivePagedListBuilder} for example will construct a new PagedList and DataSource
+ * when the current DataSource is invalidated, and pass the new PagedList through the
+ * {@code LiveData<PagedList>} to observers.
*
* @return the new DataSource.
*/
@@ -159,11 +166,11 @@
private Executor mPostExecutor = null;
private boolean mHasSignalled = false;
- BaseLoadCallback(@PageResult.ResultType int resultType, @NonNull DataSource dataSource,
+ BaseLoadCallback(@NonNull DataSource dataSource, @PageResult.ResultType int resultType,
@Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ mDataSource = dataSource;
mResultType = resultType;
mPostExecutor = mainThreadExecutor;
- mDataSource = dataSource;
mReceiver = receiver;
}
diff --git a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/ItemKeyedDataSource.java
similarity index 88%
rename from paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
rename to paging/common/src/main/java/android/arch/paging/ItemKeyedDataSource.java
index ff4b1d4..b113af4 100644
--- a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/ItemKeyedDataSource.java
@@ -26,14 +26,20 @@
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
* <p>
- * Implement a DataSource using KeyedDataSource if you need to use data from item {@code N - 1}
+ * Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
* to load item {@code N}. This is common, for example, in sorted database queries where
* attributes of the item such just before the next query define how to execute it.
+ * <p>
+ * The {@code InMemoryByItemRepository} in the
+ * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
+ * shows how to implement a network ItemKeyedDataSource using
+ * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
+ * handling swipe-to-refresh, network errors, and retry.
*
* @param <Key> Type of data used to query Value types out of the DataSource.
* @param <Value> Type of items being loaded by the DataSource.
*/
-public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
+public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
/**
* Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
@@ -46,7 +52,8 @@
* Load items around this key, or at the beginning of the data set if {@code null} is
* passed.
* <p>
- * Note that this key is generally a hint
+ * Note that this key is generally a hint, and may be ignored if you want to always load
+ * from the beginning.
*/
@Nullable
public final Key requestedInitialKey;
@@ -107,6 +114,12 @@
* <p>
* A callback can be called only once, and will throw if called again.
* <p>
+ * If you can compute the number of items in the data set before and after the loaded range,
+ * call the three parameter {@link #onResult(List, int, int)} to pass that information. You
+ * can skip passing this information by calling the single parameter {@link #onResult(List)},
+ * either if it's difficult to compute, or if {@link LoadInitialParams#placeholdersEnabled} is
+ * {@code false}, so the positioning information will be ignored.
+ * <p>
* It is always valid for a DataSource loading method that takes a callback to stash the
* callback and call it later. This enables DataSources to be fully asynchronous, and to handle
* temporary, recoverable error states (such as a network error that can be retried).
@@ -115,7 +128,7 @@
*/
public static class LoadInitialCallback<Value> extends LoadCallback<Value> {
private final boolean mCountingEnabled;
- LoadInitialCallback(@NonNull KeyedDataSource dataSource, boolean countingEnabled,
+ LoadInitialCallback(@NonNull ItemKeyedDataSource dataSource, boolean countingEnabled,
@NonNull PageResult.Receiver<Value> receiver) {
super(dataSource, PageResult.INIT, null, receiver);
mCountingEnabled = countingEnabled;
@@ -156,7 +169,7 @@
}
/**
- * Callback for KeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)}
+ * Callback for ItemKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)}
* and {@link #loadAfter(LoadParams, LoadCallback)} to return data.
* <p>
* A callback can be called only once, and will throw if called again.
@@ -168,16 +181,16 @@
* @param <Value> Type of items being loaded.
*/
public static class LoadCallback<Value> extends BaseLoadCallback<Value> {
- LoadCallback(@NonNull KeyedDataSource dataSource, @PageResult.ResultType int type,
+ LoadCallback(@NonNull ItemKeyedDataSource dataSource, @PageResult.ResultType int type,
@Nullable Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
- super(type, dataSource, mainThreadExecutor, receiver);
+ super(dataSource, type, mainThreadExecutor, receiver);
}
/**
* Called to pass loaded data from a DataSource.
* <p>
- * Call this method from your KeyedDataSource's
+ * Call this method from your ItemKeyedDataSource's
* {@link #loadBefore(LoadParams, LoadCallback)} and
* {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
* <p>
@@ -187,7 +200,7 @@
* It is always valid to pass a different amount of data than what is requested. Pass an
* empty list if there is no more data to load.
*
- * @param data List of items loaded from the KeyedDataSource.
+ * @param data List of items loaded from the ItemKeyedDataSource.
*/
public void onResult(@NonNull List<Value> data) {
dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
@@ -301,7 +314,7 @@
/**
* Return a key associated with the given item.
* <p>
- * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique
+ * If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
* integer ID, you would return {@code item.getID()} here. This key can then be passed to
* {@link #loadBefore(LoadParams, LoadCallback)} or
* {@link #loadAfter(LoadParams, LoadCallback)} to load additional items adjacent to the item
diff --git a/paging/common/src/main/java/android/arch/paging/PageKeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/PageKeyedDataSource.java
new file mode 100644
index 0000000..c12df3d
--- /dev/null
+++ b/paging/common/src/main/java/android/arch/paging/PageKeyedDataSource.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging;
+
+import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Incremental data loader for page-keyed content, where requests return keys for next/previous
+ * pages.
+ * <p>
+ * Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
+ * to load page {@code N}. This is common, for example, in network APIs that include a next/previous
+ * link or key with each page load.
+ * <p>
+ * The {@code InMemoryByPageRepository} in the
+ * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
+ * shows how to implement a network PageKeyedDataSource using
+ * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
+ * handling swipe-to-refresh, network errors, and retry.
+ *
+ * @param <Key> Type of data used to query Value types out of the DataSource.
+ * @param <Value> Type of items being loaded by the DataSource.
+ */
+public abstract class PageKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
+ private final Object mKeyLock = new Object();
+
+ @Nullable
+ @GuardedBy("mKeyLock")
+ private Key mNextKey = null;
+
+ @Nullable
+ @GuardedBy("mKeyLock")
+ private Key mPreviousKey = null;
+
+ private void initKeys(@Nullable Key previousKey, @Nullable Key nextKey) {
+ synchronized (mKeyLock) {
+ mPreviousKey = previousKey;
+ mNextKey = nextKey;
+ }
+ }
+
+ private void setPreviousKey(@Nullable Key previousKey) {
+ synchronized (mKeyLock) {
+ mPreviousKey = previousKey;
+ }
+ }
+
+ private void setNextKey(@Nullable Key nextKey) {
+ synchronized (mKeyLock) {
+ mNextKey = nextKey;
+ }
+ }
+
+ private @Nullable Key getPreviousKey() {
+ synchronized (mKeyLock) {
+ return mPreviousKey;
+ }
+ }
+
+ private @Nullable Key getNextKey() {
+ synchronized (mKeyLock) {
+ return mNextKey;
+ }
+ }
+
+ /**
+ * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
+ *
+ * @param <Key> Type of data used to query pages.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static class LoadInitialParams<Key> {
+ /**
+ * Requested number of items to load.
+ * <p>
+ * Note that this may be larger than available data.
+ */
+ public final int requestedLoadSize;
+
+ /**
+ * Defines whether placeholders are enabled, and whether the total count passed to
+ * {@link LoadInitialCallback#onResult(List, int, int, Key, Key)} will be ignored.
+ */
+ public final boolean placeholdersEnabled;
+
+
+ LoadInitialParams(int requestedLoadSize,
+ boolean placeholdersEnabled) {
+ this.requestedLoadSize = requestedLoadSize;
+ this.placeholdersEnabled = placeholdersEnabled;
+ }
+ }
+
+ /**
+ * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)} and
+ * {@link #loadAfter(LoadParams, LoadCallback)}.
+ *
+ * @param <Key> Type of data used to query pages.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static class LoadParams<Key> {
+ /**
+ * Load items before/after this key.
+ * <p>
+ * Returned data must begin directly adjacent to this position.
+ */
+ public final Key key;
+
+ /**
+ * Requested number of items to load.
+ * <p>
+ * Returned page can be of this size, but it may be altered if that is easier, e.g. a
+ * network data source where the backend defines page size.
+ */
+ public final int requestedLoadSize;
+
+ LoadParams(Key key, int requestedLoadSize) {
+ this.key = key;
+ this.requestedLoadSize = requestedLoadSize;
+ }
+ }
+
+ /**
+ * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
+ * to return data and, optionally, position/count information.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * If you can compute the number of items in the data set before and after the loaded range,
+ * call the five parameter {@link #onResult(List, int, int, Object, Object)} to pass that
+ * information. You can skip passing this information by calling the three parameter
+ * {@link #onResult(List, Object, Object)}, either if it's difficult to compute, or if
+ * {@link LoadInitialParams#placeholdersEnabled} is {@code false}, so the positioning
+ * information will be ignored.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <Key> Type of data used to query pages.
+ * @param <Value> Type of items being loaded.
+ */
+ public static class LoadInitialCallback<Key, Value> extends BaseLoadCallback<Value> {
+ private final PageKeyedDataSource<Key, Value> mDataSource;
+ private final boolean mCountingEnabled;
+ LoadInitialCallback(@NonNull PageKeyedDataSource<Key, Value> dataSource,
+ boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
+ super(dataSource, PageResult.INIT, null, receiver);
+ mDataSource = dataSource;
+ mCountingEnabled = countingEnabled;
+ }
+
+ /**
+ * Called to pass initial load state from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ * <p>
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be loaded from this DataSource,
+ * pass {@code N}.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial {@code data} parameter
+ * as well as any items that can be loaded in front or behind of
+ * {@code data}.
+ */
+ public void onResult(@NonNull List<Value> data, int position, int totalCount,
+ @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
+ validateInitialLoadParams(data, position, totalCount);
+
+ // setup keys before dispatching data, so guaranteed to be ready
+ mDataSource.initKeys(previousPageKey, nextPageKey);
+
+ int trailingUnloadedCount = totalCount - position - data.size();
+ if (mCountingEnabled) {
+ dispatchResultToReceiver(new PageResult<>(
+ data, position, trailingUnloadedCount, 0));
+ } else {
+ dispatchResultToReceiver(new PageResult<>(data, position));
+ }
+ }
+
+ /**
+ * Called to pass loaded data from a DataSource.
+ * <p>
+ * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
+ * initialize without counting available data, or supporting placeholders.
+ * <p>
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the PageKeyedDataSource.
+ * @param previousPageKey Key for page before the initial load result, or {@code null} if no
+ * more data can be loaded before.
+ * @param nextPageKey Key for page after the initial load result, or {@code null} if no
+ * more data can be loaded after.
+ */
+ public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
+ @Nullable Key nextPageKey) {
+ mDataSource.initKeys(previousPageKey, nextPageKey);
+ dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+ }
+ }
+
+ /**
+ * Callback for PageKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)} and
+ * {@link #loadAfter(LoadParams, LoadCallback)} to return data.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <Key> Type of data used to query pages.
+ * @param <Value> Type of items being loaded.
+ */
+ public static class LoadCallback<Key, Value> extends BaseLoadCallback<Value> {
+ private final PageKeyedDataSource<Key, Value> mDataSource;
+ LoadCallback(@NonNull PageKeyedDataSource<Key, Value> dataSource,
+ @PageResult.ResultType int type, @Nullable Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ super(dataSource, type, mainThreadExecutor, receiver);
+ mDataSource = dataSource;
+ }
+
+ /**
+ * Called to pass loaded data from a DataSource.
+ * <p>
+ * Call this method from your PageKeyedDataSource's
+ * {@link #loadBefore(LoadParams, LoadCallback)} and
+ * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
+ * <p>
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ * <p>
+ * Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've
+ * loaded a page in {@link #loadBefore(LoadParams, LoadCallback)}, pass the key for the
+ * previous page, or {@code null} if the loaded page is the first. If in
+ * {@link #loadAfter(LoadParams, LoadCallback)}, pass the key for the next page, or
+ * {@code null} if the loaded page is the last.
+ *
+ * @param data List of items loaded from the PageKeyedDataSource.
+ * @param adjacentPageKey Key for subsequent page load (previous page in {@link #loadBefore}
+ * / next page in {@link #loadAfter}), or {@code null} if there are
+ * no more pages to load in the current load direction.
+ */
+ public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
+ if (mResultType == PageResult.APPEND) {
+ mDataSource.setNextKey(adjacentPageKey);
+ } else {
+ mDataSource.setPreviousKey(adjacentPageKey);
+ }
+ dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+ }
+ }
+
+ @Nullable
+ @Override
+ final Key getKey(int position, Value item) {
+ // don't attempt to persist keys, since we currently don't pass them to initial load
+ return null;
+ }
+
+ @Override
+ final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
+ boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ LoadInitialCallback<Key, Value> callback =
+ new LoadInitialCallback<>(this, enablePlaceholders, receiver);
+ loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
+
+ // If initialLoad's callback is not called within the body, we force any following calls
+ // to post to the UI thread. This constructor may be run on a background thread, but
+ // after constructor, mutation must happen on UI thread.
+ callback.setPostExecutor(mainThreadExecutor);
+ }
+
+
+ @Override
+ final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
+ int pageSize, @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ @Nullable Key key = getNextKey();
+ if (key != null) {
+ loadAfter(new LoadParams<>(key, pageSize),
+ new LoadCallback<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
+ }
+ }
+
+ @Override
+ final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
+ int pageSize, @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ @Nullable Key key = getPreviousKey();
+ if (key != null) {
+ loadBefore(new LoadParams<>(key, pageSize),
+ new LoadCallback<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
+ }
+ }
+
+ /**
+ * Load initial data.
+ * <p>
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
+ * the callback via the three-parameter
+ * {@link LoadInitialCallback#onResult(List, int, int, Object, Object)}. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
+ * <p>
+ * {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement, so it may be may be
+ * altered or ignored.
+ *
+ * @param params Parameters for initial load, including requested load size.
+ * @param callback Callback that receives initial load data.
+ */
+ public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
+ @NonNull LoadInitialCallback<Key, Value> callback);
+
+ /**
+ * Prepend page with the key specified by {@link LoadParams#key LoadParams.key}.
+ * <p>
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+ * <p>
+ * Data may be passed synchronously during the load method, or deferred and called at a
+ * later time. Further loads going down will be blocked until the callback is called.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @param callback Callback that receives loaded data.
+ */
+ public abstract void loadBefore(@NonNull LoadParams<Key> params,
+ @NonNull LoadCallback<Key, Value> callback);
+
+ /**
+ * Append page with the key specified by {@link LoadParams#key LoadParams.key}.
+ * <p>
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+ * <p>
+ * Data may be passed synchronously during the load method, or deferred and called at a
+ * later time. Further loads going down will be blocked until the callback is called.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @param callback Callback that receives loaded data.
+ */
+ public abstract void loadAfter(@NonNull LoadParams<Key> params,
+ @NonNull LoadCallback<Key, Value> callback);
+}
diff --git a/paging/common/src/main/java/android/arch/paging/PagedList.java b/paging/common/src/main/java/android/arch/paging/PagedList.java
index a0ba388..c6de5c5 100644
--- a/paging/common/src/main/java/android/arch/paging/PagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/PagedList.java
@@ -578,7 +578,8 @@
* If data is supplied by a {@link PositionalDataSource}, the item returned from
* <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>.
* <p>
- * If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0.
+ * If the DataSource is a {@link ItemKeyedDataSource} or {@link PageKeyedDataSource}, it
+ * doesn't use positions, returns 0.
*/
public int getPositionOffset() {
return mStorage.getPositionOffset();
@@ -858,12 +859,14 @@
}
/**
- * Defines how many items to load when first load occurs, if you are using a
- * {@link KeyedDataSource}.
+ * Defines how many items to load when first load occurs.
* <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>
+ * When using a {@link PositionalDataSource}, the initial load size will be coerced to
+ * an integer multiple of pageSize, to enable efficient tiling.
+ * <p>
* If not set, defaults to three times page size.
*
* @param initialLoadSizeHint Number of items to load while initializing the PagedList.
@@ -875,7 +878,6 @@
return this;
}
-
/**
* Creates a {@link Config} with the given parameters.
*
@@ -906,13 +908,32 @@
/**
* Signals when a PagedList has reached the end of available data.
* <p>
- * This can be used to implement paging from the network into a local database - when the
- * database has no more data to present, a BoundaryCallback can be used to fetch more data.
+ * When local storage is a cache of network data, it's common to set up a streaming pipeline:
+ * Network data is paged into the database, database is paged into UI. Paging from the database
+ * to UI can be done with a {@code LiveData<PagedList>}, but it's still necessary to know when
+ * to trigger network loads.
* <p>
- * If an instance is shared across multiple PagedLists (e.g. when passed to
+ * BoundaryCallback does this signaling - when a DataSource runs out of data at the end of
+ * the list, {@link #onItemAtEndLoaded(Object)} is called, and you can start an async network
+ * load that will write the result directly to the database. Because the database is being
+ * observed, the UI bound to the {@code LiveData<PagedList>} will update automatically to
+ * account for the new items.
+ * <p>
+ * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
* {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple
* times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should
* avoid triggering it again while the load is ongoing.
+ * <p>
+ * BoundaryCallback only passes the item at front or end of the list. Number of items is not
+ * passed, since it may not be fully computed by the DataSource if placeholders are not
+ * supplied. Keys are not known because the BoundaryCallback is independent of the
+ * DataSource-specific keys, which may be different for local vs remote storage.
+ * <p>
+ * The database + network Repository in the
+ * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
+ * shows how to implement a network BoundaryCallback using
+ * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
+ * handling swipe-to-refresh, network errors, and retry.
*
* @param <T> Type loaded by the PagedList.
*/
diff --git a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
index c8345d4..4b9f1c0 100644
--- a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
@@ -25,15 +25,19 @@
import java.util.concurrent.Executor;
/**
- * Position-based data loader for a fixed-size, countable data set, supporting loads at arbitrary
- * positions.
+ * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
+ * arbitrary page positions.
* <p>
- * Extend PositionalDataSource if you can support counting your data set, and loading based on
- * position information.
+ * Extend PositionalDataSource if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. If your data source can't support loading arbitrary
+ * requested page sizes (e.g. when network page size constraints are only known at runtime), use
+ * either {@link PageKeyedDataSource} or {@link ItemKeyedDataSource} instead.
* <p>
* Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled}
- * PositionalDataSource requires counting the size of the dataset. This allows pages to be tiled in
+ * PositionalDataSource requires counting the size of the data set. This allows pages to be tiled in
* at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}.
+ * If placeholders are disabled, initialize with the two parameter
+ * {@link LoadInitialCallback#onResult(List, int)}.
* <p>
* Room can generate a Factory of PositionalDataSources for you:
* <pre>
@@ -134,7 +138,7 @@
LoadInitialCallback(@NonNull PositionalDataSource dataSource, boolean countingEnabled,
int pageSize, PageResult.Receiver<T> receiver) {
- super(PageResult.INIT, dataSource, null, receiver);
+ super(dataSource, PageResult.INIT, null, receiver);
mCountingEnabled = countingEnabled;
mPageSize = pageSize;
if (mPageSize < 1) {
@@ -148,7 +152,9 @@
* Call this method from your DataSource's {@code loadInitial} function to return data,
* and inform how many placeholders should be shown before and after. If counting is cheap
* to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
+ * recommended to pass the total size to the totalCount parameter. If placeholders are not
+ * requested (when {@link LoadInitialParams#placeholdersEnabled} is false), you can instead
+ * call {@link #onResult(List, int)}.
*
* @param data List of items loaded from the DataSource. If this is empty, the DataSource
* is treated as empty, and no further loads will occur.
@@ -179,11 +185,14 @@
}
/**
- * Called to pass initial load state from a DataSource without supporting placeholders.
+ * Called to pass initial load state from a DataSource without total count,
+ * when placeholders aren't requested.
+ * <p class="note"><strong>Note:</strong> This method can only be called when placeholders
+ * are disabled ({@link LoadInitialParams#placeholdersEnabled} is false).
* <p>
* Call this method from your DataSource's {@code loadInitial} function to return data,
- * if position is known but total size is not. If counting is not expensive, consider
- * calling the three parameter variant: {@link #onResult(List, int, int)}.
+ * if position is known but total size is not. If placeholders are requested, call the three
+ * parameter variant: {@link #onResult(List, int, int)}.
*
* @param data List of items loaded from the DataSource. If this is empty, the DataSource
* is treated as empty, and no further loads will occur.
@@ -191,10 +200,21 @@
* items before the items in data that can be provided by this DataSource,
* pass {@code N}.
*/
- void onResult(@NonNull List<T> data, int position) {
- // not counting, don't need to check mAcceptCount
- dispatchResultToReceiver(new PageResult<>(
- data, 0, 0, position));
+ @SuppressWarnings("WeakerAccess")
+ public void onResult(@NonNull List<T> data, int position) {
+ if (position < 0) {
+ throw new IllegalArgumentException("Position must be non-negative");
+ }
+ if (data.isEmpty() && position != 0) {
+ throw new IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set.");
+ }
+ if (mCountingEnabled) {
+ throw new IllegalStateException("Placeholders requested, but totalCount not"
+ + " provided. Please call the three-parameter onResult method, or disable"
+ + " placeholders in the PagedList.Config");
+ }
+ dispatchResultToReceiver(new PageResult<>(data, position));
}
}
@@ -214,7 +234,7 @@
private final int mPositionOffset;
LoadRangeCallback(@NonNull PositionalDataSource dataSource, int positionOffset,
Executor mainThreadExecutor, PageResult.Receiver<T> receiver) {
- super(PageResult.TILE, dataSource, mainThreadExecutor, receiver);
+ super(dataSource, PageResult.TILE, mainThreadExecutor, receiver);
mPositionOffset = positionOffset;
}
@@ -237,7 +257,7 @@
new LoadInitialCallback<>(this, acceptCount, pageSize, receiver);
LoadInitialParams params = new LoadInitialParams(
- requestedStartPosition, requestedLoadSize, pageSize, true);
+ requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
loadInitial(params, callback);
// If initialLoad's callback is not called within the body, we force any following calls
diff --git a/paging/common/src/test/java/android/arch/paging/Executors.kt b/paging/common/src/test/java/android/arch/paging/Executors.kt
index b472eed..e1c45a7 100644
--- a/paging/common/src/test/java/android/arch/paging/Executors.kt
+++ b/paging/common/src/test/java/android/arch/paging/Executors.kt
@@ -23,8 +23,8 @@
class TestExecutor : Executor {
private val mTasks = LinkedList<Runnable>()
- override fun execute(command: Runnable) {
- mTasks.add(command)
+ override fun execute(runnable: Runnable) {
+ mTasks.add(runnable)
}
internal fun executeAll(): Boolean {
@@ -40,7 +40,7 @@
}
class FailExecutor(val string: String = "Executor expected to be unused") : Executor {
- override fun execute(p0: Runnable?) {
+ override fun execute(runnable: Runnable?) {
fail(string)
}
}
diff --git a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
similarity index 92%
rename from paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
rename to paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
index b36b433..f687b88 100644
--- a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
@@ -30,7 +30,7 @@
import org.mockito.Mockito.verifyNoMoreInteractions
@RunWith(JUnit4::class)
-class KeyedDataSourceTest {
+class ItemKeyedDataSourceTest {
// ----- STANDARD -----
@@ -185,11 +185,11 @@
fun loadBefore() {
val dataSource = ItemDataSource()
@Suppress("UNCHECKED_CAST")
- val callback = mock(KeyedDataSource.LoadCallback::class.java)
- as KeyedDataSource.LoadCallback<Item>
+ val callback = mock(ItemKeyedDataSource.LoadCallback::class.java)
+ as ItemKeyedDataSource.LoadCallback<Item>
dataSource.loadBefore(
- KeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback)
+ ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback)
@Suppress("UNCHECKED_CAST")
val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
@@ -208,7 +208,7 @@
internal class ItemDataSource(private val counted: Boolean = true,
private val items: List<Item> = ITEMS_BY_NAME_ID)
- : KeyedDataSource<Key, Item>() {
+ : ItemKeyedDataSource<Key, Item>() {
override fun loadInitial(
params: LoadInitialParams<Key>,
@@ -256,9 +256,9 @@
}
}
- private fun performInitialLoad(
- callbackInvoker: (callback: KeyedDataSource.LoadInitialCallback<String>) -> Unit) {
- val dataSource = object : KeyedDataSource<String, String>() {
+ private fun performLoadInitial(
+ callbackInvoker: (callback: ItemKeyedDataSource.LoadInitialCallback<String>) -> Unit) {
+ val dataSource = object : ItemKeyedDataSource<String, String>() {
override fun getKey(item: String): String {
return ""
}
@@ -287,38 +287,38 @@
}
@Test
- fun initialLoadCallbackSuccess() = performInitialLoad {
+ fun loadInitialCallbackSuccess() = performLoadInitial {
// LoadInitialCallback correct usage
it.onResult(listOf("a", "b"), 0, 2)
}
@Test
- fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
+ fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial {
// Keyed LoadInitialCallback *can* accept result that's not a multiple of page size
val elevenLetterList = List(11) { "" + 'a' + it }
it.onResult(elevenLetterList, 0, 12)
}
@Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackListTooBig() = performInitialLoad {
+ fun loadInitialCallbackListTooBig() = performLoadInitial {
// LoadInitialCallback can't accept pos + list > totalCount
it.onResult(listOf("a", "b", "c"), 0, 2)
}
@Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
+ fun loadInitialCallbackPositionTooLarge() = performLoadInitial {
// LoadInitialCallback can't accept pos + list > totalCount
it.onResult(listOf("a", "b"), 1, 2)
}
@Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackPositionNegative() = performInitialLoad {
+ fun loadInitialCallbackPositionNegative() = performLoadInitial {
// LoadInitialCallback can't accept negative position
it.onResult(listOf("a", "b", "c"), -1, 2)
}
@Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
+ fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
// LoadInitialCallback can't accept empty result unless data set is empty
it.onResult(emptyList(), 0, 2)
}
diff --git a/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt
new file mode 100644
index 0000000..bcc2535
--- /dev/null
+++ b/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PageKeyedDataSourceTest {
+ private val mMainThread = TestExecutor()
+ private val mBackgroundThread = TestExecutor()
+
+ internal data class Item(val name: String)
+
+ internal data class Page(val prev: String?, val data: List<Item>, val next: String?)
+
+ internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP)
+ : PageKeyedDataSource<String, Item>() {
+
+ private fun getPage(key: String): Page = data[key]!!
+
+ override fun loadInitial(
+ params: LoadInitialParams<String>,
+ callback: LoadInitialCallback<String, Item>) {
+ val page = getPage(INIT_KEY)
+ callback.onResult(page.data, page.prev, page.next)
+ }
+
+ override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
+ val page = getPage(params.key)
+ callback.onResult(page.data, page.prev)
+ }
+
+ override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
+ val page = getPage(params.key)
+ callback.onResult(page.data, page.next)
+ }
+ }
+
+ @Test
+ fun loadFullVerify() {
+ // validate paging entire ItemDataSource results in full, correctly ordered data
+ val pagedList = ContiguousPagedList<String, Item>(ItemDataSource(),
+ mMainThread, mBackgroundThread,
+ null, PagedList.Config.Builder().setPageSize(100).build(), null)
+
+ // validate initial load
+ assertEquals(PAGE_MAP[INIT_KEY]!!.data, pagedList)
+
+ // flush the remaining loads
+ for (i in 0..PAGE_MAP.keys.size) {
+ pagedList.loadAround(0)
+ pagedList.loadAround(pagedList.size - 1)
+ drain()
+ }
+
+ // validate full load
+ assertEquals(ITEM_LIST, pagedList)
+ }
+
+ private fun performLoadInitial(callbackInvoker:
+ (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit) {
+ val dataSource = object : PageKeyedDataSource<String, String>() {
+ override fun loadInitial(
+ params: LoadInitialParams<String>,
+ callback: LoadInitialCallback<String, String>) {
+ callbackInvoker(callback)
+ }
+
+ override fun loadBefore(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>) {
+ fail("loadBefore not expected")
+ }
+
+ override fun loadAfter(
+ params: LoadParams<String>,
+ callback: LoadCallback<String, String>) {
+ fail("loadAfter not expected")
+ }
+ }
+
+ ContiguousPagedList<String, String>(
+ dataSource, FailExecutor(), FailExecutor(), null,
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .build(),
+ "")
+ }
+
+ @Test
+ fun loadInitialCallbackSuccess() = performLoadInitial {
+ // LoadInitialCallback correct usage
+ it.onResult(listOf("a", "b"), 0, 2, null, null)
+ }
+
+ @Test
+ fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial {
+ // Keyed LoadInitialCallback *can* accept result that's not a multiple of page size
+ val elevenLetterList = List(11) { "" + 'a' + it }
+ it.onResult(elevenLetterList, 0, 12, null, null)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun loadInitialCallbackListTooBig() = performLoadInitial {
+ // LoadInitialCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b", "c"), 0, 2, null, null)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun loadInitialCallbackPositionTooLarge() = performLoadInitial {
+ // LoadInitialCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b"), 1, 2, null, null)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun loadInitialCallbackPositionNegative() = performLoadInitial {
+ // LoadInitialCallback can't accept negative position
+ it.onResult(listOf("a", "b", "c"), -1, 2, null, null)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
+ // LoadInitialCallback can't accept empty result unless data set is empty
+ it.onResult(emptyList(), 0, 2, null, null)
+ }
+
+ companion object {
+ // first load is 2nd page to ensure we test prepend as well as append behavior
+ private val INIT_KEY: String = "key 2"
+ private val PAGE_MAP: Map<String, Page>
+ private val ITEM_LIST: List<Item>
+
+ init {
+ val map = HashMap<String, Page>()
+ val list = ArrayList<Item>()
+ val pageCount = 5
+ for (i in 1..pageCount) {
+ val data = List(4) { Item("name $i $it") }
+ list.addAll(data)
+
+ val key = "key $i"
+ val prev = if (i > 1) ("key " + (i - 1)) else null
+ val next = if (i < pageCount) ("key " + (i + 1)) else null
+ map.put(key, Page(prev, data, next))
+ }
+ PAGE_MAP = map
+ ITEM_LIST = list
+ }
+ }
+
+ private fun drain() {
+ var executed: Boolean
+ do {
+ executed = mBackgroundThread.executeAll()
+ executed = mMainThread.executeAll() || executed
+ } while (executed)
+ }
+}
diff --git a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
index da4b265..280a64d 100644
--- a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
@@ -80,6 +80,7 @@
}
private fun performInitialLoad(
+ enablePlaceholders: Boolean = true,
callbackInvoker: (callback: PositionalDataSource.LoadInitialCallback<String>) -> Unit) {
val dataSource = object : PositionalDataSource<String>() {
override fun loadInitial(
@@ -93,12 +94,16 @@
}
}
- TiledPagedList(
- dataSource, FailExecutor(), FailExecutor(), null,
- PagedList.Config.Builder()
- .setPageSize(10)
- .build(),
- 0)
+ val config = PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(enablePlaceholders)
+ .build()
+ if (enablePlaceholders) {
+ TiledPagedList(dataSource, FailExecutor(), FailExecutor(), null, config, 0)
+ } else {
+ ContiguousPagedList(dataSource.wrapAsContiguousWithoutPlaceholders(),
+ FailExecutor(), FailExecutor(), null, config, null)
+ }
}
@Test
@@ -137,4 +142,28 @@
// LoadInitialCallback can't accept empty result unless data set is empty
it.onResult(emptyList(), 0, 2)
}
+
+ @Test(expected = IllegalStateException::class)
+ fun initialLoadCallbackRequireTotalCount() = performInitialLoad(enablePlaceholders = true) {
+ // LoadInitialCallback requires 3 args when placeholders enabled
+ it.onResult(listOf("a", "b"), 0)
+ }
+
+ @Test
+ fun initialLoadCallbackSuccessTwoArg() = performInitialLoad(enablePlaceholders = false) {
+ // LoadInitialCallback correct 2 arg usage
+ it.onResult(listOf("a", "b"), 0)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackPosNegativeTwoArg() = performInitialLoad(enablePlaceholders = false) {
+ // LoadInitialCallback can't accept negative position
+ it.onResult(listOf("a", "b"), -1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackEmptyWithOffset() = performInitialLoad(enablePlaceholders = false) {
+ // LoadInitialCallback can't accept empty result unless pos is 0
+ it.onResult(emptyList(), 1)
+ }
}
diff --git a/paging/runtime/build.gradle b/paging/runtime/build.gradle
index 6bda233..1a2bd2d 100644
--- a/paging/runtime/build.gradle
+++ b/paging/runtime/build.gradle
@@ -41,8 +41,8 @@
androidTestImplementation(JUNIT)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(KOTLIN_STDLIB)
}
diff --git a/percent/build.gradle b/percent/build.gradle
index da5ccc9..7d5a651 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -9,8 +9,8 @@
dependencies {
api(project(":support-compat"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
}
android {
diff --git a/persistence/db/api/1.0.0.txt b/persistence/db/api/1.0.0.txt
index 23d3bb5..f96f17a 100644
--- a/persistence/db/api/1.0.0.txt
+++ b/persistence/db/api/1.0.0.txt
@@ -85,7 +85,7 @@
method public abstract android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
}
- public abstract interface SupportSQLiteProgram implements java.lang.AutoCloseable {
+ public abstract interface SupportSQLiteProgram implements java.io.Closeable {
method public abstract void bindBlob(int, byte[]);
method public abstract void bindDouble(int, double);
method public abstract void bindLong(int, long);
diff --git a/recyclerview-selection/build.gradle b/recyclerview-selection/build.gradle
index 9234fd8..06dc730 100644
--- a/recyclerview-selection/build.gradle
+++ b/recyclerview-selection/build.gradle
@@ -25,8 +25,8 @@
api project(':support-annotations')
api project(':support-compat')
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(JUNIT)
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionStorage.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionStorage.java
index 81db30f..454a76b 100644
--- a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionStorage.java
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionStorage.java
@@ -139,7 +139,7 @@
}
}
- private @Nullable Selection<String> readStringSelection(@Nullable Bundle state) {
+ private @Nullable Selection<String> readStringSelection(Bundle state) {
@Nullable ArrayList<String> stored =
state.getStringArrayList(EXTRA_SAVED_SELECTION_ENTRIES);
if (stored == null) {
@@ -151,7 +151,7 @@
return selection;
}
- private @Nullable Selection<Long> readLongSelection(@Nullable Bundle state) {
+ private @Nullable Selection<Long> readLongSelection(Bundle state) {
@Nullable long[] stored = state.getLongArray(EXTRA_SAVED_SELECTION_ENTRIES);
if (stored == null) {
return null;
diff --git a/room/common/src/main/java/android/arch/persistence/room/Database.java b/room/common/src/main/java/android/arch/persistence/room/Database.java
index f12d1b9..14e722f 100644
--- a/room/common/src/main/java/android/arch/persistence/room/Database.java
+++ b/room/common/src/main/java/android/arch/persistence/room/Database.java
@@ -34,7 +34,7 @@
* <pre>
* // User and Book are classes annotated with {@literal @}Entity.
* {@literal @}Database(version = 1, entities = {User.class, Book.class})
- * abstract class AppDatabase extends RoomDatabase() {
+ * abstract class AppDatabase extends RoomDatabase {
* // BookDao is a class annotated with {@literal @}Dao.
* abstract public BookDao bookDao();
* // UserDao is a class annotated with {@literal @}Dao.
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 6458e2a..d4d4893 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -84,8 +84,8 @@
androidTestImplementation(project(":room:rxjava2"))
androidTestImplementation(project(":arch:core-testing"))
androidTestImplementation(RX_JAVA)
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it's own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it's own MockMaker
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
index b259643..6278bc2 100644
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
+++ b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
@@ -16,7 +16,7 @@
package android.arch.persistence.room.integration.testapp.database;
import android.arch.paging.DataSource;
-import android.arch.paging.KeyedDataSource;
+import android.arch.paging.ItemKeyedDataSource;
import android.arch.persistence.room.InvalidationTracker;
import android.support.annotation.NonNull;
@@ -27,7 +27,7 @@
/**
* Sample Room keyed data source.
*/
-public class LastNameAscCustomerDataSource extends KeyedDataSource<String, Customer> {
+public class LastNameAscCustomerDataSource extends ItemKeyedDataSource<String, Customer> {
private final CustomerDao mCustomerDao;
@SuppressWarnings("FieldCanBeLocal")
private final InvalidationTracker.Observer mObserver;
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index ecbd0b3..8a91ac7 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -46,11 +46,10 @@
testImplementation(project(":arch:core-testing"))
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
- testImplementation libs.support.annotations
androidTestImplementation(JUNIT)
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
}
// Used by testCompile in room-compiler
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
index 8c94024..2a108f9 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
@@ -90,7 +90,7 @@
* @param configuration The database configuration.
*/
@CallSuper
- public void init(DatabaseConfiguration configuration) {
+ public void init(@NonNull DatabaseConfiguration configuration) {
mOpenHelper = createOpenHelper(configuration);
mCallbacks = configuration.callbacks;
mAllowMainThreadQueries = configuration.allowMainThreadQueries;
@@ -101,6 +101,7 @@
*
* @return The SQLite open helper used by this database.
*/
+ @NonNull
public SupportSQLiteOpenHelper getOpenHelper() {
return mOpenHelper;
}
@@ -113,6 +114,7 @@
* @param config The configuration of the Room database.
* @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
*/
+ @NonNull
protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
/**
@@ -122,6 +124,7 @@
*
* @return Creates a new InvalidationTracker.
*/
+ @NonNull
protected abstract InvalidationTracker createInvalidationTracker();
/**
@@ -199,7 +202,7 @@
* @param sql The query to compile.
* @return The compiled query.
*/
- public SupportSQLiteStatement compileStatement(String sql) {
+ public SupportSQLiteStatement compileStatement(@NonNull String sql) {
assertNotMainThread();
return mOpenHelper.getWritableDatabase().compileStatement(sql);
}
@@ -238,7 +241,7 @@
*
* @param body The piece of code to execute.
*/
- public void runInTransaction(Runnable body) {
+ public void runInTransaction(@NonNull Runnable body) {
beginTransaction();
try {
body.run();
@@ -256,7 +259,7 @@
* @param <V> The type of the return value.
* @return The value returned from the {@link Callable}.
*/
- public <V> V runInTransaction(Callable<V> body) {
+ public <V> V runInTransaction(@NonNull Callable<V> body) {
beginTransaction();
try {
V result = body.call();
@@ -278,7 +281,7 @@
*
* @param db The database instance.
*/
- protected void internalInitInvalidationTracker(SupportSQLiteDatabase db) {
+ protected void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase db) {
mInvalidationTracker.internalInit(db);
}
@@ -290,6 +293,7 @@
*
* @return The invalidation tracker for the database.
*/
+ @NonNull
public InvalidationTracker getInvalidationTracker() {
return mInvalidationTracker;
}
@@ -365,7 +369,7 @@
* @return this
*/
@NonNull
- public Builder<T> addMigrations(Migration... migrations) {
+ public Builder<T> addMigrations(@NonNull Migration... migrations) {
mMigrationContainer.addMigrations(migrations);
return this;
}
@@ -471,7 +475,7 @@
*
* @param migrations List of available migrations.
*/
- public void addMigrations(Migration... migrations) {
+ public void addMigrations(@NonNull Migration... migrations) {
for (Migration migration : migrations) {
addMigration(migration);
}
diff --git a/samples/SupportCarDemos/OWNERS b/samples/SupportCarDemos/OWNERS
new file mode 100644
index 0000000..42ef8d4
--- /dev/null
+++ b/samples/SupportCarDemos/OWNERS
@@ -0,0 +1,2 @@
+ajchen@google.com
+yaoyx@google.com
\ No newline at end of file
diff --git a/samples/SupportCarDemos/src/main/AndroidManifest.xml b/samples/SupportCarDemos/src/main/AndroidManifest.xml
index fd46652..c32fcf4 100644
--- a/samples/SupportCarDemos/src/main/AndroidManifest.xml
+++ b/samples/SupportCarDemos/src/main/AndroidManifest.xml
@@ -15,12 +15,12 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.support.car">
+ package="com.example.androidx.car">
<application android:label="@string/activity_sample_code"
android:supportsRtl="true"
android:icon="@drawable/app_sample_code"
- android:theme="@style/android:Theme.Holo.Light">
+ android:theme="@style/CarTheme">
<activity android:name=".SupportCarDemoActivity">
<intent-filter>
@@ -39,6 +39,17 @@
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".SupportCarDemoActivity" />
</activity>
+
+ <activity android:name=".ListItemActivity"
+ android:label="ListItem"
+ android:parentActivityName=".SupportCarDemoActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SAMPLE_CODE" />
+ </intent-filter>
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value=".SupportCarDemoActivity" />
+ </activity>
</application>
</manifest>
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java
new file mode 100644
index 0000000..6aa5ba6
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.car;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.car.widget.ListItem;
+import androidx.car.widget.ListItemAdapter;
+import androidx.car.widget.ListItemProvider;
+import androidx.car.widget.PagedListView;
+
+/**
+ * Demo activity for {@link ListItem}.
+ */
+public class ListItemActivity extends Activity {
+
+ private static int pixelToDip(Context context, int pixels) {
+ return (int) (pixels / context.getResources().getDisplayMetrics().density);
+ }
+
+ PagedListView mPagedListView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_paged_list_view);
+
+ mPagedListView = findViewById(R.id.paged_list_view);
+
+ ListItemAdapter adapter = new ListItemAdapter(this,
+ new SampleProvider(this));
+ mPagedListView.setAdapter(adapter);
+ mPagedListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+ }
+
+ private static class SampleProvider extends ListItemProvider {
+ private Context mContext;
+ private List<ListItem> mItems;
+
+ private View.OnClickListener mGetParentHeight = (v) -> {
+ int parentHeight = ((FrameLayout) v.getParent().getParent().getParent()).getHeight();
+ Toast.makeText(v.getContext(),
+ "card height is " + pixelToDip(mContext, parentHeight) + " dp",
+ Toast.LENGTH_SHORT).show();
+ };
+
+ private ListItemProvider.ListProvider mListProvider;
+
+ SampleProvider(Context context) {
+ mContext = context;
+ mItems = new ArrayList<>();
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+ .withTitle("single line with full icon and one action")
+ .withAction("card height", true, mGetParentHeight)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withTitle("primary action set by drawable")
+ .withPrimaryActionIcon(mContext.getDrawable(R.drawable.pressed_icon), true)
+ .withViewBinder(vh -> vh.getPrimaryIcon().setClickable(true))
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+ .withTitle("single line with small icon and clickable end icon")
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+ mGetParentHeight)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionEmptyIcon()
+ .withTitle("single line with empty icon and end icon no divider")
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, false)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withTitle("title is single line and ellipsizes. "
+ + mContext.getString(R.string.long_text))
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionNoIcon()
+ .withTitle("single line with two actions and no divider")
+ .withActions("action 1", false,
+ (v) -> Toast.makeText(
+ v.getContext(), "action 1", Toast.LENGTH_SHORT).show(),
+ "action 2", false,
+ (v) -> Toast.makeText(
+ v.getContext(), "action 2", Toast.LENGTH_SHORT).show())
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionNoIcon()
+ .withTitle("single line with two actions and action 2 divider")
+ .withActions("action 1", false,
+ (v) -> Toast.makeText(
+ v.getContext(), "action 1", Toast.LENGTH_SHORT).show(),
+ "action 2", true,
+ (v) -> Toast.makeText(
+ v.getContext(), "action 2", Toast.LENGTH_SHORT).show())
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionNoIcon()
+ .withTitle("single line with divider between actions. "
+ + mContext.getString(R.string.long_text))
+ .withActions("action 1", true,
+ (v) -> Toast.makeText(
+ v.getContext(), "action 1", Toast.LENGTH_SHORT).show(),
+ "action 2", false,
+ (v) -> Toast.makeText(
+ v.getContext(), "action 2", Toast.LENGTH_SHORT).show())
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+ .withTitle("double line with full icon and no end icon divider")
+ .withBody("one line text")
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, false,
+ mGetParentHeight)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+ .withTitle("double line with small icon and one action")
+ .withBody("one line text")
+ .withAction("card height", true, mGetParentHeight)
+ .build());
+
+ String tenChars = "Ten Chars.";
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+ .withTitle("Card with small icon and text longer than limit")
+ .withBody("some chars")
+ .withBody(TextUtils.join("", Collections.nCopies(20, tenChars)))
+ .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+ mGetParentHeight)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionEmptyIcon()
+ .withTitle("double line with empty primary icon."
+ + mContext.getString(R.string.long_text))
+ .withBody("one line text as primary", true)
+ .withActions("screen size", false, (v) -> {
+ Context c = v.getContext();
+ Point size = new Point();
+ c.getSystemService(WindowManager.class).getDefaultDisplay().getSize(size);
+
+ Toast.makeText(v.getContext(),
+ String.format("%s x %s dp", pixelToDip(c, size.x),
+ pixelToDip(c, size.y)), Toast.LENGTH_SHORT).show();
+ }, "card height", true, mGetParentHeight)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withTitle("double line with no primary action and one divider")
+ .withBody("one line text as primary", true)
+ .withActions("screen size", false, (v) -> {
+ Context c = v.getContext();
+ Point size = new Point();
+ c.getSystemService(WindowManager.class).getDefaultDisplay().getSize(size);
+
+ Toast.makeText(v.getContext(),
+ String.format("%s x %s dp", pixelToDip(c, size.x),
+ pixelToDip(c, size.y)), Toast.LENGTH_SHORT).show();
+ }, "card height", true, mGetParentHeight)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+ .withBody("Only body - no title is set")
+ .withAction("card height", true, mGetParentHeight)
+ .build());
+
+ mItems.add(new ListItem.Builder(mContext)
+ .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+ .withBody("Only body - no title. " + mContext.getString(R.string.long_text))
+ .build());
+
+ mListProvider = new ListItemProvider.ListProvider(mItems);
+ }
+
+ @Override
+ public ListItem get(int position) {
+ return mListProvider.get(position);
+ }
+
+ @Override
+ public int size() {
+ return mListProvider.size();
+ }
+ }
+}
diff --git a/samples/SupportCarDemos/src/main/java/com/example/android/support/car/PagedListViewActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
similarity index 96%
rename from samples/SupportCarDemos/src/main/java/com/example/android/support/car/PagedListViewActivity.java
rename to samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
index 0fb643f..2aa4e0c 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/android/support/car/PagedListViewActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.example.android.support.car;
+package com.example.androidx.car;
import android.app.Activity;
import android.os.Bundle;
-import android.support.car.widget.PagedListView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,6 +27,8 @@
import java.util.ArrayList;
import java.util.List;
+import androidx.car.widget.PagedListView;
+
/**
* Demo activity for PagedListView.
*/
diff --git a/samples/SupportCarDemos/src/main/java/com/example/android/support/car/SupportCarDemoActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SupportCarDemoActivity.java
similarity index 98%
rename from samples/SupportCarDemos/src/main/java/com/example/android/support/car/SupportCarDemoActivity.java
rename to samples/SupportCarDemos/src/main/java/com/example/androidx/car/SupportCarDemoActivity.java
index 2b4f894..049c5c6 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/android/support/car/SupportCarDemoActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SupportCarDemoActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.example.android.support.car;
+package com.example.androidx.car;
import android.app.ListActivity;
import android.content.Intent;
diff --git a/samples/SupportCarDemos/src/main/res/drawable/pressed_icon.xml b/samples/SupportCarDemos/src/main/res/drawable/pressed_icon.xml
new file mode 100644
index 0000000..32a497f
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/drawable/pressed_icon.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:drawable="@android:drawable/btn_star_big_on"/>
+ <item android:drawable="@android:drawable/btn_star_big_off"/>
+</selector>
diff --git a/samples/SupportCarDemos/src/main/res/layout/activity_paged_list_view.xml b/samples/SupportCarDemos/src/main/res/layout/activity_paged_list_view.xml
index aa8d527..5b9a1a5 100644
--- a/samples/SupportCarDemos/src/main/res/layout/activity_paged_list_view.xml
+++ b/samples/SupportCarDemos/src/main/res/layout/activity_paged_list_view.xml
@@ -19,7 +19,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <android.support.car.widget.PagedListView
+ <androidx.car.widget.PagedListView
android:id="@+id/paged_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/samples/SupportCarDemos/src/main/res/layout/paged_list_item.xml b/samples/SupportCarDemos/src/main/res/layout/paged_list_item.xml
index 387e2c9..26f9c5a 100644
--- a/samples/SupportCarDemos/src/main/res/layout/paged_list_item.xml
+++ b/samples/SupportCarDemos/src/main/res/layout/paged_list_item.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<android.support.car.widget.ColumnCardView
+<androidx.car.widget.ColumnCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="0dp"
@@ -27,4 +27,4 @@
android:singleLine="true"
android:ellipsize="end"
android:textAppearance="@style/CarBody1"/>
-</android.support.car.widget.ColumnCardView>
\ No newline at end of file
+</androidx.car.widget.ColumnCardView>
\ No newline at end of file
diff --git a/samples/SupportCarDemos/src/main/res/values/strings.xml b/samples/SupportCarDemos/src/main/res/values/strings.xml
index 0a49857..adffe89 100644
--- a/samples/SupportCarDemos/src/main/res/values/strings.xml
+++ b/samples/SupportCarDemos/src/main/res/values/strings.xml
@@ -16,5 +16,6 @@
-->
<resources>
<string name="activity_sample_code">Support Car Demos</string>
+ <string name="long_text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</string>
</resources>
diff --git a/samples/SupportCarDemos/src/main/res/values/themes.xml b/samples/SupportCarDemos/src/main/res/values/themes.xml
new file mode 100644
index 0000000..4b82ecd
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/values/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- The main theme for all activities within the Car Demo. -->
+ <style name="CarTheme" parent="android:Theme.Material.Light">
+ <item name="android:windowBackground">@color/car_grey_50</item>
+ <item name="android:colorAccent">@color/car_yellow_500</item>
+ <item name="android:colorPrimary">@color/car_highlight</item>
+ <item name="android:colorPrimaryDark">@color/car_grey_300</item>
+ <item name="android:buttonStyle">@style/CarButton</item>
+ <item name="android:borderlessButtonStyle">@style/CarButton.Borderless</item>
+ <item name="android:progressBarStyleHorizontal">
+ @style/CarProgressBar.Horizontal
+ </item>
+ <item name="android:windowLightStatusBar">true</item>
+ </style>
+</resources>
diff --git a/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java b/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java
index 6ed9885..d799352 100644
--- a/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java
+++ b/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java
@@ -26,8 +26,8 @@
import android.support.annotation.RestrictTo;
import android.text.TextUtils;
+import java.util.ArrayDeque;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Spliterators;
@@ -285,7 +285,7 @@
/**
*/
public static Stream<SliceItem> stream(SliceItem slice) {
- Queue<SliceItem> items = new LinkedList();
+ Queue<SliceItem> items = new ArrayDeque<>();
items.add(slice);
return getSliceItemStream(items);
}
@@ -293,7 +293,7 @@
/**
*/
public static Stream<SliceItem> stream(Slice slice) {
- Queue<SliceItem> items = new LinkedList();
+ Queue<SliceItem> items = new ArrayDeque<>();
items.addAll(slice.getItems());
return getSliceItemStream(items);
}
diff --git a/testutils/build.gradle b/testutils/build.gradle
index d99826d..074ab34 100644
--- a/testutils/build.gradle
+++ b/testutils/build.gradle
@@ -24,8 +24,8 @@
api(project(":support-fragment"))
api(project(":appcompat-v7"))
- implementation(TEST_RUNNER, libs.exclude_annotations)
- implementation(ESPRESSO_CORE, libs.exclude_annotations)
+ implementation(TEST_RUNNER)
+ implementation(ESPRESSO_CORE)
implementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
implementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
implementation(JUNIT)
diff --git a/transition/build.gradle b/transition/build.gradle
index 3e78cfe..dcf3a76 100644
--- a/transition/build.gradle
+++ b/transition/build.gradle
@@ -11,8 +11,8 @@
api(project(":support-compat"))
compileOnly project(':support-fragment')
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(project(":support-v4"))
diff --git a/tv-provider/build.gradle b/tv-provider/build.gradle
index eb5d297..7090108 100644
--- a/tv-provider/build.gradle
+++ b/tv-provider/build.gradle
@@ -10,7 +10,7 @@
api(project(":support-annotations"))
api(project(":support-compat"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
}
android {
diff --git a/v13/api/current.txt b/v13/api/current.txt
index 73871f6..1d9fdbf 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -5,44 +5,63 @@
method public static android.support.v13.view.DragAndDropPermissionsCompat requestDragAndDropPermissions(android.app.Activity, android.view.DragEvent);
}
- public class FragmentCompat {
- ctor public FragmentCompat();
- method public static void requestPermissions(android.app.Fragment, java.lang.String[], int);
+ public deprecated class FragmentCompat {
+ ctor public deprecated FragmentCompat();
+ method public static deprecated void requestPermissions(android.app.Fragment, java.lang.String[], int);
method public static deprecated void setMenuVisibility(android.app.Fragment, boolean);
- method public static void setPermissionCompatDelegate(android.support.v13.app.FragmentCompat.PermissionCompatDelegate);
- method public static void setUserVisibleHint(android.app.Fragment, boolean);
- method public static boolean shouldShowRequestPermissionRationale(android.app.Fragment, java.lang.String);
+ method public static deprecated void setPermissionCompatDelegate(android.support.v13.app.FragmentCompat.PermissionCompatDelegate);
+ method public static deprecated void setUserVisibleHint(android.app.Fragment, boolean);
+ method public static deprecated boolean shouldShowRequestPermissionRationale(android.app.Fragment, java.lang.String);
}
- public static abstract interface FragmentCompat.OnRequestPermissionsResultCallback {
- method public abstract void onRequestPermissionsResult(int, java.lang.String[], int[]);
+ public static abstract deprecated interface FragmentCompat.OnRequestPermissionsResultCallback {
+ method public abstract deprecated void onRequestPermissionsResult(int, java.lang.String[], int[]);
}
- public static abstract interface FragmentCompat.PermissionCompatDelegate {
- method public abstract boolean requestPermissions(android.app.Fragment, java.lang.String[], int);
+ public static abstract deprecated interface FragmentCompat.PermissionCompatDelegate {
+ method public abstract deprecated boolean requestPermissions(android.app.Fragment, java.lang.String[], int);
}
- public abstract class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
- ctor public FragmentPagerAdapter(android.app.FragmentManager);
- method public abstract android.app.Fragment getItem(int);
- method public long getItemId(int);
- method public boolean isViewFromObject(android.view.View, java.lang.Object);
+ public abstract deprecated class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
+ ctor public deprecated FragmentPagerAdapter(android.app.FragmentManager);
+ method public deprecated void destroyItem(android.view.ViewGroup, int, java.lang.Object);
+ method public deprecated void finishUpdate(android.view.ViewGroup);
+ method public abstract deprecated android.app.Fragment getItem(int);
+ method public deprecated long getItemId(int);
+ method public deprecated java.lang.Object instantiateItem(android.view.ViewGroup, int);
+ method public deprecated boolean isViewFromObject(android.view.View, java.lang.Object);
+ method public deprecated void restoreState(android.os.Parcelable, java.lang.ClassLoader);
+ method public deprecated android.os.Parcelable saveState();
+ method public deprecated void setPrimaryItem(android.view.ViewGroup, int, java.lang.Object);
+ method public deprecated void startUpdate(android.view.ViewGroup);
}
- public abstract class FragmentStatePagerAdapter extends android.support.v4.view.PagerAdapter {
- ctor public FragmentStatePagerAdapter(android.app.FragmentManager);
- method public abstract android.app.Fragment getItem(int);
- method public boolean isViewFromObject(android.view.View, java.lang.Object);
+ public abstract deprecated class FragmentStatePagerAdapter extends android.support.v4.view.PagerAdapter {
+ ctor public deprecated FragmentStatePagerAdapter(android.app.FragmentManager);
+ method public deprecated void destroyItem(android.view.ViewGroup, int, java.lang.Object);
+ method public deprecated void finishUpdate(android.view.ViewGroup);
+ method public abstract deprecated android.app.Fragment getItem(int);
+ method public deprecated java.lang.Object instantiateItem(android.view.ViewGroup, int);
+ method public deprecated boolean isViewFromObject(android.view.View, java.lang.Object);
+ method public deprecated void restoreState(android.os.Parcelable, java.lang.ClassLoader);
+ method public deprecated android.os.Parcelable saveState();
+ method public deprecated void setPrimaryItem(android.view.ViewGroup, int, java.lang.Object);
+ method public deprecated void startUpdate(android.view.ViewGroup);
}
- public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
- ctor public FragmentTabHost(android.content.Context);
- ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet);
- method public void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
- method public void onTabChanged(java.lang.String);
+ public deprecated class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+ ctor public deprecated FragmentTabHost(android.content.Context);
+ ctor public deprecated FragmentTabHost(android.content.Context, android.util.AttributeSet);
+ method public deprecated void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
+ method protected deprecated void onAttachedToWindow();
+ method protected deprecated void onDetachedFromWindow();
+ method protected deprecated void onRestoreInstanceState(android.os.Parcelable);
+ method protected deprecated android.os.Parcelable onSaveInstanceState();
+ method public deprecated void onTabChanged(java.lang.String);
+ method public deprecated void setOnTabChangedListener(android.widget.TabHost.OnTabChangeListener);
method public deprecated void setup();
- method public void setup(android.content.Context, android.app.FragmentManager);
- method public void setup(android.content.Context, android.app.FragmentManager, int);
+ method public deprecated void setup(android.content.Context, android.app.FragmentManager);
+ method public deprecated void setup(android.content.Context, android.app.FragmentManager, int);
}
}
diff --git a/v13/build.gradle b/v13/build.gradle
index 0f5c9b6..425a31f 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -10,8 +10,8 @@
api(project(":support-annotations"))
api(project(":support-v4"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
}
diff --git a/v13/java/android/support/v13/app/FragmentCompat.java b/v13/java/android/support/v13/app/FragmentCompat.java
index 31c2343..e8915fb 100644
--- a/v13/java/android/support/v13/app/FragmentCompat.java
+++ b/v13/java/android/support/v13/app/FragmentCompat.java
@@ -30,8 +30,19 @@
/**
* Helper for accessing features in {@link Fragment} in a backwards compatible fashion.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework fragment.
*/
+@Deprecated
public class FragmentCompat {
+
+ /**
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework fragment.
+ */
+ @Deprecated
+ public FragmentCompat() {
+ }
+
interface FragmentCompatImpl {
void setUserVisibleHint(Fragment f, boolean deferStart);
void requestPermissions(Fragment fragment, String[] permissions, int requestCode);
@@ -48,7 +59,11 @@
* to the compatibility methods in this class will first check whether the delegate can
* handle the method call, and invoke the corresponding method if it can.
* </p>
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
public interface PermissionCompatDelegate {
/**
@@ -66,7 +81,11 @@
*
* @return Whether the delegate has handled the permission request.
* @see FragmentCompat#requestPermissions(Fragment, String[], int)
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
boolean requestPermissions(Fragment fragment, String[] permissions, int requestCode);
}
@@ -157,22 +176,34 @@
* delegate.
*
* @param delegate The delegate to be set. {@code null} to clear the set delegate.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
public static void setPermissionCompatDelegate(PermissionCompatDelegate delegate) {
sDelegate = delegate;
}
/**
* @hide
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @Deprecated
public static PermissionCompatDelegate getPermissionCompatDelegate() {
return sDelegate;
}
/**
* This interface is the contract for receiving the results for permission requests.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
public interface OnRequestPermissionsResultCallback {
/**
@@ -188,7 +219,11 @@
* or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
*
* @see #requestPermissions(android.app.Fragment, String[], int)
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults);
}
@@ -197,7 +232,8 @@
* Call {@link Fragment#setMenuVisibility(boolean) Fragment.setMenuVisibility(boolean)}
* if running on an appropriate version of the platform.
*
- * @deprecated Use {@link Fragment#setMenuVisibility(boolean)} directly.
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
@Deprecated
public static void setMenuVisibility(Fragment f, boolean visible) {
@@ -207,7 +243,11 @@
/**
* Call {@link Fragment#setUserVisibleHint(boolean) setUserVisibleHint(boolean)}
* if running on an appropriate version of the platform.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
public static void setUserVisibleHint(Fragment f, boolean deferStart) {
IMPL.setUserVisibleHint(f, deferStart);
}
@@ -262,7 +302,11 @@
* @see android.support.v4.content.ContextCompat#checkSelfPermission(
* android.content.Context, String)
* @see #shouldShowRequestPermissionRationale(android.app.Fragment, String)
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
public static void requestPermissions(@NonNull Fragment fragment,
@NonNull String[] permissions, int requestCode) {
if (sDelegate != null && sDelegate.requestPermissions(fragment, permissions, requestCode)) {
@@ -293,7 +337,11 @@
* @see android.support.v4.content.ContextCompat#checkSelfPermission(
* android.content.Context, String)
* @see #requestPermissions(android.app.Fragment, String[], int)
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+ * {@link Fragment}.
*/
+ @Deprecated
public static boolean shouldShowRequestPermissionRationale(@NonNull Fragment fragment,
@NonNull String permission) {
return IMPL.shouldShowRequestPermissionRationale(fragment, permission);
diff --git a/v13/java/android/support/v13/app/FragmentPagerAdapter.java b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
index e0b788a..112ed02 100644
--- a/v13/java/android/support/v13/app/FragmentPagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
@@ -61,7 +61,10 @@
*
* {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager_list.xml
* complete}
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
*/
+@Deprecated
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
@@ -70,15 +73,26 @@
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
public FragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
*/
+ @Deprecated
public abstract Fragment getItem(int position);
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void startUpdate(ViewGroup container) {
if (container.getId() == View.NO_ID) {
@@ -87,6 +101,10 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@SuppressWarnings("ReferenceEquality")
@Override
public Object instantiateItem(ViewGroup container, int position) {
@@ -116,6 +134,10 @@
return fragment;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
@@ -126,6 +148,10 @@
mCurTransaction.detach((Fragment)object);
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@SuppressWarnings("ReferenceEquality")
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
@@ -143,6 +169,10 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
@@ -152,16 +182,28 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@Override
public Parcelable saveState() {
return null;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
@@ -174,7 +216,10 @@
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
*/
+ @Deprecated
public long getItemId(int position) {
return position;
}
diff --git a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
index 45a6bf5..76a3224 100644
--- a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
@@ -64,7 +64,10 @@
*
* {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
* complete}
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
*/
+@Deprecated
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragStatePagerAdapter";
private static final boolean DEBUG = false;
@@ -76,15 +79,26 @@
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
public FragmentStatePagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
*/
+ @Deprecated
public abstract Fragment getItem(int position);
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void startUpdate(ViewGroup container) {
if (container.getId() == View.NO_ID) {
@@ -93,6 +107,10 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
@@ -129,6 +147,10 @@
return fragment;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
@@ -148,6 +170,10 @@
mCurTransaction.remove(fragment);
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@SuppressWarnings("ReferenceEquality")
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
@@ -165,6 +191,10 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
@@ -174,11 +204,19 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@Override
public Parcelable saveState() {
Bundle state = null;
@@ -201,6 +239,10 @@
return state;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+ */
+ @Deprecated
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
diff --git a/v13/java/android/support/v13/app/FragmentTabHost.java b/v13/java/android/support/v13/app/FragmentTabHost.java
index 2326ccb..5c34ab5 100644
--- a/v13/java/android/support/v13/app/FragmentTabHost.java
+++ b/v13/java/android/support/v13/app/FragmentTabHost.java
@@ -38,7 +38,10 @@
* Version of {@link android.support.v4.app.FragmentTabHost} that can be
* used with the platform {@link android.app.Fragment} APIs. You will not
* normally use this, instead using action bar tabs.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
*/
+@Deprecated
public class FragmentTabHost extends TabHost implements TabHost.OnTabChangeListener {
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
private FrameLayout mRealTabContent;
@@ -117,6 +120,10 @@
};
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
public FragmentTabHost(Context context) {
// Note that we call through to the version that takes an AttributeSet,
// because the simple Context construct can result in a broken object!
@@ -124,6 +131,10 @@
initFragmentTabHost(context, null);
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
public FragmentTabHost(Context context, AttributeSet attrs) {
super(context, attrs);
initFragmentTabHost(context, attrs);
@@ -167,9 +178,7 @@
}
/**
- * @deprecated Don't call the original TabHost setup, you must instead
- * call {@link #setup(Context, FragmentManager)} or
- * {@link #setup(Context, FragmentManager, int)}.
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
*/
@Override
@Deprecated
@@ -178,6 +187,10 @@
"Must call setup() that takes a Context and FragmentManager");
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
public void setup(Context context, FragmentManager manager) {
ensureHierarchy(context); // Ensure views required by super.setup()
super.setup();
@@ -186,6 +199,10 @@
ensureContent();
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
public void setup(Context context, FragmentManager manager, int containerId) {
ensureHierarchy(context); // Ensure views required by super.setup()
super.setup();
@@ -212,11 +229,19 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
@Override
public void setOnTabChangedListener(OnTabChangeListener l) {
mOnTabChangeListener = l;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
@@ -239,6 +264,10 @@
addTab(tabSpec);
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -278,12 +307,20 @@
}
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAttached = false;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
@@ -292,6 +329,10 @@
return ss;
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
@@ -303,6 +344,10 @@
setCurrentTabByTag(ss.curTab);
}
+ /**
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+ */
+ @Deprecated
@Override
public void onTabChanged(String tabId) {
if (mAttached) {
diff --git a/v14/preference/res/layout-v17/preference_category_material.xml b/v14/preference/res/layout-v17/preference_category_material.xml
index 804da6a..db3abfe 100644
--- a/v14/preference/res/layout-v17/preference_category_material.xml
+++ b/v14/preference/res/layout-v17/preference_category_material.xml
@@ -15,49 +15,13 @@
~ limitations under the License
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="8dp"
- android:layout_marginTop="8dp"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart">
-
- <LinearLayout
- android:id="@+id/icon_frame"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
- android:orientation="horizontal">
- <android.support.v7.internal.widget.PreferenceImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:maxHeight="18dp"
- app:maxWidth="18dp"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingStart="56dp">
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:textAlignment="viewStart"
- android:textColor="@color/preference_fallback_accent_color"/>
- <TextView
- android:id="@android:id/summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:singleLine="true"
- android:textColor="?android:attr/textColorSecondary"/>
- </LinearLayout>
-
-</FrameLayout>
+ android:layout_marginBottom="16dip"
+ android:textAppearance="@style/Preference_TextAppearanceMaterialBody2"
+ android:textColor="@color/preference_fallback_accent_color"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingTop="16dip" />
diff --git a/v14/preference/res/layout-v21/preference_category_material.xml b/v14/preference/res/layout-v21/preference_category_material.xml
index 1331268..dad9a5c 100644
--- a/v14/preference/res/layout-v21/preference_category_material.xml
+++ b/v14/preference/res/layout-v21/preference_category_material.xml
@@ -15,52 +15,13 @@
~ limitations under the License
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="8dp"
- android:layout_marginTop="8dp"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart">
-
- <LinearLayout
- android:id="@+id/icon_frame"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
- android:orientation="horizontal">
- <android.support.v7.internal.widget.PreferenceImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:maxHeight="18dp"
- app:maxWidth="18dp"
- android:tint="?android:attr/textColorPrimary"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingStart="56dp">
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Body2"
- android:textColor="?android:attr/colorAccent"/>
- <TextView
- android:id="@android:id/summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceListItemSecondary"
- android:textColor="?android:attr/textColorSecondary"/>
- </LinearLayout>
-
-</FrameLayout>
+ android:layout_marginBottom="16dip"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingTop="16dip" />
diff --git a/v14/preference/res/layout-v21/preference_dropdown_material.xml b/v14/preference/res/layout-v21/preference_dropdown_material.xml
index f886d88..a92095e 100644
--- a/v14/preference/res/layout-v21/preference_dropdown_material.xml
+++ b/v14/preference/res/layout-v21/preference_dropdown_material.xml
@@ -15,18 +15,74 @@
~ limitations under the License
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false"
+ android:focusable="true" >
<Spinner
android:id="@+id/spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/preference_no_icon_padding_start"
android:visibility="invisible" />
- <include layout="@layout/preference_material"/>
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="-4dp"
+ android:minWidth="60dp"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingRight="12dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <android.support.v7.internal.widget.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:maxWidth="48dp"
+ app:maxHeight="48dp" />
+ </LinearLayout>
-</FrameLayout>
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/Preference_TextAppearanceMaterialSubhead"
+ android:ellipsize="marquee" />
+
+ <TextView android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="10" />
+
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingLeft="16dp"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/v14/preference/res/layout/preference_category_material.xml b/v14/preference/res/layout/preference_category_material.xml
index 8eb2137..e366e7a 100644
--- a/v14/preference/res/layout/preference_category_material.xml
+++ b/v14/preference/res/layout/preference_category_material.xml
@@ -15,49 +15,13 @@
~ limitations under the License
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="8dp"
- android:layout_marginTop="8dp"
- android:paddingLeft="?android:attr/listPreferredItemPaddingLeft">
-
- <LinearLayout
- android:id="@+id/icon_frame"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
- android:orientation="horizontal">
- <android.support.v7.internal.widget.PreferenceImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:maxHeight="18dp"
- app:maxWidth="18dp"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingLeft="56dp">
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
- android:textAlignment="viewStart"
- android:textColor="@color/preference_fallback_accent_color"/>
- <TextView
- android:id="@android:id/summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:singleLine="true"
- android:textColor="?android:attr/textColorSecondary"/>
- </LinearLayout>
-
-</FrameLayout>
+ android:layout_marginBottom="16dip"
+ android:textAppearance="@style/Preference_TextAppearanceMaterialBody2"
+ android:textColor="@color/preference_fallback_accent_color"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingTop="16dip" />
diff --git a/v14/preference/res/values/styles.xml b/v14/preference/res/values/styles.xml
index edd5285..26b1544 100644
--- a/v14/preference/res/values/styles.xml
+++ b/v14/preference/res/values/styles.xml
@@ -24,10 +24,6 @@
<style name="Preference.Material">
<item name="android:layout">@layout/preference_material</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">true</item>
- <item name="singleLineTitle">false</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference.Information.Material">
@@ -38,16 +34,10 @@
<style name="Preference.Category.Material">
<item name="android:layout">@layout/preference_category_material</item>
- <item name="allowDividerAbove">true</item>
- <item name="allowDividerBelow">true</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference.CheckBoxPreference.Material">
<item name="android:layout">@layout/preference_material</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">true</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference.SwitchPreferenceCompat.Material">
@@ -56,10 +46,6 @@
<style name="Preference.SwitchPreference.Material">
<item name="android:layout">@layout/preference_material</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">true</item>
- <item name="singleLineTitle">false</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference.SeekBarPreference.Material">
@@ -70,31 +56,18 @@
<style name="Preference.PreferenceScreen.Material">
<item name="android:layout">@layout/preference_material</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">true</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference.DialogPreference.Material">
<item name="android:layout">@layout/preference_material</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">true</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference.DialogPreference.EditTextPreference.Material">
<item name="android:layout">@layout/preference_material</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">true</item>
- <item name="singleLineTitle">false</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference.DropDown.Material">
<item name="android:layout">@layout/preference_dropdown_material</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">true</item>
- <item name="iconSpaceReserved">true</item>
</style>
<style name="Preference_TextAppearanceMaterialBody2">
@@ -113,7 +86,6 @@
<style name="PreferenceFragment.Material">
<item name="android:divider">@drawable/preference_list_divider_material</item>
- <item name="allowDividerAfterLastItem">false</item>
</style>
<style name="PreferenceFragmentList.Material">
diff --git a/v14/preference/res/values/themes.xml b/v14/preference/res/values/themes.xml
index 919873e..a69126f 100644
--- a/v14/preference/res/values/themes.xml
+++ b/v14/preference/res/values/themes.xml
@@ -36,6 +36,5 @@
<item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference.Material</item>
<item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
<item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
- <item name="android:scrollbars">vertical</item>
</style>
</resources>
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index 214c6db..a3b80a8 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -13,8 +13,8 @@
api(project(":support-vector-drawable"))
api(project(":animated-vector-drawable"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation project(':support-testutils'), {
diff --git a/v7/appcompat/res/values-bs/strings.xml b/v7/appcompat/res/values-bs/strings.xml
index 3687875..07d1411 100644
--- a/v7/appcompat/res/values-bs/strings.xml
+++ b/v7/appcompat/res/values-bs/strings.xml
@@ -29,8 +29,8 @@
<string name="abc_searchview_description_voice" msgid="893419373245838918">"Glasovno pretraživanje"</string>
<string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Odaberite aplikaciju"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Prikaži sve"</string>
- <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"Podijeli koristeći aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
- <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Podijeli sa"</string>
+ <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"Dijeli koristeći aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+ <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Dijeli sa"</string>
<string name="abc_capital_on" msgid="3405795526292276155">"UKLJUÄŒI"</string>
<string name="abc_capital_off" msgid="121134116657445385">"ISKLJUÄŒI"</string>
<string name="search_menu_title" msgid="146198913615257606">"Pretraži"</string>
diff --git a/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java b/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java
index 564bbfc..834f854 100644
--- a/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java
+++ b/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -54,7 +54,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
/**
@@ -85,7 +84,7 @@
final Handler mSubMenuHoverHandler;
/** List of menus that were added before this popup was shown. */
- private final List<MenuBuilder> mPendingMenus = new LinkedList<>();
+ private final List<MenuBuilder> mPendingMenus = new ArrayList<>();
/**
* List of open menus. The first item is the root menu and each
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java b/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java
index 76e06da..14723a0 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java
@@ -268,10 +268,10 @@
// Mark indices of children that can receive an extra cell.
if (lp.cellsUsed < minCells) {
minCells = lp.cellsUsed;
- minCellsAt = 1 << i;
+ minCellsAt = 1L << i;
minCellsItemCount = 1;
} else if (lp.cellsUsed == minCells) {
- minCellsAt |= 1 << i;
+ minCellsAt |= 1L << i;
minCellsItemCount++;
}
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
index e82e469..7e98494 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
@@ -45,8 +45,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Hashtable;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Utility class which encapsulates the logic for the TextView auto-size text feature added to
@@ -66,7 +66,8 @@
private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
// Cache of TextView methods used via reflection; the key is the method name and the value is
// the method itself or null if it can not be found.
- private static Hashtable<String, Method> sTextViewMethodByNameCache = new Hashtable<>();
+ private static ConcurrentHashMap<String, Method> sTextViewMethodByNameCache =
+ new ConcurrentHashMap<>();
// Use this to specify that any of the auto-size configuration int values have not been set.
static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
// Ported from TextView#VERY_WIDE. Represents a maximum width in pixels the TextView takes when
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java
index edc9781..b98197c 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java
@@ -283,7 +283,7 @@
mAdapter.unregisterDataSetObserver(mObserver);
}
mAdapter = adapter;
- if (mAdapter != null) {
+ if (adapter != null) {
adapter.registerDataSetObserver(mObserver);
}
diff --git a/v7/cardview/res/values/attrs.xml b/v7/cardview/res/values/attrs.xml
index deed51b..8bac9cc 100644
--- a/v7/cardview/res/values/attrs.xml
+++ b/v7/cardview/res/values/attrs.xml
@@ -15,6 +15,9 @@
-->
<resources>
+ <!-- Default CardView style -->
+ <attr name="cardViewStyle" format="reference" />
+
<declare-styleable name="CardView">
<!-- Background color for CardView. -->
<attr name="cardBackgroundColor" format="color" />
diff --git a/v7/cardview/src/main/java/android/support/v7/widget/CardView.java b/v7/cardview/src/main/java/android/support/v7/widget/CardView.java
index 58a04f0..a45ee98 100644
--- a/v7/cardview/src/main/java/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/main/java/android/support/v7/widget/CardView.java
@@ -108,18 +108,57 @@
final Rect mShadowBounds = new Rect();
public CardView(@NonNull Context context) {
- super(context);
- initialize(context, null, 0);
+ this(context, null);
}
public CardView(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
+ this(context, attrs, R.attr.cardViewStyle);
}
public CardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- initialize(context, attrs, defStyleAttr);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
+ R.style.CardView);
+ ColorStateList backgroundColor;
+ if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
+ backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
+ } else {
+ // There isn't one set, so we'll compute one based on the theme
+ final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
+ final int themeColorBackground = aa.getColor(0, 0);
+ aa.recycle();
+
+ // If the theme colorBackground is light, use our own light color, otherwise dark
+ final float[] hsv = new float[3];
+ Color.colorToHSV(themeColorBackground, hsv);
+ backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
+ ? getResources().getColor(R.color.cardview_light_background)
+ : getResources().getColor(R.color.cardview_dark_background));
+ }
+ float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
+ float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
+ float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
+ mCompatPadding = a.getBoolean(R.styleable.CardView_cardUseCompatPadding, false);
+ mPreventCornerOverlap = a.getBoolean(R.styleable.CardView_cardPreventCornerOverlap, true);
+ int defaultPadding = a.getDimensionPixelSize(R.styleable.CardView_contentPadding, 0);
+ mContentPadding.left = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingLeft,
+ defaultPadding);
+ mContentPadding.top = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingTop,
+ defaultPadding);
+ mContentPadding.right = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingRight,
+ defaultPadding);
+ mContentPadding.bottom = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingBottom,
+ defaultPadding);
+ if (elevation > maxElevation) {
+ maxElevation = elevation;
+ }
+ mUserSetMinWidth = a.getDimensionPixelSize(R.styleable.CardView_android_minWidth, 0);
+ mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
+ a.recycle();
+
+ IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
+ elevation, maxElevation);
}
@Override
@@ -220,50 +259,6 @@
}
}
- private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
- R.style.CardView);
- ColorStateList backgroundColor;
- if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
- backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
- } else {
- // There isn't one set, so we'll compute one based on the theme
- final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
- final int themeColorBackground = aa.getColor(0, 0);
- aa.recycle();
-
- // If the theme colorBackground is light, use our own light color, otherwise dark
- final float[] hsv = new float[3];
- Color.colorToHSV(themeColorBackground, hsv);
- backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
- ? getResources().getColor(R.color.cardview_light_background)
- : getResources().getColor(R.color.cardview_dark_background));
- }
- float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
- float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
- float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
- mCompatPadding = a.getBoolean(R.styleable.CardView_cardUseCompatPadding, false);
- mPreventCornerOverlap = a.getBoolean(R.styleable.CardView_cardPreventCornerOverlap, true);
- int defaultPadding = a.getDimensionPixelSize(R.styleable.CardView_contentPadding, 0);
- mContentPadding.left = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingLeft,
- defaultPadding);
- mContentPadding.top = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingTop,
- defaultPadding);
- mContentPadding.right = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingRight,
- defaultPadding);
- mContentPadding.bottom = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingBottom,
- defaultPadding);
- if (elevation > maxElevation) {
- maxElevation = elevation;
- }
- mUserSetMinWidth = a.getDimensionPixelSize(R.styleable.CardView_android_minWidth, 0);
- mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
- a.recycle();
-
- IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
- elevation, maxElevation);
- }
-
@Override
public void setMinimumWidth(int minWidth) {
mUserSetMinWidth = minWidth;
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index dc3a494..7df5397 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -10,8 +10,8 @@
api(project(":support-compat"))
api(project(":support-core-ui"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
}
android {
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 0c94194..dbf3da5 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -11,8 +11,8 @@
api(project(":appcompat-v7"))
api(project(":palette-v7"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(TEST_RULES)
}
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index cf6fc1f..cc372ec 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -2560,12 +2560,16 @@
// TODO: Remove the following logging when no longer needed.
if (sGlobal == null || (mBluetoothRoute != null && route.isDefault())) {
final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
- StringBuffer sb = new StringBuffer();
+ StringBuilder sb = new StringBuilder();
// callStack[3] is the caller of this method.
for (int i = 3; i < callStack.length; i++) {
StackTraceElement caller = callStack[i];
- sb.append(caller.getClassName() + "." + caller.getMethodName()
- + ":" + caller.getLineNumber()).append(" ");
+ sb.append(caller.getClassName())
+ .append(".")
+ .append(caller.getMethodName())
+ .append(":")
+ .append(caller.getLineNumber())
+ .append(" ");
}
if (sGlobal == null) {
Log.w(TAG, "setSelectedRouteInternal is called while sGlobal is null: pkgName="
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 95c0799..a1b1fc9 100644
--- a/v7/palette/build.gradle
+++ b/v7/palette/build.gradle
@@ -10,7 +10,7 @@
api(project(":support-compat"))
api(project(":support-core-utils"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
}
android {
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index 16af11e..698afb6 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -27,8 +27,8 @@
api(project(":appcompat-v7"))
api(project(":recyclerview-v7"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
}
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index b98e7f7..0a83989 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -11,8 +11,8 @@
api(project(":support-compat"))
api(project(":support-core-ui"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(JUNIT)
@@ -20,7 +20,7 @@
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
- testImplementation(TEST_RUNNER, libs.exclude_annotations)
+ testImplementation(TEST_RUNNER)
}
android {
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
index 84c28b1..afadfc9 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
@@ -6544,7 +6544,8 @@
* @see #getItemViewType(int)
* @see #onBindViewHolder(ViewHolder, int)
*/
- public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
+ @NonNull
+ public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
/**
* Called by RecyclerView to display the data at the specified position. This method should
@@ -6566,7 +6567,7 @@
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
*/
- public abstract void onBindViewHolder(VH holder, int position);
+ public abstract void onBindViewHolder(@NonNull VH holder, int position);
/**
* Called by RecyclerView to display the data at the specified position. This method
@@ -6597,7 +6598,8 @@
* @param payloads A non-null list of merged payloads. Can be empty list if requires full
* update.
*/
- public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
+ public void onBindViewHolder(@NonNull VH holder, int position,
+ @NonNull List<Object> payloads) {
onBindViewHolder(holder, position);
}
@@ -6607,7 +6609,7 @@
*
* @see #onCreateViewHolder(ViewGroup, int)
*/
- public final VH createViewHolder(ViewGroup parent, int viewType) {
+ public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
@@ -6622,7 +6624,7 @@
*
* @see #onBindViewHolder(ViewHolder, int)
*/
- public final void bindViewHolder(VH holder, int position) {
+ public final void bindViewHolder(@NonNull VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
@@ -6719,7 +6721,7 @@
*
* @param holder The ViewHolder for the view being recycled
*/
- public void onViewRecycled(VH holder) {
+ public void onViewRecycled(@NonNull VH holder) {
}
/**
@@ -6756,7 +6758,7 @@
* RecyclerView will check the View's transient state again before giving a final decision.
* Default implementation returns false.
*/
- public boolean onFailedToRecycleView(VH holder) {
+ public boolean onFailedToRecycleView(@NonNull VH holder) {
return false;
}
@@ -6770,7 +6772,7 @@
*
* @param holder Holder of the view being attached
*/
- public void onViewAttachedToWindow(VH holder) {
+ public void onViewAttachedToWindow(@NonNull VH holder) {
}
/**
@@ -6782,7 +6784,7 @@
*
* @param holder Holder of the view being detached
*/
- public void onViewDetachedFromWindow(VH holder) {
+ public void onViewDetachedFromWindow(@NonNull VH holder) {
}
/**
@@ -6810,7 +6812,7 @@
*
* @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
*/
- public void registerAdapterDataObserver(AdapterDataObserver observer) {
+ public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
@@ -6824,7 +6826,7 @@
*
* @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver)
*/
- public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
+ public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.unregisterObserver(observer);
}
@@ -6836,7 +6838,7 @@
* @param recyclerView The RecyclerView instance which started observing this adapter.
* @see #onDetachedFromRecyclerView(RecyclerView)
*/
- public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
}
/**
@@ -6845,7 +6847,7 @@
* @param recyclerView The RecyclerView instance which stopped observing this adapter.
* @see #onAttachedToRecyclerView(RecyclerView)
*/
- public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
}
/**
@@ -6921,7 +6923,7 @@
*
* @see #notifyItemRangeChanged(int, int)
*/
- public final void notifyItemChanged(int position, Object payload) {
+ public final void notifyItemChanged(int position, @Nullable Object payload) {
mObservable.notifyItemRangeChanged(position, 1, payload);
}
@@ -6969,7 +6971,8 @@
*
* @see #notifyItemChanged(int)
*/
- public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ public final void notifyItemRangeChanged(int positionStart, int itemCount,
+ @Nullable Object payload) {
mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
}
@@ -10072,7 +10075,7 @@
if (vScroll == 0 && hScroll == 0) {
return false;
}
- mRecyclerView.smoothScrollBy(hScroll, vScroll);
+ mRecyclerView.scrollBy(hScroll, vScroll);
return true;
}
@@ -10833,8 +10836,12 @@
*/
private void onEnteredHiddenState(RecyclerView parent) {
// While the view item is in hidden state, make it invisible for the accessibility.
- mWasImportantForAccessibilityBeforeHidden =
- ViewCompat.getImportantForAccessibility(itemView);
+ if (mPendingAccessibilityState != PENDING_ACCESSIBILITY_STATE_NOT_SET) {
+ mWasImportantForAccessibilityBeforeHidden = mPendingAccessibilityState;
+ } else {
+ mWasImportantForAccessibilityBeforeHidden =
+ ViewCompat.getImportantForAccessibility(itemView);
+ }
parent.setChildImportantForAccessibilityInternal(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
@@ -11193,7 +11200,7 @@
// do nothing
}
- public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
// fallback to onItemRangeChanged(positionStart, itemCount) if app
// does not override this method.
onItemRangeChanged(positionStart, itemCount);
@@ -11670,7 +11677,8 @@
notifyItemRangeChanged(positionStart, itemCount, null);
}
- public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ public void notifyItemRangeChanged(int positionStart, int itemCount,
+ @Nullable Object payload) {
// since onItemRangeChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
@@ -12063,6 +12071,7 @@
+ "mTargetPosition=" + mTargetPosition
+ ", mData=" + mData
+ ", mItemCount=" + mItemCount
+ + ", mIsMeasuring=" + mIsMeasuring
+ ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount
+ ", mDeletedInvisibleItemCountSincePreviousLayout="
+ mDeletedInvisibleItemCountSincePreviousLayout
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java b/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java
index aee48df..d2b6a20 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java
@@ -457,7 +457,7 @@
destroyCallbacks();
}
mRecyclerView = recyclerView;
- if (mRecyclerView != null) {
+ if (recyclerView != null) {
final Resources resources = recyclerView.getResources();
mSwipeEscapeVelocity = resources
.getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java b/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
deleted file mode 100644
index 32e1295..0000000
--- a/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.util;
-
-import android.app.Instrumentation;
-import android.graphics.Rect;
-import android.support.annotation.NonNull;
-import android.support.testutils.PollingCheck;
-import android.support.v7.widget.TestActivity;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * A JUnit rule that ensures that IME is closed after a test is finished by determining if the
- * keyboard is open, and if it is closing it. If the rules determines the keyboard is open and is
- * unable to close it within a timeout (see source), an exception will be thrown.
- *
- * A test that wants to benefit from this functionality must call
- * {@link #setup(TestActivity, Instrumentation)} with the {@link TestActivity} under test and the
- * test's {@link Instrumentation}, or this rule does nothing.
- */
-public class ImeCleanUpTestRule implements TestRule {
-
- // We consider the keyboard open if its height is at least this percentage of the available
- // screen height.
- private static final float KEYBOARD_HEIGHT_TO_SCREEN_RATIO = .15f;
-
- private View mContainerView;
- private Instrumentation mInstrumentation;
-
- @Override
- public Statement apply(final Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- try {
- base.evaluate();
- } finally {
- closeImeIfOpen();
- }
- }
- };
- }
-
- /**
- * Call to enable the functionality of this TestRule.
- * @param testActivity The {@link TestActivity} under test.
- * @param instrumentation The test's {@link Instrumentation}.
- */
- public void setup(@NonNull TestActivity testActivity,
- @NonNull Instrumentation instrumentation) {
- mContainerView = testActivity.getContainer();
- mInstrumentation = instrumentation;
- }
-
- private void closeImeIfOpen() {
- if (mContainerView == null || mInstrumentation == null) {
- return;
- }
-
- final Rect r = new Rect();
- mContainerView.getWindowVisibleDisplayFrame(r);
-
- // This is the entire height of the screen available to both the view and IME
- final int screenHeight = mContainerView.getHeight();
-
- // r.bottom is the position above IME if it's open or device button.
- // if IME is shown, r.bottom is smaller than screenHeight.
- int imeHeight = screenHeight - r.bottom;
-
- if (imeHeight > screenHeight * KEYBOARD_HEIGHT_TO_SCREEN_RATIO) {
- // Soft keyboard is shown, therefore we click the back button to close it and wait for
- // it to be closed.
- mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
- PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
- @Override
- public boolean canProceed() {
- mContainerView.getWindowVisibleDisplayFrame(r);
- int imeHeight = screenHeight - r.bottom;
- return imeHeight < screenHeight * KEYBOARD_HEIGHT_TO_SCREEN_RATIO;
- }
- });
- }
- }
-}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
index 13dd1e4..f4a8e37 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -325,16 +325,16 @@
}
}
- class GridEditTextAdapter extends EditTextAdapter {
+ class GridFocusableAdapter extends FocusableAdapter {
Set<Integer> mFullSpanItems = new HashSet<Integer>();
int mSpanPerItem = 1;
- GridEditTextAdapter(int count) {
+ GridFocusableAdapter(int count) {
this(count, 1);
}
- GridEditTextAdapter(int count, int spanPerItem) {
+ GridFocusableAdapter(int count, int spanPerItem) {
super(count);
mSpanPerItem = spanPerItem;
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index 157fb12..eed7d20 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -35,14 +35,13 @@
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
+import android.support.testutils.PollingCheck;
import android.support.v4.view.ViewCompat;
import android.support.v7.recyclerview.test.R;
-import android.text.Editable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -325,6 +324,12 @@
result[0] = view.requestFocus();
}
});
+ PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return view.hasFocus();
+ }
+ });
if (waitForScroll && result[0]) {
waitForIdleScroll(mRecyclerView);
}
@@ -789,34 +794,32 @@
}
}
- public class EditTextAdapter extends RecyclerView.Adapter<TestViewHolder> {
+ public class FocusableAdapter extends RecyclerView.Adapter<TestViewHolder> {
- final ArrayList<Editable> mEditables;
- public EditTextAdapter(int count) {
- mEditables = new ArrayList<>();
- for (int i = 0; i < count; ++i) {
- mEditables.add(Editable.Factory.getInstance().newEditable("Sample Text " + i));
- }
+ private int mCount;
+
+ FocusableAdapter(int count) {
+ mCount = count;
}
@Override
public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final EditText editText = new EditText(parent.getContext());
- editText.setLayoutParams(new ViewGroup.LayoutParams(
+ final TextView textView = new TextView(parent.getContext());
+ textView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- final TestViewHolder viewHolder = new TestViewHolder(editText);
- return viewHolder;
+ textView.setFocusable(true);
+ textView.setBackgroundResource(R.drawable.item_bg);
+ return new TestViewHolder(textView);
}
@Override
public void onBindViewHolder(TestViewHolder holder, int position) {
- ((EditText) holder.itemView).setText(Editable.Factory.getInstance().newEditable(
- mEditables.get(position)));
+ ((TextView) holder.itemView).setText("Item " + position);
}
@Override
public int getItemCount() {
- return mEditables.size();
+ return mCount;
}
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DummyItemAnimator.java b/v7/recyclerview/tests/src/android/support/v7/widget/DummyItemAnimator.java
new file mode 100644
index 0000000..a1491fa
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DummyItemAnimator.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This is a dummy ItemAnimator class that does not depends on Duration, Tests would use this class
+ * to control whenever they want the Animator to finish.
+ * 1. Test MUST call endAnimation(ViewHolder) on UI thread to finish animation of a given ViewHolder
+ * Or Test calls endAnimations() on UI thread to end animations for all.
+ * 2. Test can call getAddAnimations() etc. to get ViewHolders that currently running animation.
+ * 3. Test can call {@link #expect(int, int)} and {@link #waitFor(int)} to wait given
+ * Events are fired.
+ */
+public class DummyItemAnimator extends SimpleItemAnimator {
+
+ static final long TIMEOUT_SECOND = 10;
+
+ ArrayList<RecyclerView.ViewHolder> mAdds = new ArrayList();
+ ArrayList<RecyclerView.ViewHolder> mRemoves = new ArrayList();
+ ArrayList<RecyclerView.ViewHolder> mMoves = new ArrayList();
+ ArrayList<RecyclerView.ViewHolder> mChangesOld = new ArrayList();
+ ArrayList<RecyclerView.ViewHolder> mChangesNew = new ArrayList();
+
+ @Retention(CLASS)
+ @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+ public @interface CountDownLatchIndex {
+ }
+
+ @CountDownLatchIndex
+ public static final int ADD_START = 0;
+
+ @CountDownLatchIndex
+ public static final int ADD_FINISHED = 1;
+
+ @CountDownLatchIndex
+ public static final int REMOVE_START = 2;
+
+ @CountDownLatchIndex
+ public static final int REMOVE_FINISHED = 3;
+
+ @CountDownLatchIndex
+ public static final int MOVE_START = 4;
+
+ @CountDownLatchIndex
+ public static final int MOVE_FINISHED = 5;
+
+ @CountDownLatchIndex
+ public static final int CHANGE_OLD_START = 6;
+
+ @CountDownLatchIndex
+ public static final int CHANGE_OLD_FINISHED = 7;
+
+ @CountDownLatchIndex
+ public static final int CHANGE_NEW_START = 8;
+
+ @CountDownLatchIndex
+ public static final int CHANGE_NEW_FINISHED = 9;
+
+ static final int NUM_COUNT_DOWN_LATCH = 10;
+
+ CountDownLatch[] mCountDownLatches = new CountDownLatch[NUM_COUNT_DOWN_LATCH];
+
+
+ public List<RecyclerView.ViewHolder> getAddAnimations() {
+ return mAdds;
+ }
+
+ public List<RecyclerView.ViewHolder> getRemoveAnimations() {
+ return mRemoves;
+ }
+
+ public List<RecyclerView.ViewHolder> getMovesAnimations() {
+ return mMoves;
+ }
+
+ public List<RecyclerView.ViewHolder> getChangesOldAnimations() {
+ return mChangesOld;
+ }
+
+ public List<RecyclerView.ViewHolder> getChangesNewAnimations() {
+ return mChangesNew;
+ }
+
+ @Override
+ public boolean animateRemove(RecyclerView.ViewHolder holder) {
+ mRemoves.add(holder);
+ dispatchRemoveStarting(holder);
+ return false;
+ }
+
+ @Override
+ public boolean animateAdd(RecyclerView.ViewHolder holder) {
+ mAdds.add(holder);
+ dispatchAddStarting(holder);
+ return false;
+ }
+
+ @Override
+ public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX,
+ int toY) {
+ mMoves.add(holder);
+ dispatchMoveStarting(holder);
+ return false;
+ }
+
+ @Override
+ public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+ RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
+ mChangesOld.add(oldHolder);
+ mChangesNew.add(newHolder);
+ dispatchChangeStarting(oldHolder, true);
+ dispatchChangeStarting(newHolder, false);
+ return false;
+ }
+
+ public void expect(@CountDownLatchIndex int index, int count) {
+ mCountDownLatches[index] = new CountDownLatch(count);
+ }
+
+ public void waitFor(@CountDownLatchIndex int index)
+ throws InterruptedException {
+ mCountDownLatches[index].await(TIMEOUT_SECOND, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
+ CountDownLatch latch = mCountDownLatches[oldItem ? CHANGE_OLD_START : CHANGE_NEW_START];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onMoveStarting(RecyclerView.ViewHolder item) {
+ CountDownLatch latch = mCountDownLatches[MOVE_START];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onAddStarting(RecyclerView.ViewHolder item) {
+ CountDownLatch latch = mCountDownLatches[ADD_START];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onRemoveStarting(RecyclerView.ViewHolder item) {
+ CountDownLatch latch = mCountDownLatches[REMOVE_START];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
+ CountDownLatch latch = mCountDownLatches[oldItem
+ ? CHANGE_OLD_FINISHED : CHANGE_NEW_FINISHED];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onMoveFinished(RecyclerView.ViewHolder item) {
+ CountDownLatch latch = mCountDownLatches[MOVE_FINISHED];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onAddFinished(RecyclerView.ViewHolder item) {
+ CountDownLatch latch = mCountDownLatches[ADD_FINISHED];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onRemoveFinished(RecyclerView.ViewHolder item) {
+ CountDownLatch latch = mCountDownLatches[REMOVE_FINISHED];
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void runPendingAnimations() {
+ }
+
+ @Override
+ public void endAnimation(RecyclerView.ViewHolder item) {
+ if (mAdds.remove(item)) {
+ dispatchAddFinished(item);
+ } else if (mRemoves.remove(item)) {
+ dispatchRemoveFinished(item);
+ } else if (mMoves.remove(item)) {
+ dispatchMoveFinished(item);
+ } else if (mChangesOld.remove(item)) {
+ dispatchChangeFinished(item, true);
+ } else if (mChangesNew.remove(item)) {
+ dispatchChangeFinished(item, false);
+ }
+ }
+
+ @Override
+ public void endAnimations() {
+ for (int i = mAdds.size() - 1; i >= 0; i--) {
+ endAnimation(mAdds.get(i));
+ }
+ for (int i = mRemoves.size() - 1; i >= 0; i--) {
+ endAnimation(mRemoves.get(i));
+ }
+ for (int i = mMoves.size() - 1; i >= 0; i--) {
+ endAnimation(mMoves.get(i));
+ }
+ for (int i = mChangesOld.size() - 1; i >= 0; i--) {
+ endAnimation(mChangesOld.get(i));
+ }
+ for (int i = mChangesNew.size() - 1; i >= 0; i--) {
+ endAnimation(mChangesNew.get(i));
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mAdds.size() != 0
+ || mRemoves.size() != 0
+ || mMoves.size() != 0
+ || mChangesOld.size() != 0
+ || mChangesNew.size() != 0;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 5d378ff..1a5892d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -20,7 +20,6 @@
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
@@ -37,20 +36,15 @@
import android.support.test.filters.LargeTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.util.ImeCleanUpTestRule;
-import android.support.v7.util.TouchUtils;
import android.test.UiThreadTest;
import android.util.SparseIntArray;
import android.util.StateSet;
-import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import org.hamcrest.CoreMatchers;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,9 +58,6 @@
@RunWith(AndroidJUnit4.class)
public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
- @Rule
- public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
-
@Test
public void focusSearchFailureUp() throws Throwable {
focusSearchFailure(false);
@@ -179,100 +170,140 @@
}
}
+ /**
+ * Tests that the GridLayoutManager retains the focused element after multiple measure
+ * calls to the RecyclerView. There was a bug where the focused view was lost when the soft
+ * keyboard opened. This test simulates the measure/layout events triggered by the opening
+ * of the soft keyboard by making two calls to measure. A simulation was done because using
+ * the soft keyboard in the test caused many issues on API levels 15, 17 and 19.
+ */
@Test
- public void editTextVisibility() throws Throwable {
+ public void focusedChildStaysInViewWhenRecyclerViewShrinks() throws Throwable {
+
+ // Arrange.
+
final int spanCount = 3;
final int itemCount = 100;
- imeCleanUp.setup(getActivity(), getInstrumentation());
- RecyclerView recyclerView = new WrappedRecyclerView(getActivity());
- GridEditTextAdapter editTextAdapter = new GridEditTextAdapter(itemCount) {
- @Override
- public TestViewHolder onCreateViewHolder(ViewGroup parent,
- int viewType) {
- TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
- // Good to have colors for debugging
- StateListDrawable stl = new StateListDrawable();
- stl.addState(new int[]{android.R.attr.state_focused},
- new ColorDrawable(Color.RED));
- stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
- //noinspection deprecation using this for kitkat tests
- testViewHolder.itemView.setBackgroundDrawable(stl);
- return testViewHolder;
- }
- };
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
- }
- });
-
- recyclerView.setLayoutParams(
- new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ final RecyclerView recyclerView = inflateWrappedRV();
+ ViewGroup.LayoutParams lp = recyclerView.getLayoutParams();
+ lp.height = WRAP_CONTENT;
+ lp.width = MATCH_PARENT;
Config config = new Config(spanCount, itemCount);
mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
config.mReverseLayout);
- editTextAdapter.assignSpanSizeLookup(mGlm);
- recyclerView.setAdapter(editTextAdapter);
recyclerView.setLayoutManager(mGlm);
- waitForFirstLayout(recyclerView);
- // First focus on the last fully visible EditText located at span index #1.
- View toFocus = findLastFullyVisibleChild(mRecyclerView);
- int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
- focusIndex = (focusIndex / spanCount) * spanCount + 1;
- toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
- assertTrue(focusIndex >= 1 && focusIndex < itemCount);
+ GridFocusableAdapter gridFocusableAdapter = new GridFocusableAdapter(itemCount);
+ gridFocusableAdapter.assignSpanSizeLookup(mGlm);
+ recyclerView.setAdapter(gridFocusableAdapter);
- final int heightBeforeImeOpen = mRecyclerView.getHeight();
- TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
- getInstrumentation().waitForIdleSync();
- // Wait for IME to pop up.
- PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ mGlm.expectLayout(1);
+ mActivityRule.runOnUiThread(new Runnable() {
@Override
- public boolean canProceed() {
- return mRecyclerView.getHeight() < heightBeforeImeOpen;
+ public void run() {
+ getActivity().getContainer().addView(recyclerView);
}
});
+ mGlm.waitForLayout(3);
+
+ int width = recyclerView.getWidth();
+ int height = recyclerView.getHeight();
+ final int widthMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ final int fullHeightMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
+ // "MinusOne" so that a measure call will appropriately trigger onMeasure after RecyclerView
+ // was previously laid out with the full height version.
+ final int fullHeightMinusOneMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(height - 1, View.MeasureSpec.AT_MOST);
+ final int halfHeightMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(height / 2, View.MeasureSpec.AT_MOST);
+
+ // Act 1.
+
+ // First focus on the last fully visible child located at span index #1.
+ View toFocus = findLastFullyVisibleChild(recyclerView);
+ int focusIndex = recyclerView.getChildAdapterPosition(toFocus);
+ focusIndex = (focusIndex / spanCount) * spanCount + 1;
+ toFocus = recyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+ assertTrue(focusIndex >= 1 && focusIndex < itemCount);
+
+ requestFocus(toFocus, false);
+
+ mGlm.expectLayout(1);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+ recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+ recyclerView.layout(
+ 0,
+ 0,
+ recyclerView.getMeasuredWidth(),
+ recyclerView.getMeasuredHeight());
+ }
+ });
+ mGlm.waitForLayout(3);
+
+ // Assert 1.
assertThat("Child at position " + focusIndex + " should be focused",
toFocus.hasFocus(), is(true));
assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
- isViewPartiallyInBound(mRecyclerView, toFocus));
+ isViewPartiallyInBound(recyclerView, toFocus));
- // Close IME
- final int heightBeforeImeClose = mRecyclerView.getHeight();
- getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
- getInstrumentation().waitForIdleSync();
- // Wait for IME to close
- PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ // Act 2.
+
+ mGlm.expectLayout(1);
+ mActivityRule.runOnUiThread(new Runnable() {
@Override
- public boolean canProceed() {
- return mRecyclerView.getHeight() > heightBeforeImeClose;
+ public void run() {
+ recyclerView.measure(widthMeasureSpec, fullHeightMeasureSpec);
+ recyclerView.layout(
+ 0,
+ 0,
+ recyclerView.getMeasuredWidth(),
+ recyclerView.getMeasuredHeight());
}
});
+ mGlm.waitForLayout(3);
+
+ // Assert 2.
+
assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
- isViewPartiallyInBound(mRecyclerView, toFocus));
+ isViewPartiallyInBound(recyclerView, toFocus));
+
+ // Act 3.
// Now focus on the first fully visible EditText located at the last span index.
- toFocus = findFirstFullyVisibleChild(mRecyclerView);
- focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+ toFocus = findFirstFullyVisibleChild(recyclerView);
+ focusIndex = recyclerView.getChildAdapterPosition(toFocus);
focusIndex = (focusIndex / spanCount) * spanCount + (spanCount - 1);
- toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
- final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
- TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
- getInstrumentation().waitForIdleSync();
- // Wait for IME to pop up
- PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ toFocus = recyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+
+ requestFocus(toFocus, false);
+
+ mGlm.expectLayout(1);
+ mActivityRule.runOnUiThread(new Runnable() {
@Override
- public boolean canProceed() {
- return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+ public void run() {
+ recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+ recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+ recyclerView.layout(
+ 0,
+ 0,
+ recyclerView.getMeasuredWidth(),
+ recyclerView.getMeasuredHeight());
}
});
+ mGlm.waitForLayout(3);
+
+ // Assert 3.
+
assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
- isViewPartiallyInBound(mRecyclerView, toFocus));
+ isViewPartiallyInBound(recyclerView, toFocus));
}
@Test
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
index 4dd0d8f..77585b5 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -20,7 +20,6 @@
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
@@ -37,19 +36,14 @@
import android.os.Build;
import android.support.test.filters.LargeTest;
import android.support.test.filters.SdkSuppress;
-import android.support.testutils.PollingCheck;
import android.support.v4.view.AccessibilityDelegateCompat;
-import android.support.v7.util.ImeCleanUpTestRule;
-import android.support.v7.util.TouchUtils;
+import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.util.StateSet;
-import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
-import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -69,68 +63,76 @@
@LargeTest
public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
- @Rule
- public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
-
+ /**
+ * Tests that the LinearLayoutManager retains the focused element after multiple measure
+ * calls to the RecyclerView. There was a bug where the focused view was lost when the soft
+ * keyboard opened. This test simulates the measure/layout events triggered by the opening
+ * of the soft keyboard by making two calls to measure. A simulation was done because using
+ * the soft keyboard in the test caused many issues on API levels 15, 17 and 19.
+ */
@Test
- public void editTextVisibility() throws Throwable {
+ public void focusedChildStaysInViewWhenRecyclerViewShrinks() throws Throwable {
- // Simulating a scenario where an EditText is tapped (which will receive focus).
- // The soft keyboard that's opened overlaps the focused EditText which will shrink RV's
- // padded bounded area. LLM should still lay out the focused EditText so that it becomes
- // visible above the soft keyboard.
- // The condition for this test is setting RV's height to a non-exact height, so that measure
- // is called twice (once with the larger height and another time with smaller height when
- // the keyboard shows up). To ensure this resizing of RV, SOFT_INPUT_ADJUST_RESIZE is set.
- imeCleanUp.setup(getActivity(), getInstrumentation());
- final LinearLayout container = new LinearLayout(getActivity());
- container.setOrientation(LinearLayout.VERTICAL);
- container.setLayoutParams(
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
- .LayoutParams.MATCH_PARENT));
+ // Arrange.
- final EditTextAdapter editTextAdapter = new EditTextAdapter(50);
-
- mRecyclerView = inflateWrappedRV();
- ViewGroup.LayoutParams lp = mRecyclerView.getLayoutParams();
+ final RecyclerView recyclerView = inflateWrappedRV();
+ ViewGroup.LayoutParams lp = recyclerView.getLayoutParams();
lp.height = WRAP_CONTENT;
lp.width = MATCH_PARENT;
+ recyclerView.setHasFixedSize(true);
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setAdapter(editTextAdapter);
+ final FocusableAdapter focusableAdapter =
+ new FocusableAdapter(50);
+ recyclerView.setAdapter(focusableAdapter);
+
mLayoutManager = new WrappedLinearLayoutManager(getActivity(), VERTICAL, false);
- mRecyclerView.setLayoutManager(mLayoutManager);
-
- container.addView(mRecyclerView);
+ recyclerView.setLayoutManager(mLayoutManager);
mLayoutManager.expectLayouts(1);
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
- getActivity().getContainer().addView(container);
+ getActivity().getContainer().addView(recyclerView);
}
});
+ mLayoutManager.waitForLayout(3);
+
+ int width = recyclerView.getWidth();
+ int height = recyclerView.getHeight();
+ final int widthMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ final int fullHeightMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
+ // "MinusOne" so that a measure call will appropriately trigger onMeasure after RecyclerView
+ // was previously laid out with the full height version.
+ final int fullHeightMinusOneMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(height - 1, View.MeasureSpec.AT_MOST);
+ final int halfHeightMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(height / 2, View.MeasureSpec.AT_MOST);
+
+ // Act 1.
+
+ View toFocus = findLastFullyVisibleChild(recyclerView);
+ int focusIndex = recyclerView.getChildAdapterPosition(toFocus);
+
+ requestFocus(toFocus, false);
+
+ mLayoutManager.expectLayouts(1);
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
- mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+ recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+ recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+ recyclerView.layout(
+ 0,
+ 0,
+ recyclerView.getMeasuredWidth(),
+ recyclerView.getMeasuredHeight());
}
});
+ mLayoutManager.waitForLayout(3);
- // First focus on the last fully visible EditText.
- View toFocus = findLastFullyVisibleChild(mRecyclerView);
- int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
-
- final int heightBeforeImeOpen = mRecyclerView.getHeight();
- TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
- getInstrumentation().waitForIdleSync();
- // Wait for IME to pop up.
- PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
- @Override
- public boolean canProceed() {
- return mRecyclerView.getHeight() < heightBeforeImeOpen;
- }
- });
+ // Verify 1.
assertThat("Child at position " + focusIndex + " should be focused",
toFocus.hasFocus(), is(true));
@@ -138,40 +140,59 @@
// requestRectangleOnScreen (inside bringPointIntoView) for the focused view with a rect
// containing the content area. This rect is guaranteed to be fully visible whereas a
// portion of TextView could be out of bounds.
- assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
- isViewPartiallyInBound(mRecyclerView, toFocus));
+ assertThat("Child view at adapter pos " + focusIndex + " should be fully visible.",
+ isViewPartiallyInBound(recyclerView, toFocus), is(true));
- // Close IME
- final int heightBeforeImeClose = mRecyclerView.getHeight();
- getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
- getInstrumentation().waitForIdleSync();
- // Wait for IME to close
- PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ // Act 2.
+
+ mLayoutManager.expectLayouts(1);
+ mActivityRule.runOnUiThread(new Runnable() {
@Override
- public boolean canProceed() {
- return mRecyclerView.getHeight() > heightBeforeImeClose;
+ public void run() {
+ recyclerView.measure(widthMeasureSpec, fullHeightMeasureSpec);
+ recyclerView.layout(
+ 0,
+ 0,
+ recyclerView.getMeasuredWidth(),
+ recyclerView.getMeasuredHeight());
}
});
+ mLayoutManager.waitForLayout(3);
+
+ // Verify 2.
+
assertThat("Child at position " + focusIndex + " should be focused",
toFocus.hasFocus(), is(true));
assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
- isViewPartiallyInBound(mRecyclerView, toFocus));
+ isViewPartiallyInBound(recyclerView, toFocus));
+
+ // Act 3.
// Now focus on the first fully visible EditText.
- toFocus = findFirstFullyVisibleChild(mRecyclerView);
- focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
- final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
- TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
- getInstrumentation().waitForIdleSync();
- // Wait for IME to pop up
- PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ toFocus = findFirstFullyVisibleChild(recyclerView);
+ focusIndex = recyclerView.getChildAdapterPosition(toFocus);
+
+ requestFocus(toFocus, false);
+
+ mLayoutManager.expectLayouts(1);
+ mActivityRule.runOnUiThread(new Runnable() {
@Override
- public boolean canProceed() {
- return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+ public void run() {
+ recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+ recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+ recyclerView.layout(
+ 0,
+ 0,
+ recyclerView.getMeasuredWidth(),
+ recyclerView.getMeasuredHeight());
}
});
+ mLayoutManager.waitForLayout(3);
+
+ // Assert 3.
+
assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
- isViewPartiallyInBound(mRecyclerView, toFocus));
+ isViewPartiallyInBound(recyclerView, toFocus));
}
@Test
@@ -592,7 +613,7 @@
@Override
public void onFocusChange(View v, boolean hasFocus) {
assertNull("Focus just got cleared and no children should be holding"
- + " focus now.", mRecyclerView.getFocusedChild());
+ + " focus now.", mRecyclerView.getFocusedChild());
try {
// Calling focusSearch should be a no-op here since even though there
// are unfocusable views down to scroll to, none of RV's children hold
@@ -771,6 +792,118 @@
mRecyclerView.getChildCount() <= childCount + 3 /*1 for removed view, 2 for its size*/);
}
+ void waitOneCycle() throws Throwable {
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ }
+ });
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
+ @Test
+ public void hiddenNoneRemoveViewAccessibility() throws Throwable {
+ final Config config = new Config();
+ int adapterSize = 1000;
+ final boolean[] firstItemSpecialSize = new boolean[] {false};
+ TestAdapter adapter = new TestAdapter(adapterSize) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+ if (!(lp instanceof ViewGroup.MarginLayoutParams)) {
+ lp = new ViewGroup.MarginLayoutParams(0, 0);
+ holder.itemView.setLayoutParams(lp);
+ }
+ ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
+ final int maxSize;
+ if (config.mOrientation == HORIZONTAL) {
+ maxSize = mRecyclerView.getWidth();
+ mlp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
+ } else {
+ maxSize = mRecyclerView.getHeight();
+ mlp.width = ViewGroup.MarginLayoutParams.MATCH_PARENT;
+ }
+
+ final int desiredSize;
+ if (position == 0 && firstItemSpecialSize[0]) {
+ desiredSize = maxSize / 3;
+ } else {
+ desiredSize = maxSize / 8;
+ }
+ if (config.mOrientation == HORIZONTAL) {
+ mlp.width = desiredSize;
+ } else {
+ mlp.height = desiredSize;
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position, List<Object> payloads) {
+ onBindViewHolder(holder, position);
+ }
+ };
+ adapter.setHasStableIds(false);
+ config.adapter(adapter);
+ setupByConfig(config, true);
+ final DummyItemAnimator itemAnimator = new DummyItemAnimator();
+ mRecyclerView.setItemAnimator(itemAnimator);
+
+ // push last item out by increasing first item's size
+ final int childBeingPushOut = mLayoutManager.getChildCount() - 1;
+ RecyclerView.ViewHolder itemViewHolder = mRecyclerView
+ .findViewHolderForAdapterPosition(childBeingPushOut);
+ final int originalAccessibility = ViewCompat.getImportantForAccessibility(
+ itemViewHolder.itemView);
+ assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == originalAccessibility
+ || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == originalAccessibility);
+
+ itemAnimator.expect(DummyItemAnimator.MOVE_START, 1);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ firstItemSpecialSize[0] = true;
+ mTestAdapter.notifyItemChanged(0, "XXX");
+ }
+ });
+ // wait till itemAnimator starts which will block itemView's accessibility
+ itemAnimator.waitFor(DummyItemAnimator.MOVE_START);
+ // RV Changes accessiblity after onMoveStart, so wait one more cycle.
+ waitOneCycle();
+ assertTrue(itemAnimator.getMovesAnimations().contains(itemViewHolder));
+ assertEquals(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+ ViewCompat.getImportantForAccessibility(itemViewHolder.itemView));
+
+ // notify Change again to run predictive animation.
+ mLayoutManager.expectLayouts(2);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTestAdapter.notifyItemChanged(0, "XXX");
+ }
+ });
+ mLayoutManager.waitForLayout(1);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ itemAnimator.endAnimations();
+ }
+ });
+ // scroll to the view being pushed out, it should get same view from cache as the item
+ // in adapter does not change.
+ smoothScrollToPosition(childBeingPushOut);
+ RecyclerView.ViewHolder itemViewHolder2 = mRecyclerView
+ .findViewHolderForAdapterPosition(childBeingPushOut);
+ assertSame(itemViewHolder, itemViewHolder2);
+ // the important for accessibility should be reset to YES/AUTO:
+ final int newAccessibility = ViewCompat.getImportantForAccessibility(
+ itemViewHolder.itemView);
+ assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == newAccessibility
+ || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == newAccessibility);
+ }
+
@Test
public void keepFocusOnRelayout() throws Throwable {
setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
diff --git a/wear/Android.mk b/wear/Android.mk
index 0ea7890..2be9bfa 100644
--- a/wear/Android.mk
+++ b/wear/Android.mk
@@ -42,7 +42,8 @@
android-support-core-ui \
android-support-percent \
android-support-v7-recyclerview \
- android-support-v4
+ android-support-v4 \
+ android-support-constraint-layout
LOCAL_STATIC_JAVA_LIBRARIES := \
prebuilt-com.google.android.wearable-stubs
LOCAL_JAR_EXCLUDE_FILES := none
diff --git a/wear/build.gradle b/wear/build.gradle
index 55320b9..4937619 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -13,12 +13,12 @@
api(project(":percent"))
api(project(":recyclerview-v7"))
- androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
- androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- provided fileTree(dir: 'wear_stubs', include: ['com.google.android.wearable-stubs.jar'])
+ compileOnly fileTree(dir: 'wear_stubs', include: ['com.google.android.wearable-stubs.jar'])
}
android {