Merge tag android-5.1.0_r1 into AOSP_5.1_MERGE
Change-Id: Iba7bb4a34fc51030e432e9ebef756aa4c01be76e
diff --git a/build.gradle b/build.gradle
index 79896f5..25e72a4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,10 +9,11 @@
}
}
-ext.supportVersion = '21.0.0'
-ext.extraVersion = 7
+ext.supportVersion = '21.0.3'
+ext.extraVersion = 10
ext.supportRepoOut = ''
ext.buildToolsVersion = '19.0.3'
+ext.buildNumber = Integer.toString(ext.extraVersion)
/*
* With the build server you are given two env variables.
@@ -24,6 +25,9 @@
if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build').getCanonicalFile()
project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
+
+ // the build server does not pass the build number so we infer it from the last folder of the dist path.
+ ext.buildNumber = project.ext.distDir.getName()
} else {
buildDir = file('../../out/host/gradle/frameworks/support/build')
project.ext.distDir = file('../../out/dist')
@@ -46,7 +50,7 @@
from project.ext.supportRepoOut
destinationDir project.ext.distDir
into 'm2repository'
- baseName = String.format("android_m2repository_r%02d", project.ext.extraVersion)
+ baseName = String.format("sdk-repo-linux-m2repository-%s", project.ext.buildNumber)
}
createArchive.dependsOn createRepository
@@ -85,7 +89,7 @@
<sdk:name-display>Local Maven repository for Support Libraries</sdk:name-display>\n\
<sdk:path>m2repository</sdk:path>\n\
<sdk:archives>\n\
- <sdk:archive os=\"any\" arch=\"any\">\n\
+ <sdk:archive>\n\
<sdk:size>${size}</sdk:size>\n\
<sdk:checksum type=\"sha1\">${sha1}</sdk:checksum>\n\
<sdk:url>${repoArchiveName}</sdk:url>\n\
diff --git a/tests/java/android/support/v4/app/NotificationCompatActionWearableExtenderTest.java b/tests/java/android/support/v4/app/NotificationCompatActionWearableExtenderTest.java
new file mode 100644
index 0000000..ea67375
--- /dev/null
+++ b/tests/java/android/support/v4/app/NotificationCompatActionWearableExtenderTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.tests.R;
+import android.test.AndroidTestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link android.support.v4.app.NotificationCompat.Action.WearableExtender}.
+ */
+public class NotificationCompatActionWearableExtenderTest extends AndroidTestCase {
+
+ private int mIcon;
+ private String mTitle = "Test Title";
+ private PendingIntent mPendingIntent;
+
+ private String mInProgress = "In Progress Label";
+ private String mConfirm = "Confirmation Label";
+ private String mCancel = "Cancelation Label";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mIcon = R.drawable.action_icon;
+ mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
+ }
+
+ // Test that the default empty Extender is equal to the compat version.
+ public void testEmptyEquals() throws Exception {
+ assertExtendersEqual(new Notification.Action.WearableExtender(),
+ new NotificationCompat.Action.WearableExtender());
+ }
+
+ // Test that the fully populated Extender is equal to the compat version.
+ public void testFullEquals() throws Exception {
+ Notification.Action.WearableExtender baseExtender =
+ new Notification.Action.WearableExtender()
+ .setAvailableOffline(true)
+ .setInProgressLabel(mInProgress)
+ .setConfirmLabel(mConfirm)
+ .setCancelLabel(mCancel);
+ NotificationCompat.Action.WearableExtender compatExtender =
+ new NotificationCompat.Action.WearableExtender()
+ .setAvailableOffline(true)
+ .setInProgressLabel(mInProgress)
+ .setConfirmLabel(mConfirm)
+ .setCancelLabel(mCancel);
+ assertExtendersEqual(baseExtender, compatExtender);
+ }
+
+ // Test that the base WearableExtender from an empty Notification is equal to the compat.
+ public void testEmptyNotification() throws Exception {
+ Notification baseNotif = new Notification.Builder(getContext())
+ .build();
+ Notification compatNotif = new NotificationCompat.Builder(getContext())
+ .build();
+
+ assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+ assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+ assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+ assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+ }
+
+ public void testDefaultActionNotification() throws Exception {
+ Notification.Action.Builder baseAction =
+ new Notification.Action.Builder(mIcon, mTitle, mPendingIntent);
+ NotificationCompat.Action.Builder compatAction =
+ new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent);
+
+ Notification.WearableExtender baseNoteExtender =
+ new Notification.WearableExtender()
+ .addAction(baseAction.build());
+ NotificationCompat.WearableExtender compatNoteExtender =
+ new NotificationCompat.WearableExtender()
+ .addAction(compatAction.build());
+
+ Notification baseNotif = new Notification.Builder(getContext())
+ .extend(baseNoteExtender).build();
+ Notification compatNotif = new NotificationCompat.Builder(getContext())
+ .extend(compatNoteExtender).build();
+
+ assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+ assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+ assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+ assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+ }
+
+ public void testDefaultActionExtenderNotification() throws Exception {
+ Notification.Action.WearableExtender baseExtender =
+ new Notification.Action.WearableExtender();
+ NotificationCompat.Action.WearableExtender compatExtender =
+ new NotificationCompat.Action.WearableExtender();
+
+ Notification.Action.Builder baseAction =
+ new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(baseExtender);
+ NotificationCompat.Action.Builder compatAction =
+ new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(compatExtender);
+
+ Notification.WearableExtender baseNoteExtender =
+ new Notification.WearableExtender()
+ .addAction(baseAction.build());
+ NotificationCompat.WearableExtender compatNoteExtender =
+ new NotificationCompat.WearableExtender()
+ .addAction(compatAction.build());
+
+ Notification baseNotif = new Notification.Builder(getContext())
+ .extend(baseNoteExtender).build();
+ Notification compatNotif = new NotificationCompat.Builder(getContext())
+ .extend(compatNoteExtender).build();
+
+ assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+ assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+ assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+ assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+ }
+
+ public void testFullNotification() throws Exception {
+ Notification.Action.WearableExtender baseExtender =
+ new Notification.Action.WearableExtender()
+ .setAvailableOffline(true)
+ .setInProgressLabel(mInProgress)
+ .setConfirmLabel(mConfirm)
+ .setCancelLabel(mCancel);
+ NotificationCompat.Action.WearableExtender compatExtender =
+ new NotificationCompat.Action.WearableExtender()
+ .setAvailableOffline(true)
+ .setInProgressLabel(mInProgress)
+ .setConfirmLabel(mConfirm)
+ .setCancelLabel(mCancel);
+
+ Notification.Action.Builder baseAction =
+ new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(baseExtender);
+ NotificationCompat.Action.Builder compatAction =
+ new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(compatExtender);
+
+ Notification.WearableExtender baseNoteExtender =
+ new Notification.WearableExtender()
+ .addAction(baseAction.build());
+ NotificationCompat.WearableExtender compatNoteExtender =
+ new NotificationCompat.WearableExtender()
+ .addAction(compatAction.build());
+
+ Notification baseNotif = new Notification.Builder(getContext())
+ .extend(baseNoteExtender).build();
+ Notification compatNotif = new NotificationCompat.Builder(getContext())
+ .extend(compatNoteExtender).build();
+
+ assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+ assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+ assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+ assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+ }
+
+ public void testMultipleActionsInANotification() throws Exception {
+ Notification.Action.WearableExtender baseExtender1 =
+ new Notification.Action.WearableExtender()
+ .setAvailableOffline(true)
+ .setInProgressLabel(mInProgress)
+ .setConfirmLabel(mConfirm)
+ .setCancelLabel(mCancel);
+ NotificationCompat.Action.WearableExtender compatExtender1 =
+ new NotificationCompat.Action.WearableExtender()
+ .setAvailableOffline(true)
+ .setInProgressLabel(mInProgress)
+ .setConfirmLabel(mConfirm)
+ .setCancelLabel(mCancel);
+
+ Notification.Action.Builder baseAction1 =
+ new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(baseExtender1);
+ NotificationCompat.Action.Builder compatAction1 =
+ new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(compatExtender1);
+
+ Notification.Action.WearableExtender baseExtender2 =
+ new Notification.Action.WearableExtender()
+ .setAvailableOffline(false)
+ .setInProgressLabel("Alternate Label")
+ .setConfirmLabel("Duplicated Label")
+ .setCancelLabel("Duplicated Label");
+ NotificationCompat.Action.WearableExtender compatExtender2 =
+ new NotificationCompat.Action.WearableExtender()
+ .setAvailableOffline(false)
+ .setInProgressLabel("Alternate Label")
+ .setConfirmLabel("Duplicated Label")
+ .setCancelLabel("Duplicated Label");
+
+ Notification.Action.Builder baseAction2 =
+ new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(baseExtender2);
+ NotificationCompat.Action.Builder compatAction2 =
+ new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+ .extend(compatExtender2);
+
+ Notification.WearableExtender baseNoteExtender =
+ new Notification.WearableExtender()
+ .addAction(baseAction1.build())
+ .addAction(new Notification.Action(R.drawable.action_icon2, "Action1",
+ mPendingIntent))
+ .addAction(baseAction2.build());
+ NotificationCompat.WearableExtender compatNoteExtender =
+ new NotificationCompat.WearableExtender()
+ .addAction(compatAction1.build())
+ .addAction(new NotificationCompat.Action(R.drawable.action_icon2,
+ "Action1", mPendingIntent))
+ .addAction(compatAction2.build());
+
+ Notification baseNotif = new Notification.Builder(getContext())
+ .extend(baseNoteExtender).build();
+ Notification compatNotif = new NotificationCompat.Builder(getContext())
+ .extend(compatNoteExtender).build();
+
+ assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+ assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+ assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+ assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+ }
+
+ private void assertExtendersEqual(Notification.Action.WearableExtender base,
+ NotificationCompat.Action.WearableExtender compat) {
+ assertEquals(base.isAvailableOffline(), compat.isAvailableOffline());
+ assertEquals(base.getInProgressLabel(), compat.getInProgressLabel());
+ assertEquals(base.getConfirmLabel(), compat.getConfirmLabel());
+ assertEquals(base.getCancelLabel(), compat.getCancelLabel());
+ }
+
+ // Parse the Notification using the base parser and the compat parser and confirm
+ // that the WearableExtender bundles are equivelent.
+ private void assertExtendersFromNotificationEqual(Notification first,
+ Notification second) {
+ Notification.WearableExtender baseExtender = new Notification.WearableExtender(first);
+ NotificationCompat.WearableExtender compatExtender =
+ new NotificationCompat.WearableExtender(second);
+ List<Notification.Action> baseArray = baseExtender.getActions();
+ List<NotificationCompat.Action> compatArray = compatExtender.getActions();
+ assertEquals(baseArray.size(), compatArray.size());
+ for (int i = 0; i < baseArray.size(); i++) {
+ // Verify that the key value pairs are equal. We only care about
+ // the bundle in getExtras().getBundle("android.wearable.EXTENSIONS"),
+ // but it doesn't hurt to check them all as long we recurse.
+ assertBundlesEqual(baseArray.get(i).getExtras(),
+ compatArray.get(i).getExtras());
+ // Verify that the parsed WearableExtentions are equal
+ Notification.Action.WearableExtender base =
+ new Notification.Action.WearableExtender(baseArray.get(i));
+ NotificationCompat.Action.WearableExtender compat =
+ new NotificationCompat.Action.WearableExtender(compatArray.get(i));
+ assertExtendersEqual(base, compat);
+ }
+ }
+
+ private void assertBundlesEqual(Bundle bundle1, Bundle bundle2) {
+ assertEquals(bundle1.size(), bundle2.size());
+ for (String key : bundle1.keySet()) {
+ Object value1 = bundle1.get(key);
+ Object value2 = bundle2.get(key);
+ if (value1 instanceof Bundle && value2 instanceof Bundle) {
+ assertBundlesEqual((Bundle) value1, (Bundle) value2);
+ } else {
+ assertEquals(value1, value2);
+ }
+ }
+ }
+}
diff --git a/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java b/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
index c6fa124..2a988ed 100644
--- a/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
+++ b/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
@@ -84,7 +84,10 @@
R.drawable.action_icon, "Test title", mPendingIntent)
.addRemoteInput(remoteInput.build())
.extend(new NotificationCompat.Action.WearableExtender()
- .setAvailableOffline(false));
+ .setAvailableOffline(false)
+ .setInProgressLabel("In Progress Label")
+ .setConfirmLabel("Confirmation Label")
+ .setCancelLabel("Cancelation Label"));
// Add an arbitrary key/value.
action2.getExtras().putFloat("action_float", 10.5f);
@@ -129,7 +132,10 @@
R.drawable.action_icon, "Test title", mPendingIntent)
.addRemoteInput(remoteInput.build())
.extend(new Notification.Action.WearableExtender()
- .setAvailableOffline(false));
+ .setAvailableOffline(false)
+ .setInProgressLabel("In Progress Label")
+ .setConfirmLabel("Confirmation Label")
+ .setCancelLabel("Cancelation Label"));
// Add an arbitrary key/value.
action2.getExtras().putFloat("action_float", 10.5f);
@@ -242,7 +248,13 @@
private void assertBundlesEqual(Bundle bundle1, Bundle bundle2) {
assertEquals(bundle1.size(), bundle2.size());
for (String key : bundle1.keySet()) {
- assertEquals(bundle1.get(key), bundle2.get(key));
+ Object value1 = bundle1.get(key);
+ Object value2 = bundle2.get(key);
+ if (value1 instanceof Bundle && value2 instanceof Bundle) {
+ assertBundlesEqual((Bundle) value1, (Bundle) value2);
+ } else {
+ assertEquals(value1, value2);
+ }
}
}
}
diff --git a/tests/java/android/support/v4/content/FileProviderTest.java b/tests/java/android/support/v4/content/FileProviderTest.java
index b7bd01c..f8122fa 100644
--- a/tests/java/android/support/v4/content/FileProviderTest.java
+++ b/tests/java/android/support/v4/content/FileProviderTest.java
@@ -26,6 +26,10 @@
import android.support.v4.content.FileProvider.SimplePathStrategy;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.Suppress;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
import java.io.File;
import java.io.FileNotFoundException;
@@ -33,12 +37,10 @@
import java.io.InputStream;
import java.io.OutputStream;
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
/**
* Tests for {@link FileProvider}
*/
+@Suppress
public class FileProviderTest extends AndroidTestCase {
private static final String TEST_AUTHORITY = "moocow";
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index f64ca8e..1574cb7 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -43,7 +43,7 @@
# A helper sub-library that makes direct use of API 21.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v17-leanback-api21
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, api21)
LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -93,6 +93,7 @@
$(call all-java-files-under, src) \
$(call all-html-files-under, src)
leanback.docs.java_libraries := \
+ framework \
android-support-v4 \
android-support-v7-recyclerview \
android-support-v17-leanback-res \
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java b/v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java
new file mode 100644
index 0000000..469847f
--- /dev/null
+++ b/v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.content.Context;
+import android.transition.Slide;
+import android.util.AttributeSet;
+
+public class SlideNoPropagation extends Slide {
+
+ public SlideNoPropagation() {
+ }
+
+ public SlideNoPropagation(int slideEdge) {
+ super(slideEdge);
+ }
+
+ public SlideNoPropagation(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setSlideEdge(int slideEdge) {
+ super.setSlideEdge(slideEdge);
+ setPropagation(null);
+ }
+}
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
index 44c88d8..4de735a 100644
--- a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
+++ b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
@@ -52,6 +52,7 @@
shadowContainer.setOutlineProvider(sOutlineProvider);
}
shadowContainer.setZ(sNormalZ);
+ shadowContainer.setTransitionGroup(true);
return shadowContainer;
}
diff --git a/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java b/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java
deleted file mode 100644
index 30d258a..0000000
--- a/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.transition;
-
-import android.view.View;
-
-/**
- * Used by Slide to determine the slide edge and distance when it is about to
- * create animator.
- * @hide
- */
-public interface SlideCallback {
-
- /**
- * Called when Slide is about to create animator for an appearing/disappearing view.
- * Callback returns true to ask Slide to create animator, edge is returned
- * in edge[0], distance in pixels is returned in distance[0]. Slide will not
- * create animator if callback returns false.
- */
- public boolean getSlide(View view, boolean appear, int[] edge, float[] distance);
-
-}
diff --git a/v17/leanback/generatev4.py b/v17/leanback/generatev4.py
new file mode 100755
index 0000000..58a727a
--- /dev/null
+++ b/v17/leanback/generatev4.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+print "Generate v4 fragment related code for leanback"
+
+cls = ['Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
+ 'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid']
+
+for w in cls:
+ print "copy {}Fragment to {}SupportFragment".format(w, w)
+
+ file = open('src/android/support/v17/leanback/app/{}Fragment.java'.format(w), 'r')
+ outfile = open('src/android/support/v17/leanback/app/{}SupportFragment.java'.format(w), 'w')
+
+ outfile.write("/* This file is auto-generated from {}Fragment.java. DO NOT MODIFY. */\n\n".format(w))
+
+ for line in file:
+ for w in cls:
+ line = line.replace('{}Fragment'.format(w), '{}SupportFragment'.format(w))
+ line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
+ line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
+ outfile.write(line)
+ file.close()
+ outfile.close()
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
new file mode 100644
index 0000000..0cc9081
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.view.Gravity;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+class LeanbackTransitionHelperKitKat {
+
+ static public Object loadTitleInTransition(Context context) {
+ SlideKitkat slide = new SlideKitkat();
+ slide.setSlideEdge(Gravity.TOP);
+ slide.setInterpolator(AnimationUtils.loadInterpolator(context,
+ android.R.anim.decelerate_interpolator));
+ slide.addTarget(R.id.browse_title_group);
+ return slide;
+ }
+
+ static public Object loadTitleOutTransition(Context context) {
+ SlideKitkat slide = new SlideKitkat();
+ slide.setSlideEdge(Gravity.TOP);
+ slide.setInterpolator(AnimationUtils.loadInterpolator(context,
+ R.animator.lb_decelerator_4));
+ slide.addTarget(R.id.browse_title_group);
+ return slide;
+ }
+
+}
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
index 2bdc3aa..28acbbd 100644
--- a/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
@@ -22,6 +22,9 @@
import android.transition.Transition;
import android.transition.TransitionValues;
+/**
+ * @hide
+ */
class Scale extends Transition {
private static final String PROPNAME_SCALE = "android:leanback:scale";
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java
deleted file mode 100644
index 82c72cf..0000000
--- a/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.transition;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.util.Log;
-import android.util.Property;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.transition.Visibility;
-import android.transition.Transition;
-import android.transition.TransitionValues;
-import android.support.v17.leanback.R;
-
-/**
- * Slide distance toward/from a edge. The direction and distance are determined by
- * {@link SlideCallback}.
- */
-class Slide extends Visibility {
- private static final String TAG = "Slide";
-
- /**
- * Move Views in or out of the left edge of the scene.
- * @see #setSlideEdge(int)
- */
- public static final int LEFT = 0;
-
- /**
- * Move Views in or out of the top edge of the scene.
- * @see #setSlideEdge(int)
- */
- public static final int TOP = 1;
-
- /**
- * Move Views in or out of the right edge of the scene.
- * @see #setSlideEdge(int)
- */
- public static final int RIGHT = 2;
-
- /**
- * Move Views in or out of the bottom edge of the scene. This is the
- * default slide direction.
- * @see #setSlideEdge(int)
- */
- public static final int BOTTOM = 3;
-
- private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
- private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
-
- private int[] mTempLoc = new int[2];
- SlideCallback mCallback;
- private int[] mTempEdge = new int[1];
- private float[] mTempDistance = new float[1];
-
- private interface CalculateSlide {
- /** Returns the translation value for view when it out of the scene */
- float getGone(float slide, View view);
-
- /** Returns the translation value for view when it is in the scene */
- float getHere(View view);
-
- /** Returns the property to animate translation */
- Property<View, Float> getProperty();
- }
-
- private static abstract class CalculateSlideHorizontal implements CalculateSlide {
- @Override
- public float getHere(View view) {
- return view.getTranslationX();
- }
-
- @Override
- public Property<View, Float> getProperty() {
- return View.TRANSLATION_X;
- }
- }
-
- private static abstract class CalculateSlideVertical implements CalculateSlide {
- @Override
- public float getHere(View view) {
- return view.getTranslationY();
- }
-
- @Override
- public Property<View, Float> getProperty() {
- return View.TRANSLATION_Y;
- }
- }
-
- private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
- @Override
- public float getGone(float distance, View view) {
- return view.getTranslationX() - distance;
- }
- };
-
- private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
- @Override
- public float getGone(float distance, View view) {
- return view.getTranslationY() - distance;
- }
- };
-
- private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
- @Override
- public float getGone(float distance, View view) {
- return view.getTranslationX() + distance;
- }
- };
-
- private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
- @Override
- public float getGone(float distance, View view) {
- return view.getTranslationY() + distance;
- }
- };
-
- public Slide() {
- }
-
- public void setCallback(SlideCallback callback) {
- mCallback = callback;
- }
-
- private CalculateSlide getSlideEdge(int slideEdge) {
- switch (slideEdge) {
- case LEFT:
- return sCalculateLeft;
- case TOP:
- return sCalculateTop;
- case RIGHT:
- return sCalculateRight;
- case BOTTOM:
- return sCalculateBottom;
- default:
- throw new IllegalArgumentException("Invalid slide direction");
- }
- }
-
- private Animator createAnimation(final View view, Property<View, Float> property,
- float start, float end, float terminalValue, TimeInterpolator interpolator,
- int finalVisibility) {
- float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value);
- if (startPosition != null) {
- start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0];
- view.setTag(R.id.lb_slide_transition_value, null);
- }
- final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
-
- SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end,
- finalVisibility);
- anim.addListener(listener);
- anim.addPauseListener(listener);
- anim.setInterpolator(interpolator);
- return anim;
- }
-
- @Override
- public Animator onAppear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
- View view = (endValues != null) ? endValues.view : null;
- if (view == null) {
- return null;
- }
- if (mCallback == null || !mCallback.getSlide(view, true, mTempEdge, mTempDistance)) {
- return null;
- }
- final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
- float end = slideCalculator.getHere(view);
- float start = slideCalculator.getGone(mTempDistance[0], view);
- return createAnimation(view, slideCalculator.getProperty(), start, end, end, sDecelerate,
- View.VISIBLE);
- }
-
- @Override
- public Animator onDisappear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
- View view = (startValues != null) ? startValues.view : null;
- if (view == null) {
- return null;
- }
- if (mCallback == null || !mCallback.getSlide(view, false, mTempEdge, mTempDistance)) {
- return null;
- }
- final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
- float start = slideCalculator.getHere(view);
- float end = slideCalculator.getGone(mTempDistance[0], view);
-
- return createAnimation(view, slideCalculator.getProperty(), start, end, start,
- sAccelerate, View.INVISIBLE);
- }
-
- private static class SlideAnimatorListener extends AnimatorListenerAdapter {
- private boolean mCanceled = false;
- private float mPausedValue;
- private final View mView;
- private final float mEndValue;
- private final float mTerminalValue;
- private final int mFinalVisibility;
- private final Property<View, Float> mProp;
-
- public SlideAnimatorListener(View view, Property<View, Float> prop,
- float terminalValue, float endValue, int finalVisibility) {
- mProp = prop;
- mView = view;
- mTerminalValue = terminalValue;
- mEndValue = endValue;
- mFinalVisibility = finalVisibility;
- view.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationCancel(Animator animator) {
- float[] transitionPosition = new float[2];
- transitionPosition[0] = mView.getTranslationX();
- transitionPosition[1] = mView.getTranslationY();
- mView.setTag(R.id.lb_slide_transition_value, transitionPosition);
- mProp.set(mView, mTerminalValue);
- mCanceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- if (!mCanceled) {
- mProp.set(mView, mTerminalValue);
- }
- mView.setVisibility(mFinalVisibility);
- }
-
- @Override
- public void onAnimationPause(Animator animator) {
- mPausedValue = mProp.get(mView);
- mProp.set(mView, mEndValue);
- mView.setVisibility(mFinalVisibility);
- }
-
- @Override
- public void onAnimationResume(Animator animator) {
- mProp.set(mView, mPausedValue);
- mView.setVisibility(View.VISIBLE);
- }
- }
-}
\ No newline at end of file
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java
new file mode 100644
index 0000000..a1f2d63
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Property;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.transition.Visibility;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+import android.support.v17.leanback.R;
+
+/**
+ * Slide distance toward/from a edge.
+ * This is a limited Slide implementation for KitKat without propagation support.
+ * @hide
+ */
+class SlideKitkat extends Visibility {
+ private static final String TAG = "SlideKitkat";
+
+ private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+ private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+
+ private int mSlideEdge;
+ private CalculateSlide mSlideCalculator;
+
+ private interface CalculateSlide {
+ /** Returns the translation value for view when it out of the scene */
+ float getGone(View view);
+
+ /** Returns the translation value for view when it is in the scene */
+ float getHere(View view);
+
+ /** Returns the property to animate translation */
+ Property<View, Float> getProperty();
+ }
+
+ private static abstract class CalculateSlideHorizontal implements CalculateSlide {
+ @Override
+ public float getHere(View view) {
+ return view.getTranslationX();
+ }
+
+ @Override
+ public Property<View, Float> getProperty() {
+ return View.TRANSLATION_X;
+ }
+ }
+
+ private static abstract class CalculateSlideVertical implements CalculateSlide {
+ @Override
+ public float getHere(View view) {
+ return view.getTranslationY();
+ }
+
+ @Override
+ public Property<View, Float> getProperty() {
+ return View.TRANSLATION_Y;
+ }
+ }
+
+ private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(View view) {
+ return view.getTranslationX() - view.getWidth();
+ }
+ };
+
+ private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
+ @Override
+ public float getGone(View view) {
+ return view.getTranslationY() - view.getHeight();
+ }
+ };
+
+ private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(View view) {
+ return view.getTranslationX() + view.getWidth();
+ }
+ };
+
+ private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
+ @Override
+ public float getGone(View view) {
+ return view.getTranslationY() + view.getHeight();
+ }
+ };
+
+ private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(View view) {
+ if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ return view.getTranslationX() + view.getWidth();
+ } else {
+ return view.getTranslationX() - view.getWidth();
+ }
+ }
+ };
+
+ private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(View view) {
+ if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ return view.getTranslationX() - view.getWidth();
+ } else {
+ return view.getTranslationX() + view.getWidth();
+ }
+ }
+ };
+
+ public SlideKitkat() {
+ setSlideEdge(Gravity.BOTTOM);
+ }
+
+ public SlideKitkat(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
+ int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.BOTTOM);
+ setSlideEdge(edge);
+ long duration = a.getInt(R.styleable.lbSlide_android_duration, -1);
+ if (duration >= 0) {
+ setDuration(duration);
+ }
+ long startDelay = a.getInt(R.styleable.lbSlide_android_startDelay, -1);
+ if (startDelay > 0) {
+ setStartDelay(startDelay);
+ }
+ final int resID = a.getResourceId(R.styleable.lbSlide_android_interpolator, 0);
+ if (resID > 0) {
+ setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+ }
+ a.recycle();
+ }
+
+ /**
+ * Change the edge that Views appear and disappear from.
+ *
+ * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
+ * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
+ * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+ * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
+ */
+ public void setSlideEdge(int slideEdge) {
+ switch (slideEdge) {
+ case Gravity.LEFT:
+ mSlideCalculator = sCalculateLeft;
+ break;
+ case Gravity.TOP:
+ mSlideCalculator = sCalculateTop;
+ break;
+ case Gravity.RIGHT:
+ mSlideCalculator = sCalculateRight;
+ break;
+ case Gravity.BOTTOM:
+ mSlideCalculator = sCalculateBottom;
+ break;
+ case Gravity.START:
+ mSlideCalculator = sCalculateStart;
+ break;
+ case Gravity.END:
+ mSlideCalculator = sCalculateEnd;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid slide direction");
+ }
+ mSlideEdge = slideEdge;
+ }
+
+ /**
+ * Returns the edge that Views appear and disappear from.
+ * @return the edge of the scene to use for Views appearing and disappearing. One of
+ * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
+ * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+ * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
+ */
+ public int getSlideEdge() {
+ return mSlideEdge;
+ }
+
+ private Animator createAnimation(final View view, Property<View, Float> property,
+ float start, float end, float terminalValue, TimeInterpolator interpolator,
+ int finalVisibility) {
+ float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value);
+ if (startPosition != null) {
+ start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0];
+ view.setTag(R.id.lb_slide_transition_value, null);
+ }
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
+
+ SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end,
+ finalVisibility);
+ anim.addListener(listener);
+ anim.addPauseListener(listener);
+ anim.setInterpolator(interpolator);
+ return anim;
+ }
+
+ @Override
+ public Animator onAppear(ViewGroup sceneRoot,
+ TransitionValues startValues, int startVisibility,
+ TransitionValues endValues, int endVisibility) {
+ View view = (endValues != null) ? endValues.view : null;
+ if (view == null) {
+ return null;
+ }
+ float end = mSlideCalculator.getHere(view);
+ float start = mSlideCalculator.getGone(view);
+ return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate,
+ View.VISIBLE);
+ }
+
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot,
+ TransitionValues startValues, int startVisibility,
+ TransitionValues endValues, int endVisibility) {
+ View view = (startValues != null) ? startValues.view : null;
+ if (view == null) {
+ return null;
+ }
+ float start = mSlideCalculator.getHere(view);
+ float end = mSlideCalculator.getGone(view);
+
+ return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
+ sAccelerate, View.INVISIBLE);
+ }
+
+ private static class SlideAnimatorListener extends AnimatorListenerAdapter {
+ private boolean mCanceled = false;
+ private float mPausedValue;
+ private final View mView;
+ private final float mEndValue;
+ private final float mTerminalValue;
+ private final int mFinalVisibility;
+ private final Property<View, Float> mProp;
+
+ public SlideAnimatorListener(View view, Property<View, Float> prop,
+ float terminalValue, float endValue, int finalVisibility) {
+ mProp = prop;
+ mView = view;
+ mTerminalValue = terminalValue;
+ mEndValue = endValue;
+ mFinalVisibility = finalVisibility;
+ view.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ float[] transitionPosition = new float[2];
+ transitionPosition[0] = mView.getTranslationX();
+ transitionPosition[1] = mView.getTranslationY();
+ mView.setTag(R.id.lb_slide_transition_value, transitionPosition);
+ mProp.set(mView, mTerminalValue);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mCanceled) {
+ mProp.set(mView, mTerminalValue);
+ }
+ mView.setVisibility(mFinalVisibility);
+ }
+
+ @Override
+ public void onAnimationPause(Animator animator) {
+ mPausedValue = mProp.get(mView);
+ mProp.set(mView, mEndValue);
+ mView.setVisibility(mFinalVisibility);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animator) {
+ mProp.set(mView, mPausedValue);
+ mView.setVisibility(View.VISIBLE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
index 43ea462..b4b6abe 100644
--- a/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
@@ -21,6 +21,7 @@
import android.transition.Fade;
import android.transition.Scene;
import android.transition.Transition;
+import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.transition.TransitionValues;
@@ -58,9 +59,9 @@
return new AutoTransition();
}
- static Object createSlide(SlideCallback callback) {
- Slide slide = new Slide();
- slide.setCallback(callback);
+ static Object createSlide(int slideEdge) {
+ SlideKitkat slide = new SlideKitkat();
+ slide.setSlideEdge(slideEdge);
return slide;
}
@@ -223,4 +224,8 @@
static void addTarget(Object transition, View view) {
((Transition) transition).addTarget(view);
}
+
+ static Object loadTransition(Context context, int resId) {
+ return TransitionInflater.from(context).inflateTransition(resId);
+ }
}
diff --git a/v17/leanback/res/animator/lb_decelerator_4.xml b/v17/leanback/res/animator/lb_decelerator_4.xml
new file mode 100644
index 0000000..6273eef
--- /dev/null
+++ b/v17/leanback/res/animator/lb_decelerator_4.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:factor="4.0"/>
diff --git a/v17/leanback/res/layout/lb_browse_fragment.xml b/v17/leanback/res/layout/lb_browse_fragment.xml
index 8ea9a53..bc8ffc3 100644
--- a/v17/leanback/res/layout/lb_browse_fragment.xml
+++ b/v17/leanback/res/layout/lb_browse_fragment.xml
@@ -21,7 +21,7 @@
<!-- BrowseFrameLayout serves as root of transition and manages switch between
left and right-->
- <android.support.v17.leanback.app.BrowseFrameLayout
+ <android.support.v17.leanback.widget.BrowseFrameLayout
android:focusable="true"
android:focusableInTouchMode="true"
android:descendantFocusability="afterDescendants"
@@ -29,11 +29,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
>
- <android.support.v17.leanback.app.BrowseRowsFrameLayout
+ <android.support.v17.leanback.widget.BrowseRowsFrameLayout
android:id="@+id/browse_container_dock"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false" />
+ android:layout_height="match_parent" />
<!-- Padding needed for shadow -->
<FrameLayout
@@ -43,5 +42,5 @@
android:clipToPadding="false"
android:paddingEnd="50dp" />
<include layout="@layout/lb_browse_title" />
- </android.support.v17.leanback.app.BrowseFrameLayout>
+ </android.support.v17.leanback.widget.BrowseFrameLayout>
</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_browse_title.xml b/v17/leanback/res/layout/lb_browse_title.xml
index 75068d8..e14350a 100644
--- a/v17/leanback/res/layout/lb_browse_title.xml
+++ b/v17/leanback/res/layout/lb_browse_title.xml
@@ -19,8 +19,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="?attr/browsePaddingTop"
- android:paddingRight="?attr/browsePaddingRight"
- android:paddingLeft="?attr/browsePaddingLeft"
+ android:paddingEnd="?attr/browsePaddingEnd"
+ android:paddingStart="?attr/browsePaddingStart"
android:paddingBottom="?attr/browsePaddingTop"
style="?attr/browseTitleViewStyle" />
diff --git a/v17/leanback/res/layout/lb_control_button_primary.xml b/v17/leanback/res/layout/lb_control_button_primary.xml
index 86e3e0d..af94487 100644
--- a/v17/leanback/res/layout/lb_control_button_primary.xml
+++ b/v17/leanback/res/layout/lb_control_button_primary.xml
@@ -33,4 +33,11 @@
android:layout_height="@dimen/lb_control_icon_height"
android:layout_gravity="center" />
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ style="?attr/playbackControlButtonLabelStyle" />
+
</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_details_description.xml b/v17/leanback/res/layout/lb_details_description.xml
index 798504f..005cae4 100644
--- a/v17/leanback/res/layout/lb_details_description.xml
+++ b/v17/leanback/res/layout/lb_details_description.xml
@@ -23,7 +23,7 @@
>
<!-- Top margins set programatically -->
- <TextView
+ <android.support.v17.leanback.widget.ResizingTextView
android:id="@+id/lb_details_description_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/v17/leanback/res/layout/lb_details_overview.xml b/v17/leanback/res/layout/lb_details_overview.xml
index 1e4e0fc..dd73e76 100644
--- a/v17/leanback/res/layout/lb_details_overview.xml
+++ b/v17/leanback/res/layout/lb_details_overview.xml
@@ -19,8 +19,8 @@
xmlns:lb="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingLeft="@dimen/lb_details_overview_margin_left"
- android:paddingRight="@dimen/lb_details_overview_margin_right"
+ android:paddingStart="@dimen/lb_details_overview_margin_start"
+ android:paddingEnd="@dimen/lb_details_overview_margin_end"
android:paddingBottom="@dimen/lb_details_overview_margin_bottom"
android:clipToPadding="false"
>
@@ -59,12 +59,12 @@
<android.support.v17.leanback.widget.NonOverlappingFrameLayout
android:id="@+id/details_overview_description"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top"
- android:layout_marginLeft="@dimen/lb_details_overview_description_margin_left"
- android:layout_marginRight="@dimen/lb_details_overview_description_margin_right"
+ android:layout_marginStart="@dimen/lb_details_overview_description_margin_start"
+ android:layout_marginEnd="@dimen/lb_details_overview_description_margin_end"
android:paddingTop="@dimen/lb_details_overview_description_margin_top"
android:clipToPadding="false"
android:clipChildren="false"
@@ -79,8 +79,8 @@
android:clipToPadding="false"
android:focusable="true"
android:focusableInTouchMode="true"
- android:paddingLeft="@dimen/lb_details_overview_description_margin_left"
- android:paddingRight="@dimen/lb_details_overview_description_margin_right"
+ android:paddingStart="@dimen/lb_details_overview_description_margin_start"
+ android:paddingEnd="@dimen/lb_details_overview_description_margin_end"
lb:horizontalMargin="@dimen/lb_details_overview_action_items_margin"
lb:rowHeight="@dimen/lb_details_overview_actions_height" />
diff --git a/v17/leanback/res/layout/lb_headers_fragment.xml b/v17/leanback/res/layout/lb_headers_fragment.xml
index a621c0b..035b116 100644
--- a/v17/leanback/res/layout/lb_headers_fragment.xml
+++ b/v17/leanback/res/layout/lb_headers_fragment.xml
@@ -16,9 +16,11 @@
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/browse_headers_root"
+ android:transitionGroup="true"
android:layout_width="@dimen/lb_browse_headers_width"
android:layout_height="match_parent"
- android:paddingRight="@dimen/lb_browse_header_padding_right"
+ android:paddingEnd="@dimen/lb_browse_header_padding_end"
android:elevation="@dimen/lb_browse_headers_z"
>
<android.support.v17.leanback.widget.VerticalGridView
@@ -28,7 +30,7 @@
style="?attr/headersVerticalGridStyle"/>
<View
android:id="@+id/fade_out_edge"
- android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
android:layout_width="@dimen/lb_browse_header_fading_length"
android:layout_height="match_parent"
android:background="@drawable/lb_headers_right_fading" >
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/v17/leanback/res/layout/lb_image_card_view.xml
index f74085c..38cfd4b 100644
--- a/v17/leanback/res/layout/lb_image_card_view.xml
+++ b/v17/leanback/res/layout/lb_image_card_view.xml
@@ -43,7 +43,7 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_marginTop="@dimen/lb_basic_card_info_text_margin"
- android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+ android:layout_marginStart="@dimen/lb_basic_card_info_text_margin"
android:maxLines="1"
android:fontFamily="sans-serif-condensed"
android:textColor="@color/lb_basic_card_title_text_color"
@@ -53,9 +53,9 @@
android:id="@+id/content_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
- android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+ android:layout_marginStart="@dimen/lb_basic_card_info_text_margin"
android:layout_marginBottom="@dimen/lb_basic_card_info_text_margin"
android:maxLines="1"
android:fontFamily="sans-serif-condensed"
@@ -67,7 +67,7 @@
android:layout_width="@dimen/lb_basic_card_info_badge_size"
android:layout_height="@dimen/lb_basic_card_info_badge_size"
android:layout_alignParentBottom="true"
- android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
android:scaleType="fitCenter"
android:visibility="gone"
android:contentDescription="@null" />
diff --git a/v17/leanback/res/layout/lb_playback_controls.xml b/v17/leanback/res/layout/lb_playback_controls.xml
index e7ed643..b366215 100644
--- a/v17/leanback/res/layout/lb_playback_controls.xml
+++ b/v17/leanback/res/layout/lb_playback_controls.xml
@@ -27,6 +27,7 @@
android:layout_height="4dp"
android:maxHeight="4dp"
android:minHeight="4dp"
+ android:layoutDirection="ltr"
android:progressDrawable="@drawable/lb_playback_progress_bar" />
<FrameLayout
@@ -39,19 +40,20 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
+ android:layoutDirection="ltr"
android:orientation="horizontal" />
<FrameLayout
android:id="@+id/more_actions_dock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="right" />
+ android:layout_gravity="end" />
<TextView
android:id="@+id/current_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|start"
+ android:layout_gravity="top|left"
android:layout_marginStart="@dimen/lb_playback_current_time_margin_start"
android:paddingTop="@dimen/lb_playback_time_padding_top"
style="?attr/playbackControlsTimeStyle" />
@@ -60,7 +62,7 @@
android:id="@+id/total_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|end"
+ android:layout_gravity="top|right"
android:layout_marginEnd="@dimen/lb_playback_total_time_margin_end"
android:paddingTop="@dimen/lb_playback_time_padding_top"
style="?attr/playbackControlsTimeStyle" />
diff --git a/v17/leanback/res/layout/lb_playback_controls_row.xml b/v17/leanback/res/layout/lb_playback_controls_row.xml
index 2875b51..395dfd1 100644
--- a/v17/leanback/res/layout/lb_playback_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_controls_row.xml
@@ -16,7 +16,7 @@
-->
<!-- Note: clipChildren/clipToPadding false are needed to apply shadows to child
- views with no padding of their own. -->
+ views with no padding of their own. Also to allow for negative marge on description. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
@@ -24,8 +24,8 @@
android:orientation="vertical"
android:clipChildren="false"
android:clipToPadding="false"
- android:paddingLeft="@dimen/lb_playback_controls_margin_left"
- android:paddingRight="@dimen/lb_playback_controls_margin_right" >
+ android:paddingStart="@dimen/lb_playback_controls_margin_start"
+ android:paddingEnd="@dimen/lb_playback_controls_margin_end" >
<LinearLayout
android:id="@+id/controls_card"
@@ -56,7 +56,9 @@
android:layout_height="0dp"
android:layout_marginEnd="@dimen/lb_playback_description_margin_end"
android:layout_marginStart="@dimen/lb_playback_description_margin_start"
- android:layout_marginTop="@dimen/lb_playback_description_margin_top"
+ android:paddingTop="@dimen/lb_playback_description_margin_top"
+ android:clipToPadding="false"
+ android:clipChildren="false"
android:layout_weight="1"
android:gravity="top" />
@@ -71,6 +73,7 @@
android:id="@+id/controls_dock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:minHeight="@dimen/lb_control_button_height"
android:layout_marginEnd="@dimen/lb_playback_description_margin_end"
android:layout_marginStart="@dimen/lb_playback_description_margin_start" />
</LinearLayout>
diff --git a/v17/leanback/res/layout/lb_row_container.xml b/v17/leanback/res/layout/lb_row_container.xml
index 1ed9f83..69fb0e1 100644
--- a/v17/leanback/res/layout/lb_row_container.xml
+++ b/v17/leanback/res/layout/lb_row_container.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:paddingLeft="?attr/browsePaddingLeft"
+ android:paddingStart="?attr/browsePaddingStart"
android:clipToPadding="false">
</android.support.v17.leanback.widget.NonOverlappingLinearLayout>
</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_rows_fragment.xml b/v17/leanback/res/layout/lb_rows_fragment.xml
index c4ffdc3..c188b3c 100644
--- a/v17/leanback/res/layout/lb_rows_fragment.xml
+++ b/v17/leanback/res/layout/lb_rows_fragment.xml
@@ -14,10 +14,16 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<android.support.v17.leanback.widget.VerticalGridView
+<android.support.v17.leanback.widget.ScaleFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/container_list"
+ android:id="@+id/scale_frame"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- style="?attr/rowsVerticalGridStyle" />
+ android:layout_height="match_parent">
+ <android.support.v17.leanback.widget.VerticalGridView
+ android:id="@+id/container_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="?attr/rowsVerticalGridStyle" />
+
+</android.support.v17.leanback.widget.ScaleFrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/v17/leanback/res/layout/lb_search_bar.xml
index 4dff716..6123ea7 100644
--- a/v17/leanback/res/layout/lb_search_bar.xml
+++ b/v17/leanback/res/layout/lb_search_bar.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
- android:layout_marginLeft="@dimen/lb_search_bar_speech_orb_margin_left"
+ android:layout_marginStart="@dimen/lb_search_bar_speech_orb_margin_start"
android:focusable="true"
android:focusableInTouchMode="true"
>
@@ -31,9 +31,9 @@
android:id="@+id/lb_search_bar_items"
android:layout_width="@dimen/lb_search_bar_items_width"
android:layout_height="@dimen/lb_search_bar_items_height"
- android:layout_toRightOf="@+id/lb_search_bar_speech_orb"
+ android:layout_toEndOf="@+id/lb_search_bar_speech_orb"
android:layout_centerVertical="true"
- android:layout_marginLeft="@dimen/lb_search_bar_items_margin_left"
+ android:layout_marginStart="@dimen/lb_search_bar_items_margin_start"
android:layout_marginTop="@dimen/lb_search_bar_inner_margin_top"
android:layout_marginBottom="@dimen/lb_search_bar_inner_margin_bottom"
android:background="@drawable/lb_in_app_search_bg"
@@ -43,9 +43,9 @@
android:id="@+id/lb_search_bar_badge"
android:layout_width="@dimen/lb_search_bar_icon_width"
android:layout_height="@dimen/lb_search_bar_icon_height"
- android:layout_gravity="center_vertical|left"
+ android:layout_gravity="center_vertical|start"
android:layout_centerVertical="true"
- android:layout_marginLeft="@dimen/lb_search_bar_icon_margin_left"
+ android:layout_marginStart="@dimen/lb_search_bar_icon_margin_start"
android:src="@null"
android:visibility="gone"
style="?attr/browseTitleIconStyle"/>
@@ -54,11 +54,11 @@
android:id="@+id/lb_search_text_editor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|right"
- android:layout_marginLeft="@dimen/lb_search_bar_edit_text_margin_left"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginStart="@dimen/lb_search_bar_edit_text_margin_start"
android:layout_centerVertical="true"
android:cursorVisible="true"
- android:layout_toRightOf="@+id/lb_search_bar_badge"
+ android:layout_toEndOf="@+id/lb_search_bar_badge"
android:editable="true"
android:background="@null"
android:fontFamily="sans-serif"
diff --git a/v17/leanback/res/layout/lb_title_view.xml b/v17/leanback/res/layout/lb_title_view.xml
index 590a683..e82621e 100644
--- a/v17/leanback/res/layout/lb_title_view.xml
+++ b/v17/leanback/res/layout/lb_title_view.xml
@@ -18,9 +18,11 @@
<ImageView
android:id="@+id/browse_badge"
- android:layout_width="@dimen/lb_browse_title_text_width"
- android:layout_height="@dimen/lb_browse_title_height"
- android:layout_gravity="center_vertical|right"
+ android:layout_width="wrap_content"
+ android:maxWidth="@dimen/lb_browse_title_icon_max_width"
+ android:adjustViewBounds="true"
+ android:layout_height="@dimen/lb_browse_title_icon_height"
+ android:layout_gravity="center_vertical|end"
android:src="@null"
android:visibility="gone"
style="?attr/browseTitleIconStyle"/>
@@ -29,14 +31,15 @@
android:id="@+id/browse_title"
android:layout_width="@dimen/lb_browse_title_text_width"
android:layout_height="@dimen/lb_browse_title_height"
- android:layout_gravity="center_vertical|right"
+ android:layout_gravity="center_vertical|end"
style="?attr/browseTitleTextStyle"/>
<android.support.v17.leanback.widget.SearchOrbView
android:id="@+id/browse_orb"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_gravity="center_vertical|left"
+ android:transitionGroup="true"
+ android:layout_gravity="center_vertical|start"
android:visibility="invisible" />
</merge>
diff --git a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
index db43e22..b287986 100644
--- a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
+++ b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
@@ -19,7 +19,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
- <android.support.v17.leanback.app.BrowseFrameLayout
+ <android.support.v17.leanback.widget.BrowseFrameLayout
android:id="@+id/browse_frame"
android:focusable="true"
android:focusableInTouchMode="true"
@@ -34,5 +34,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
- </android.support.v17.leanback.app.BrowseFrameLayout>
+ </android.support.v17.leanback.widget.BrowseFrameLayout>
</FrameLayout>
diff --git a/v17/leanback/res/transition-v19/lb_browse_headers_in.xml b/v17/leanback/res/transition-v19/lb_browse_headers_in.xml
new file mode 100644
index 0000000..66e7750
--- /dev/null
+++ b/v17/leanback/res/transition-v19/lb_browse_headers_in.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transitionSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/lb_browse_headers_transition_duration" >
+ <changeBounds android:startDelay="@integer/lb_browse_headers_transition_delay" />
+ <transition class="android.support.v17.leanback.Scale"
+ android:startDelay="@integer/lb_browse_headers_transition_delay" >
+ <targets>
+ <target android:targetId="@id/container_list" />
+ </targets>
+ </transition>
+ <fade android:fadingMode="fade_in"
+ android:startDelay="@integer/lb_browse_headers_transition_delay" />
+ <fade android:fadingMode="fade_out" />
+</transitionSet>
diff --git a/v17/leanback/res/transition-v19/lb_browse_headers_out.xml b/v17/leanback/res/transition-v19/lb_browse_headers_out.xml
new file mode 100644
index 0000000..a12c9b7
--- /dev/null
+++ b/v17/leanback/res/transition-v19/lb_browse_headers_out.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transitionSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/lb_browse_headers_transition_duration" >
+ <changeBounds />
+ <transition class="android.support.v17.leanback.Scale" >
+ <targets>
+ <target android:targetId="@id/container_list" />
+ </targets>
+ </transition>
+ <fade android:fadingMode="fade_in"
+ android:startDelay="@integer/lb_browse_headers_transition_delay" />
+ <fade android:fadingMode="fade_out" />
+</transitionSet>
diff --git a/v17/leanback/res/transition-v21/lb_browse_enter_transition.xml b/v17/leanback/res/transition-v21/lb_browse_enter_transition.xml
new file mode 100644
index 0000000..4ad9888
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_enter_transition.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <fade
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:duration="150"/>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml b/v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml
new file mode 100644
index 0000000..e26204b
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <changeBounds
+ android:duration="350"
+ android:interpolator="@android:interpolator/linear_out_slow_in">
+ </changeBounds>
+ <slide
+ android:duration="350"
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:slideEdge="right">
+ </slide>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_browse_headers_in.xml b/v17/leanback/res/transition-v21/lb_browse_headers_in.xml
new file mode 100644
index 0000000..ee4eaeb
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_headers_in.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transitionSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/lb_browse_headers_transition_duration" >
+ <changeBounds android:startDelay="@integer/lb_browse_headers_transition_delay"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <changeTransform
+ android:startDelay="@integer/lb_browse_headers_transition_delay"
+ android:interpolator="@android:interpolator/fast_out_linear_in" >
+ <targets>
+ <target android:targetId="@id/container_list" />
+ </targets>
+ </changeTransform>
+ <fade android:transitionVisibilityMode="mode_in"
+ android:startDelay="@integer/lb_browse_headers_transition_delay"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <fade android:transitionVisibilityMode="mode_out"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</transitionSet>
diff --git a/v17/leanback/res/transition-v21/lb_browse_headers_out.xml b/v17/leanback/res/transition-v21/lb_browse_headers_out.xml
new file mode 100644
index 0000000..667b7ce
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_headers_out.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transitionSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/lb_browse_headers_transition_duration" >
+ <changeBounds android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <changeTransform android:interpolator="@android:interpolator/fast_out_linear_in" >
+ <targets>
+ <target android:targetId="@id/container_list" />
+ </targets>
+ </changeTransform>
+ <fade android:transitionVisibilityMode="mode_in"
+ android:startDelay="@integer/lb_browse_headers_transition_delay"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <fade android:transitionVisibilityMode="mode_out"
+ android:interpolator="@android:interpolator/fast_out_linear_in"/>
+</transitionSet>
diff --git a/v17/leanback/res/transition-v21/lb_browse_return_transition.xml b/v17/leanback/res/transition-v21/lb_browse_return_transition.xml
new file mode 100644
index 0000000..ee88fff
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_return_transition.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <slide
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350"
+ android:slideEdge="left">
+ <targets>
+ <target android:targetId="@id/browse_headers_root" />
+ <target android:targetId="@id/browse_orb" />
+ </targets>
+ </slide>
+ <slide
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350"
+ android:slideEdge="right">
+ <targets>
+ <target android:excludeId="@+id/browse_headers_root" />
+ <target android:excludeId="@+id/browse_orb" />
+ </targets>
+ </slide>
+ <fade
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350">
+ <targets>
+ <target android:excludeId="@+id/browse_headers_root" />
+ </targets>
+ </fade>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_details_enter_transition.xml b/v17/leanback/res/transition-v21/lb_details_enter_transition.xml
new file mode 100644
index 0000000..240d4bc
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_details_enter_transition.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <transition class="android.support.v17.leanback.transition.SlideNoPropagation"
+ android:duration="500"
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:slideEdge="bottom">
+ </transition>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_details_return_transition.xml b/v17/leanback/res/transition-v21/lb_details_return_transition.xml
new file mode 100644
index 0000000..f555533
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_details_return_transition.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <transition class="android.support.v17.leanback.transition.SlideNoPropagation"
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350"
+ android:slideEdge="bottom">
+ </transition>
+ <fade
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350">
+ </fade>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml b/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
index 0ba4125..82913d9 100644
--- a/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
+++ b/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
@@ -24,8 +24,6 @@
<changeBounds
android:interpolator="@android:interpolator/linear_out_slow_in">
</changeBounds>
- <changeTransform
- android:interpolator="@android:interpolator/linear_out_slow_in" />
<changeImageTransform
android:interpolator="@android:interpolator/linear_out_slow_in"/>
</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_title_in.xml b/v17/leanback/res/transition-v21/lb_title_in.xml
new file mode 100644
index 0000000..7b38785
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_title_in.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transition
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ class="android.support.v17.leanback.transition.SlideKitkat"
+ android:interpolator="@android:anim/decelerate_interpolator"
+ lb:lb_slideEdge="top" >
+ <targets>
+ <target android:targetId="@id/browse_title_group" />
+ </targets>
+</transition>
diff --git a/v17/leanback/res/transition-v21/lb_title_out.xml b/v17/leanback/res/transition-v21/lb_title_out.xml
new file mode 100644
index 0000000..50fb67e
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_title_out.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transition
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ class="android.support.v17.leanback.transition.SlideKitkat"
+ android:interpolator="@animator/lb_decelerator_4"
+ lb:lb_slideEdge="top" >
+ <targets>
+ <target android:targetId="@id/browse_title_group" />
+ </targets>
+</transition>
diff --git a/v17/leanback/res/transition-v22/lb_browse_entrance_transition.xml b/v17/leanback/res/transition-v22/lb_browse_entrance_transition.xml
new file mode 100644
index 0000000..2068c64
--- /dev/null
+++ b/v17/leanback/res/transition-v22/lb_browse_entrance_transition.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <changeBounds
+ android:duration="350"
+ android:interpolator="@android:interpolator/linear_out_slow_in">
+ </changeBounds>
+ <slide
+ android:duration="350"
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:slideEdge="end">
+ </slide>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v22/lb_browse_return_transition.xml b/v17/leanback/res/transition-v22/lb_browse_return_transition.xml
new file mode 100644
index 0000000..62a273e
--- /dev/null
+++ b/v17/leanback/res/transition-v22/lb_browse_return_transition.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <slide
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350"
+ android:slideEdge="start">
+ <targets>
+ <target android:targetId="@id/browse_headers_root" />
+ <target android:targetId="@id/browse_orb" />
+ </targets>
+ </slide>
+ <slide
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350"
+ android:slideEdge="end">
+ <targets>
+ <target android:excludeId="@+id/browse_headers_root" />
+ <target android:excludeId="@+id/browse_orb" />
+ </targets>
+ </slide>
+ <fade
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350">
+ <targets>
+ <target android:excludeId="@+id/browse_headers_root" />
+ </targets>
+ </fade>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/values-af/strings.xml b/v17/leanback/res/values-af/strings.xml
index 2d570ba..c7109bb 100644
--- a/v17/leanback/res/values-af/strings.xml
+++ b/v17/leanback/res/values-af/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Praat om te soek"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Deursoek <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Praat om <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> te deursoek"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Speel"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Laat wag"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Vinnig vorentoe"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spoel vorentoe %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spoel terug"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spoel terug %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Slaan volgende oor"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Slaan vorige oor"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Nog handelinge"</string>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
index 03a88ce..d5bf0f5 100644
--- a/v17/leanback/res/values-am/strings.xml
+++ b/v17/leanback/res/values-am/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ለመፈለግ ይናገሩ"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ፈልግ"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ን ለመፈለግ ይናገሩ"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"አጫውት"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"ለአፍታ አቁም"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"በፍጥነት አሳልፍ"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"በ%1$dX ወደፊት አፍጥን"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ወደኋላ አጠንጥን"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"በ%1$dX አጠንጥን"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"የሚቀጥለውን ዝለል"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"የቀደመውን ዝለል"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"ተጨማሪ እርምጃዎች"</string>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
index 65d854d..31f4d1a 100644
--- a/v17/leanback/res/values-ar/strings.xml
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"التحدث للبحث"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"بحث في <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"تحدّث للبحث في <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"تشغيل"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"إيقاف مؤقت"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"تقديم سريع"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"التقديم السريع %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"إرجاع"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"الترجيع %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"تخطي التالي"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"تخطي السابق"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"مزيد من الإجراءات"</string>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
index a662ac0..de0b6f8 100644
--- a/v17/leanback/res/values-bg/strings.xml
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорете, за да търсите"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Търсете в/ъв <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Говорете, за да търсите в/ъв <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Пускане"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Поставяне на пауза"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Превъртане напред"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Превъртане напред със скорост %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Превъртане назад"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Превъртане назад със скорост %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Напред към следващия елемент"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Назад към предишния елемент"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Още действия"</string>
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn-rBD/strings.xml
index b5d9c2e..4f0526c 100644
--- a/v17/leanback/res/values-bn-rBD/strings.xml
+++ b/v17/leanback/res/values-bn-rBD/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"অনুসন্ধান করতে বলুন"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করুন"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করতে বলুন"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"চালান"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"বিরাম দিন"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"দ্রুত ফরওয়ার্ড"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"দ্রুত ফরওয়ার্ড %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"পেছনের দিকে যান"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"পেছনের দিকে যান %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"সরাসরি পরেরটিতে চলে যান"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"সরাসরি আগেরটিতে চলে যান"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"আরো অ্যাকশন"</string>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
index 1847873..187f5af 100644
--- a/v17/leanback/res/values-ca/strings.xml
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per fer una cerca."</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cerca a <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Parla per cercar a <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reprodueix"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Posa en pausa"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avança ràpidament"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avança ràpidament %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rebobina"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobina %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Passa al següent"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Passa a l\'anterior"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Més accions"</string>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
index f02aad2..1a60828 100644
--- a/v17/leanback/res/values-cs/strings.xml
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Vyhledávejte hlasem"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Hledat <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Vyhledávejte v kategorii „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“ hlasem"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d×"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d×"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Přehrát"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pozastavit"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Přetočit vpřed"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Přetočit vpřed %1$d×"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Přetočit zpět"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Přetočit zpět %1$d×"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Přeskočit na další"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Přeskočit na předchozí"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Další akce"</string>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
index 05a8306..e3e0f9f 100644
--- a/v17/leanback/res/values-da/strings.xml
+++ b/v17/leanback/res/values-da/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tal for at søge"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Søg efter <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tal for at søge efter <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Afspil"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Sæt på pause"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Spol frem"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spol frem %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spol tilbage"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spol tilbage %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Spring til næste"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Spring til forrige"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Flere handlinger"</string>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
index 52488cf..d729f7c 100644
--- a/v17/leanback/res/values-de/strings.xml
+++ b/v17/leanback/res/values-de/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Zum Suchen sprechen"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"In <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> suchen"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Zum Suchen in der Kategorie \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\" sprechen"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dx"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dx"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Wiedergabe"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausieren"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Vorspulen"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Vorspulen %1$dx"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Zurückspulen"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Zurückspulen %1$dx"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Nächsten Titel überspringen"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Vorherigen Titel überspringen"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Weitere Aktionen"</string>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
index fde314c..9b93dcf 100644
--- a/v17/leanback/res/values-el/strings.xml
+++ b/v17/leanback/res/values-el/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Μιλήστε για να κάνετε αναζήτηση"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Αναζήτηση <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Μιλήστε για αναζήτηση <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Αναπαραγωγή"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Παύση"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Γρήγορη προώθηση"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Γρήγορη προώθηση %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Επαναφορά"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Επαναφορά %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Παράβλεψη επόμενου"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Παράβλεψη προηγούμενου"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Περισσότερες ενέργειες"</string>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
index 8e071b3..ed22ccd 100644
--- a/v17/leanback/res/values-en-rGB/strings.xml
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Speak to search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Play"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pause"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Fast-Forward"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Fast Forward %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rewind"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rewind %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Skip Next"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Skip Previous"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"More Actions"</string>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
index 8e071b3..ed22ccd 100644
--- a/v17/leanback/res/values-en-rIN/strings.xml
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Speak to search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Play"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pause"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Fast-Forward"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Fast Forward %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rewind"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rewind %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Skip Next"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Skip Previous"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"More Actions"</string>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
index 315ffa0..ab05f83 100644
--- a/v17/leanback/res/values-es-rUS/strings.xml
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Habla para buscar en <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reproducir"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avanzar"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avanzar rápidamente %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Retroceder"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobinar %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ir al siguiente"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ir al anterior"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Más acciones"</string>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
index 4d86408..0cff1c9 100644
--- a/v17/leanback/res/values-es/strings.xml
+++ b/v17/leanback/res/values-es/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Habla para buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reproducir"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rápido"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rápido %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rebobinar"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobinar %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Saltar siguiente"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Saltar anterior"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Más acciones"</string>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
index 44ecbf5..32fff96 100644
--- a/v17/leanback/res/values-et-rEE/strings.xml
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Öelge otsimiseks"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Otsige teenusest <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Kõnelge teenusest <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> otsimiseks"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Esita"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Peata"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Keri edasi"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Edasikerimine %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Keri tagasi"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Tagasikerimine %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Liigu järgmise üksuse juurde"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Liigu eelmise üksuse juurde"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Veel toiminguid"</string>
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu-rES/strings.xml
index 408823b..d9f9bf7 100644
--- a/v17/leanback/res/values-eu-rES/strings.xml
+++ b/v17/leanback/res/values-eu-rES/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Esan bilatu nahi duzuna"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Bilatu <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Esan bilatu nahi duzuna, bilaketa hemen egiteko: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Erreproduzitu"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausatu"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Aurreratu"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Aurreratu %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Atzeratu"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Atzeratu %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Saltatu hurrengora"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Saltatu aurrekora"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Ekintza gehiago"</string>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
index 24299df..bb615fc 100644
--- a/v17/leanback/res/values-fa/strings.xml
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"برای جستجو صحبت کنید"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"جستجوی <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"جستجو با گفتن <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"پخش"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"توقف موقت"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"جلو بردن سریع"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"بازارسال سریع %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"عقب بردن"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"عقب بردن %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"رد شدن از بعدی"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"رد شدن از قبلی"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"عملکردهای بیشتر"</string>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
index 43f02bc..9d38d3c 100644
--- a/v17/leanback/res/values-fi/strings.xml
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -22,44 +22,28 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tee haku puhumalla"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Haku: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Puhehaku: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
- <skip />
- <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
- <skip />
- <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
- <skip />
- <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
- <skip />
- <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
- <skip />
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"Toista"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Keskeytä"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Kelaa eteenpäin"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Kelaa eteenpäin %1$dX"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Kelaa taakse"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Kelaa taaksepäin %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Siirry seuraavaan"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Siirry edelliseen"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Lisää toimintoja"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Poista Tykkään-valinta"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Valitse Tykkään"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Poista En tykkää -valinta"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Valitse En tykkää"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Ei uudelleentoistoa"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Toista kaikki uudelleen"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Toista yksi uudelleen"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Ota satunnaistoisto käyttöön"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Poista satunnaistoisto käytöstä"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Ota korkea laatu käyttöön"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Poista korkea laatu käytöstä"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ota tekstitys käyttöön"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Poista tekstitys käytöstä"</string>
</resources>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
index bdae40e..bbd3eea 100644
--- a/v17/leanback/res/values-fr-rCA/strings.xml
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncez votre recherche"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Rechercher dans <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Énoncez votre recherche dans <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Lecture"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pause"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rapide"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rapide à %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Reculer"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Retour rapide à %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Passer à l\'élément suivant"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Passer à l\'élément précédent"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Autres actions"</string>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
index f3e196b..e9c051c 100644
--- a/v17/leanback/res/values-fr/strings.xml
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncer la recherche"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Rechercher \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\""</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Énoncer la recherche \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\""</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Lecture"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Interrompre"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rapide"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rapide de %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Retour arrière"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Retour arrière de %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ignorer l\'élément suivant"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ignorer l\'élément précédent"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Autres actions"</string>
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl-rES/strings.xml
index 4720e2d..a3884d8 100644
--- a/v17/leanback/res/values-gl-rES/strings.xml
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fala para efectuar a busca"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Busca <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fala para buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reproducir"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rápido"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rápido %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rebobinar"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobinado %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Saltar seguinte"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Saltar anterior"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Máis accións"</string>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index 2d40dad..a926396 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -22,22 +22,26 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजने के लिए बोलें"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजें"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजने के लिए बोलें"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"चलाएं"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"रोकें"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"फ़ास्ट फ़ॉरवर्ड"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"फ़ास्ट फ़ॉरवर्ड %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"रिवाइंड करें"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"रिवाइंड %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"अगले पर जाएं"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"पिछले पर जाएं"</string>
- <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"अधिक कार्रवाइयां"</string>
- <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"पसंदीदा का चयन न करें"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"अधिक विकल्प"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"पसंदीदा को ना चुनें"</string>
<string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"पसंदीदा चुनें"</string>
- <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"नापसंद का चयन न करें"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"नापसंद को ना चुनें"</string>
<string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"नापसंद चुनें"</string>
<string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"कुछ भी न दोहराएं"</string>
<string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"सभी को दोहराएं"</string>
<string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"एक दोहराएं"</string>
- <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"शफ़ल सक्षम करें"</string>
- <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"शफ़ल अक्षम करें"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"फेर-बदल सक्षम करें"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"फेर-बदल अक्षम करें"</string>
<string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"उच्च गुणवत्ता सक्षम करें"</string>
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करें"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षक सक्षम करें"</string>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
index 196d9bf..166369f 100644
--- a/v17/leanback/res/values-hr/strings.xml
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite upit za pretraživanje"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite upit za pretraživanje <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reproduciraj"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauziraj"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Brzo naprijed"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Brzo unaprijed %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Unatrag"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Unatrag %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči na sljedeće"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči na prethodno"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Više radnji"</string>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
index 5a26a28..427f1cd 100644
--- a/v17/leanback/res/values-hu/strings.xml
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -22,12 +22,16 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Beszéljen a keresés indításához"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Keresés itt: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Mondj valamit a kereséshez: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Lejátszás"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Szünet"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Gyors előretekerés"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Előretekerés %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Visszatekerés"</string>
- <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"A következő átugrása"</string>
- <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Az előző átugrása"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Visszatekerés %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ugrás a következőre"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ugrás az előzőre"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"További műveletek"</string>
<string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"„Tetszik” értékelés visszavonása"</string>
<string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"„Tetszik” értékelés kiválasztása"</string>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
index 3d1ad21..7e8112e 100644
--- a/v17/leanback/res/values-hy-rAM/strings.xml
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -22,44 +22,28 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Խոսեք՝ որոնելու համար"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Որոնեք <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Խոսեք՝ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> որոնելու համար"</string>
- <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
- <skip />
- <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
- <skip />
- <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
- <skip />
- <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
- <skip />
- <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
- <skip />
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"Նվագարկել"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Դադարեցնել"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Արագ առաջ անցնել"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Առագ առաջանցում %1$dX"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Հետ փաթաթել"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Հետանցում %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Անցնել հաջորդին"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Անցնել նախորդին"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Այլ գործողություններ"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Ապանշել Հավանելու կոճակը"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Նշել Հավանելու կոճակը"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Ապանշել Չհավանելու կոճակը"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Նշել Չհավանելու կոճակը"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Չկրկնել"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Կրկնել բոլորը"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Կրկնել մեկը"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Միացնել խառը նվագարկումը"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Անջատել խառը նվագարկումը"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Միացնել բարձր որակը"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Անջատել բարձր որակը"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Միացնել խորագրերը"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Անջատել խորագրերը"</string>
</resources>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
index 243e354..2dca7d3 100644
--- a/v17/leanback/res/values-in/strings.xml
+++ b/v17/leanback/res/values-in/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ucapkan untuk menelusuri"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Telusuri <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Ucapkan untuk menelusuri <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Putar"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Jeda"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Maju Cepat"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Maju %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Putar Ulang"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Mundur %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Lewati ke Berikutnya"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Lewati ke Sebelumnya"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Tindakan Lainnya"</string>
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is-rIS/strings.xml
index 51ba56e..c84a4c6 100644
--- a/v17/leanback/res/values-is-rIS/strings.xml
+++ b/v17/leanback/res/values-is-rIS/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Talaðu til að leita"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Talaðu til að leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Spila"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Hlé"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Spóla áfram"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spóla áfram %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spóla til baka"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spóla til baka %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Fara í næsta"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Fara í fyrra"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Fleiri aðgerðir"</string>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
index 03933ac..1b58e0c 100644
--- a/v17/leanback/res/values-it/strings.xml
+++ b/v17/leanback/res/values-it/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per cercare"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cerca in <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Parla per cercare in <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Riproduci"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Metti in pausa"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avanza velocemente"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avanti veloce: %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Riavvolgi"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Indietro: %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Salta successivo"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Salta precedente"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Altre azioni"</string>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
index ca8b42b..f102498 100644
--- a/v17/leanback/res/values-iw/strings.xml
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"דבר כדי לחפש"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"חפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"דבר כדי לחפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"הפעל"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"השהה"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"הרץ קדימה"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"העברה קדימה של %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"הרץ אחורה"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"העברה לאחור של %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"דלג אל הפריט הבא"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"דלג אל הפריט הקודם"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"עוד פעולות"</string>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
index 7e63434..802631c 100644
--- a/v17/leanback/res/values-ja/strings.xml
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"音声検索"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>を検索"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>を音声検索"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"再生"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"一時停止"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"早送り"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"早送り%1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"巻き戻し"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"巻き戻し%1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"次の曲にスキップ"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"前の曲にスキップ"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"その他の操作"</string>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
index 273bd88..70aeada 100644
--- a/v17/leanback/res/values-ka-rGE/strings.xml
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -22,44 +22,32 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"თქვით საძიებო ფრაზა"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>-ის ძიება"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"თქვით <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>-ის საძიებლად"</string>
- <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"დაკვრა"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"პაუზა"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"წინ გადახვევა"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for lb_playback_controls_fast_forward_multiplier (1058753672110224526) -->
<skip />
- <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"უკან გადახვევა"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for lb_playback_controls_rewind_multiplier (1640629531440849942) -->
<skip />
- <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
- <skip />
- <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
- <skip />
- <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
- <skip />
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"შემდეგის გამოტოვება"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"წინას გამოტოვება"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"დამატებითი ქმედებები"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"მაღალი შეფასების არჩევის გაუქმება"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"მაღალი შეფასების არჩევა"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"დაბალი შეფასების არჩევის გაუქმება"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"დაბალი შეფასების არჩევა"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"არცერთის გამეორება"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"ყველას გამეორება"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"ერთის გამეორება"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"არეულად დაკვრის ჩართვა"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"არეულად დაკვრის გამორთვა"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"მაღალი ხარისხის ჩართვა"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"მაღალი ხარისხის გამორთვა"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"დახურული წარწერების ჩართვა"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"დახურული წარწერების გაუქმება"</string>
</resources>
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk-rKZ/strings.xml
index d683255..9ed6ce2 100644
--- a/v17/leanback/res/values-kk-rKZ/strings.xml
+++ b/v17/leanback/res/values-kk-rKZ/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Іздеу үшін сөйлеу"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> іздеу"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> іздеу үшін сөйлеңіз"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Ойнату"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Кідірту"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Алға айналдыру"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX алға айналдыру"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Кері айналдыру"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX кері айналдыру"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Келесіге өту"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Алдыңғыға өту"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Қосымша әрекеттер"</string>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
index 6c10e6c..ea7d0f4 100644
--- a/v17/leanback/res/values-km-rKH/strings.xml
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"និយាយដើម្បីស្វែងរក"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ស្វែងរក <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"និយាយដើម្បីស្វែងរក <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"ចាក់"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"ផ្អាក"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"បញ្ជូនបន្តរហ័ស"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ខាទៅមុខ %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ខាថយក្រោយ"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"ខាថយក្រោយ %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"រំលងបន្ទាប់"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"រំលងមុន"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"សកម្មភាពច្រើនទៀត"</string>
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn-rIN/strings.xml
index 34688ef..196b154 100644
--- a/v17/leanback/res/values-kn-rIN/strings.xml
+++ b/v17/leanback/res/values-kn-rIN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ಹುಡುಕಲು ಮಾತನಾಡಿ"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ಹುಡುಕಿ"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ಮಾತನಾಡಿ ಹುಡುಕಾಟ ನಡೆಸಿ"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"ಪ್ಲೇ ಮಾಡು"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"ವಿರಾಮಗೊಳಿಸು"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ಫಾಸ್ಟ್ ಫಾರ್ವರ್ಡ್"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ಫಾಸ್ಟ್ ಫಾರ್ವರ್ಡ್ %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ರೀವೈಂಡ್"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"ರಿವೈಂಡ್ %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ಮುಂದೆ ಸ್ಕಿಪ್ ಮಾಡಿ"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ಹಿಂದೆ ಸ್ಕಿಪ್ ಮಾಡಿ"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"ಹೆಚ್ಚು ಕ್ರಿಯೆಗಳು"</string>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
index f01f79e..c244dbf 100644
--- a/v17/leanback/res/values-ko/strings.xml
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"음성 검색"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> 검색"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> 음성 검색"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d배속"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d배속"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"재생"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"일시중지"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"빨리 감기"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$d배속 빨리 감기"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"되감기"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$d배속 되감기"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"다음으로 건너뛰기"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"이전으로 건너뛰기"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"추가 작업"</string>
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky-rKG/strings.xml
index dd84a54..4ddb284 100644
--- a/v17/leanback/res/values-ky-rKG/strings.xml
+++ b/v17/leanback/res/values-ky-rKG/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Издөө үчүн сүйлөңүз"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> издөө"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> издөө үчүн сүйлөңүз"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Ойнотуу"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Тындыруу"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Алдыга түрүү"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Алдыга түрүү %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Артка түрүү"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Артка түрүү %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Кийинкини өткөрүп жиберүү"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Мурункуну өткөрүп жиберүү"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Дагы көнүгүүлөр"</string>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
index acda291..35f519b 100644
--- a/v17/leanback/res/values-lo-rLA/strings.xml
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ເວົ້າເພື່ອຊອກຫາ"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ຊອກຫາ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"ເວົ້າເພື່ອຊອກຫາ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"ຫຼິ້ນ"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"ຢຸດຊົ່ວຄາວ"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ເລື່ອນໄປໜ້າ"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ໄປໜ້າແບບໄວ %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ຣີວາຍກັບ"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"ກັບຄືນ %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ຂ້າມໄປຕໍ່"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ຂ້າມໄປກ່ອນໜ້າ"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"ຄຳສັ່ງເພີ່ມເຕີມ"</string>
@@ -40,6 +44,6 @@
<string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"ປິດນຳໃຊ້ການສະຫຼັບ"</string>
<string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"ເປີດນຳໃຊ້ການຫຼິ້ນດ້ວຍຄຸນນະພາບສູງ"</string>
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ປິດນຳໃຊ້ການຫຼິ້ນດ້ວຍຄຸນນະພາບສູງ"</string>
- <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ເປີດນຳໃຊ້ຄຳອະທິບາຍລະອຽດ"</string>
- <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ປິດນຳໃຊ້ຄຳອະທິບາຍລະອຽດ"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ເປີດນຳໃຊ້ຄຳບັນຍາຍແບບປິດ"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ປິດນຳໃຊ້ຄຳບັນຍາຍແບບປິດ"</string>
</resources>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
index 5efd468..6ca2bab 100644
--- a/v17/leanback/res/values-lt/strings.xml
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Pasakykite, kad ieškotumėte"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Ieškoti „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Kalbėkite, kad ieškotumėte „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d k."</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d k."</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Leisti"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pristabdyti"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Sukti pirmyn"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Sukti pirmyn %1$d k. greičiau"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Sukti atgal"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Sukti atgal %1$d k. greičiau"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Praleisti kitą"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Praleisti ankstesnį"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Daugiau veiksmų"</string>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
index 88a0633..7d3bc2b 100644
--- a/v17/leanback/res/values-lv/strings.xml
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Runāt, lai meklētu"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Meklējiet <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Runājiet, lai meklētu: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Atskaņot"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauzēt"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Pārtīt uz priekšu"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Pārtīt uz priekšu %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Attīt atpakaļ"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Attīt atpakaļ %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Izlaist nākamo"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Izlaist iepriekšējo"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Citas darbības"</string>
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk-rMK/strings.xml
index a65caa1..75666e0 100644
--- a/v17/leanback/res/values-mk-rMK/strings.xml
+++ b/v17/leanback/res/values-mk-rMK/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Зборувајте за да пребарувате"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Пребарувај <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Кажете за да се пребарува <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Пушти"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Пауза"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Брзо премотај напред"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Премотај напред %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Премотај назад"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Премотај назад %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Прескокни на следна"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Прескокни на претходна"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Повеќе дејства"</string>
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml-rIN/strings.xml
index 1d2b8ac..b900f09 100644
--- a/v17/leanback/res/values-ml-rIN/strings.xml
+++ b/v17/leanback/res/values-ml-rIN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ശബ്ദം ഉപയോഗിച്ച് തിരയുക"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> തിരയുക"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> തിരയുന്നതിന് സംസാരിക്കുക"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"പ്ലേ ചെയ്യുക"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"താൽക്കാലികമായി നിർത്തുക"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ഫാസ്റ്റ് ഫോർവേഡ് ചെയ്യുക"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX വേഗത്തിൽ ഫോർവേഡുചെയ്യുക"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"റിവൈൻഡുചെയ്യുക"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX റിവൈൻഡുചെയ്യുക"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"അടുത്തതിലേക്ക് പോകുക"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"മുമ്പത്തേതിലേക്ക് പോകുക"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"കൂടുതൽ പ്രവർത്തനങ്ങൾ"</string>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
index 6f7fccf..e4a8fcd 100644
--- a/v17/leanback/res/values-mn-rMN/strings.xml
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ярьж хайх"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> Хайх"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> хайхын тулд ярина уу"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Тоглуулах"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Түр зогсоох"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Хурдан урагшлуулах"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Түргэн Урагш Гүйлгэх %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Буцааж хураах"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Хойш Гүйлгэх %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Дараагийнхийг алгасах"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Өмнөхийг алгасах"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Өөр үйлдлүүд"</string>
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr-rIN/strings.xml
index 84207c3..11748ec 100644
--- a/v17/leanback/res/values-mr-rIN/strings.xml
+++ b/v17/leanback/res/values-mr-rIN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"शोधण्यासाठी बोला"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> शोधा"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> शोधण्यासाठी बोला"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"प्ले करा"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"विराम द्या"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"फास्ट फॉरवर्ड करा"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"फास्ट फॉरवर्ड %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"रिवाईँड करा"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"रीवाईंड %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"पुढील वगळा"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"मागील वगळा"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"अधिक क्रिया"</string>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
index 9893fda..c073e43 100644
--- a/v17/leanback/res/values-ms-rMY/strings.xml
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -22,44 +22,28 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tutur untuk membuat carian"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cari <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Sebut untuk mencari <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
- <skip />
- <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
- <skip />
- <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
- <skip />
- <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
- <skip />
- <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
- <skip />
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"Main"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Jeda"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Mara Laju"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Lajukan %1$dX"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Gulung semula"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Gulung semula %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Langkau Seterusnya"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Langkau Sebelumnya"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Lagi Tindakan"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Nyahpilih Bagus"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Pilih Bagus"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Nyahpilih Tidak Bagus"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Pilih Tidak Bagus"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Jangan Ulang"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Ulang Semua"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Ulang Satu"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Dayakan Rombak"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Lumpuhkan Rombak"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Dayakan Kualiti Tinggi"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Lumpuhkan Kualiti Tinggi"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Dayakan Kapsyen Tertutup"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Lumpuhkan Kapsyen Tertutup"</string>
</resources>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
index 884e026..77a2271 100644
--- a/v17/leanback/res/values-my-rMM/strings.xml
+++ b/v17/leanback/res/values-my-rMM/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ရှာဖွေရန် ပြောပါ"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ကို ရှာရန်"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ကို ရှာရန် ပြောပါ"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"ဖွင့်ရန်"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"ခဏရပ်ရန်"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ရှေ့သို့ သွားရန်"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ရှေ့သို့ ရစ်ရန် %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ပြန်ရစ်ရန်"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"နောက်သို့ ရစ်ရန် %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"နောက်တစ်ပုဒ်သို့ ကျော်ရန်"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ယခင်တစ်ပုဒ်သို့ သွားရန်"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"နောက်ထပ် လုပ်ဆောင်ချက်များ"</string>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
index bef4244..f5ab2e1 100644
--- a/v17/leanback/res/values-nb/strings.xml
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Snakk for å søke"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Søk i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Snakk for å søke i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Spill av"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Sett på pause"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Fremoverspoling"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Fremoverspoling %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Tilbakespoling"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Tilbakespoling %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Hopp til neste"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Hopp til forrige"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Flere handlinger"</string>
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne-rNP/strings.xml
index 55d4661..c399985 100644
--- a/v17/leanback/res/values-ne-rNP/strings.xml
+++ b/v17/leanback/res/values-ne-rNP/strings.xml
@@ -22,10 +22,16 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजी गर्न बोल्नुहोस्"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोज्नुहोस्"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजी गर्न बोल्नुहोस्"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"प्ले गर्नुहोस्"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"रोक्नुहोस्"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"फास्ट फर्वार्ड"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for lb_playback_controls_fast_forward_multiplier (1058753672110224526) -->
+ <skip />
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"दोहोर्याउनुहोस्"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"पुन: वाइन्ड गर्नुहोस् %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"अर्को छोड्नुहोस्"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"अघिल्लो छोड्नुहोस्"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"थप कार्यहरू"</string>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
index 057638f..fe73141 100644
--- a/v17/leanback/res/values-nl/strings.xml
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Spreek om te zoeken"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> zoeken"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Spreek om <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> te zoeken"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Afspelen"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Onderbreken"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Vooruitspoelen"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Vooruitspoelen %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Terugspoelen"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Terugspoelen %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Naar volgende"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Naar vorige"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Meer acties"</string>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
index cb7f377..f6280a3 100644
--- a/v17/leanback/res/values-pl/strings.xml
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Powiedz, aby wyszukać"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Szukaj <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Powiedz, by wyszukać w aplikacji <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Odtwórz"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Wstrzymaj"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Przewiń do przodu"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Przewiń do przodu %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Przewiń do tyłu"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Przewiń do tyłu %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Pomiń następny"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Pomiń poprzedni"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Więcej czynności"</string>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
index 0c3fc3a..f3bf4aa 100644
--- a/v17/leanback/res/values-pt-rPT/strings.xml
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fale para pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reproduzir"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Interromper"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avançar"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avançar %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Recuar"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Recuar %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Avançar para o seguinte"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Avançar para o anterior"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Mais ações"</string>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
index 3116f83..13d01a5 100644
--- a/v17/leanback/res/values-pt/strings.xml
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fale para pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reproduzir"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avançar"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avançar %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Retroceder"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Retroceder %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Pular próxima"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Pular anterior"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Mais ações"</string>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
index 7d6efa5..cb6aa4a 100644
--- a/v17/leanback/res/values-ro/strings.xml
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -22,44 +22,28 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Rostiți pentru a căuta"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Căutați <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Vorbiți pentru a căuta în <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
- <skip />
- <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
- <skip />
- <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
- <skip />
- <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
- <skip />
- <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
- <skip />
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"Redă"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Întrerupe"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Derulează rapid înainte"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Derulați rapid înainte cu %1$dX"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Derulează"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Derulați înapoi cu %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ignoră articolul următor"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ignoră articolul anterior"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Mai multe acţiuni"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Deselectează „Îmi place”"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Selectează „Îmi place”"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Deselectează „Nu-mi place”"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Selectează „Nu-mi place”"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Nu repetă"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Repetă toate"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Repetă unul"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Activează redarea în mod aleatoriu"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Dezactivează redarea în mod aleatoriu"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Activează calitatea înaltă"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Dezactivează calitatea înaltă"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activează subtitrările"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Dezactivează subtitrările"</string>
</resources>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
index a1b2cfb..fb03f9d 100644
--- a/v17/leanback/res/values-ru/strings.xml
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Произнесите запрос"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Поиск здесь: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Произнесите запрос, чтобы найти <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Воспроизвести."</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Приостановить."</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Перемотка вперед."</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Перемотка вперед %1$dX."</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Перемотать назад."</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Перемотка назад %1$dX."</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Перейти к следующему элементу."</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Перейти к предыдущему элементу."</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Другие действия."</string>
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si-rLK/strings.xml
index 3566f0e..e5c0cf4 100644
--- a/v17/leanback/res/values-si-rLK/strings.xml
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -22,44 +22,28 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"සෙවීමට කථා කරන්න"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> සොයන්න"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> සොයන්න කථා කරන්න"</string>
- <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
- <skip />
- <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
- <skip />
- <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
- <skip />
- <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
- <skip />
- <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
- <skip />
- <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
- <skip />
- <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
- <skip />
- <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
- <skip />
- <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
- <skip />
- <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
- <skip />
- <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
- <skip />
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"ධාවනය කරන්න"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"විරාමය"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"වේගයෙන් ඉදිරියට යන"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX වේගයෙන් ඉදිරියට යවන්න"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"නැවත ඔතන්න"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX ආපස්සට යවන්න"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ඊළඟ එක මග අරින්න"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"කළින් එක මග අරින්න"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"තව ක්රියාකාරකම්"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"මහපටැඟිල්ල ඉහළට තිබීම තේරීම නොකරන්න"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"මහපටැඟිල්ල ඉහළට තිබීම තේරීම කරන්න"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"මහපටැඟිල්ල පහළට තිබීම තේරීම නොකරන්න"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"මහපටැඟිල්ල පහළට තිබීම තේරීම කරන්න"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"නැවත කරන්න කිසිවක් නැත"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"සියල්ල නැවත කරන්න"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"එකක් නැවත කරන්න"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"ඇනීම සබල කරන්න"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"ඇනීම අබල කරන්න"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"උපරිම ගුණත්වය සබල කරන ලදි"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"උපරිම ගුණත්වය අබල කරන ලදි"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"වැසුණු ශිර්ෂ කිරීම සබල කරන ලදි"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"වැසුණු ශිර්ෂ කිරීම අබල කරන ලදි"</string>
</resources>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
index 0bfe3c5..74a9044 100644
--- a/v17/leanback/res/values-sk/strings.xml
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Hovorením spustíte vyhľadávanie"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Vyhľadať výraz <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Hovorte na vyhľadávanie v kontexte <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Prehrať"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pozastaviť"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Pretočiť dopredu"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Pretočiť dopredu %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Pretočiť späť"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Pretočiť späť %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Prejsť na ďalšiu položku"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Prejsť na predchádzajúcu položku"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Viac akcií"</string>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
index 9bf2760..1af639b 100644
--- a/v17/leanback/res/values-sl/strings.xml
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite, če želite iskati"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Iskanje: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Govorite, če želite iskati: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d-kratno"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d-kratno"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Predvajaj"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Zaustavi"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Previj naprej"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Hitro previjanje naprej – %1$d-kratno"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Previj nazaj"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Previjanje nazaj – %1$d-kratno"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči naslednje"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči prejšnje"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Več dejanj"</string>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
index 8f37cab..bb5c32d 100644
--- a/v17/leanback/res/values-sr/strings.xml
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорите да бисте претраживали"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Претражите <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Изговорите да бисте претражили <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Пусти"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Паузирај"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Премотај унапред"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Премотај унапред %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Премотај уназад"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Премотај уназад %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Прескочи следећу"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Прескочи претходну"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Још радњи"</string>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
index 6fc1224..1a8e757 100644
--- a/v17/leanback/res/values-sv/strings.xml
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Säg det du söker efter"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Sök i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tala för att söka i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Spela upp"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausa"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Snabbspola framåt"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spola framåt %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spola tillbaka"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spola tillbaka %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Hoppa till nästa"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Hoppa till föregående"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Fler åtgärder"</string>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
index b6d44cf..17c7480 100644
--- a/v17/leanback/res/values-sw/strings.xml
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tamka ili utafute"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tafuta <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tamka ili utafute <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Google Play"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Sitisha"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Peleka mbele Haraka"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Peleka Mbele %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rudisha nyuma"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Peleka nyuma %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ruka Inayofuata"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ruka Iliyotangulia"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Vitendo zaidi"</string>
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta-rIN/strings.xml
index e60092c..9472522 100644
--- a/v17/leanback/res/values-ta-rIN/strings.xml
+++ b/v17/leanback/res/values-ta-rIN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"தேட, பேசவும்"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ஐத் தேடுக"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ஐத் தேட, பேசவும்"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"இயக்கு"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"இடைநிறுத்து"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"வேகமாக முன் நகர்த்து"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX வேகத்தில் முன்செல்"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"வேகமாக பின் நகர்த்து"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX வேகத்தில் பின்செல்"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"அடுத்ததைத் தவிர்"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"முந்தையதைத் தவிர்"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"மேலும் செயல்கள்"</string>
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te-rIN/strings.xml
index 762d13a..f71e8cb 100644
--- a/v17/leanback/res/values-te-rIN/strings.xml
+++ b/v17/leanback/res/values-te-rIN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"శోధించడానికి చదివి వినిపించండి"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ని శోధించండి"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ని శోధించడానికి చదివి వినిపించండి"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"ప్లే చేయి"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"పాజ్ చేయి"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"వేగంగా ఫార్వార్డ్ చేయి"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX ఫాస్ట్ ఫార్వార్డ్ చేయి"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"రివైండ్ చేయి"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX రివైండ్ చేయి"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"తదుపరి దానికి దాటవేయి"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"మునుపటి దానికి దాటవేయి"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"మరిన్ని చర్యలు"</string>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
index 5ba438d..581bac0 100644
--- a/v17/leanback/res/values-th/strings.xml
+++ b/v17/leanback/res/values-th/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"พูดเพื่อค้นหา"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ค้นหา <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"พูดเพื่อค้นหา <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"เล่น"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"หยุดชั่วคราว"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"กรอไปข้างหน้า"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"กรอไปข้างหน้า %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"กรอกลับ"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"กรอกลับ %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ข้ามไปรายการถัดไป"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ข้ามไปรายการก่อนหน้า"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"การทำงานเพิ่มเติม"</string>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
index b90a544..c4e15ec 100644
--- a/v17/leanback/res/values-tl/strings.xml
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Magsalita upang maghanap"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Hanapin ang <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Magsalita upang hanapin ang <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"I-play"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"I-pause"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"I-fast Forward"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"I-fast Forward %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"I-rewind"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"I-rewind %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Laktawan ang Susunod"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Laktawan ang Nakaraan"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Higit Pang Mga Pagkilos"</string>
@@ -40,6 +44,6 @@
<string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"I-disable ang Shuffle"</string>
<string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"I-enable ang Mataas na Kalidad"</string>
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"I-disable ang Mataas na Kalidad"</string>
- <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"I-enable ang Paglalagay ng Nakasarang Caption"</string>
- <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"I-disable ang Paglalagay ng Nakasarang Caption"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"I-enable ang Paglalagay ng Subtitle"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"I-disable ang Paglalagay ng Subtitle"</string>
</resources>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
index 77c846b..4671058 100644
--- a/v17/leanback/res/values-tr/strings.xml
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Arama yapmak için konuşun"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Ara: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Aramak için konuşun: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Oynat"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Duraklat"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"İleri Sar"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX İleri Sar"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Geri Sar"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX Geri Sar"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Sonrakine Atla"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Öncekine Atla"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Diğer İşlemler"</string>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
index 127c005..79b2782 100644
--- a/v17/leanback/res/values-uk/strings.xml
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Продиктуйте пошуковий запит"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Шукати: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Продиктуйте запит для пошуку: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Відтворити"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Призупинити"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Перемотати вперед"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Перемотати вперед %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Перемотати назад"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Перемотати назад %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Пропустити наступний елемент"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Пропустити попередній елемент"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Інші дії"</string>
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur-rPK/strings.xml
index d13641d..b670251 100644
--- a/v17/leanback/res/values-ur-rPK/strings.xml
+++ b/v17/leanback/res/values-ur-rPK/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"تلاش کرنے کیلئے بولیں"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> تلاش کریں"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> تلاش کرنے کیلئے بولیں"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"چلائیں"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"موقوف کریں"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"تیزی سے فارورڈ کریں"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"تیزی سے فارورڈ کریں %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ریوائینڈ کریں"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"ریوائنڈ کریں %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"اگلے پر جائیں"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"پچھلے پر جائیں"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"مزید کارروائیاں"</string>
@@ -40,6 +44,6 @@
<string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"شفل کو غیر فعال کریں"</string>
<string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"اعلی معیار کو فعال کریں"</string>
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"اعلی معیار کو غیر فعال کریں"</string>
- <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ذیلی سرخیاں لگانے کو فعال کریں"</string>
- <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ذیلی سرخیاں لگانے کو غیر فعال کریں"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"سب ٹائٹلز کو فعال کریں"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"سب ٹائٹلز کو غیر فعال کریں"</string>
</resources>
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz-rUZ/strings.xml
index cf16b8d..235d88f 100644
--- a/v17/leanback/res/values-uz-rUZ/strings.xml
+++ b/v17/leanback/res/values-uz-rUZ/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Qidirish uchun gapiring"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Qidirish: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Qidirish uchun ayting: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Ijro qilish"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"To‘xtatib turish"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Oldinga o‘tkazish"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX tezlikda oldinga o‘tkazish"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Orqaga qaytarish"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX tezlikda orqaga qaytarish"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Keyingisiga o‘tish"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Avvalgisiga qaytish"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Boshqa amallar"</string>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
index b265491..201d137 100644
--- a/v17/leanback/res/values-vi/strings.xml
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Nói để tìm kiếm"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tìm kiếm <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Nói để tìm kiếm <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Phát"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Tạm dừng"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Tua nhanh"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Tua đi %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Tua lại"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Tua lại %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Chuyển đến mục tiếp theo"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Chuyển về mục trước"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Tác vụ khác"</string>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
index 3c57156..276e7bb 100644
--- a/v17/leanback/res/values-zh-rCN/strings.xml
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"说话即可开始搜索"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜索<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"说话即可在<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>中搜索"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d 倍速"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d 倍速"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"播放"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"暂停"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"快进"</string>
- <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"回放"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$d 倍速快进"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"快退"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$d 倍速快退"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"跳至下一个"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"跳至上一个"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"更多操作"</string>
@@ -35,11 +39,11 @@
<string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"选择踩操作"</string>
<string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重复播放"</string>
<string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重复播放全部"</string>
- <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重复播放单个视频"</string>
- <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"启用随机播放"</string>
- <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"停用随机播放"</string>
- <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"启用高画质模式"</string>
- <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"停用高画质模式"</string>
- <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"启用特制字幕"</string>
- <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"关闭特制字幕"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重复播放一项"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"开启随机播放"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"关闭随机播放"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"开启高画质模式"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"关闭高画质模式"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"开启字幕"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"关闭字幕"</string>
</resources>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
index 88776c5..5e87989 100644
--- a/v17/leanback/res/values-zh-rHK/strings.xml
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"使用語音搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"播放"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"暫停"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"向前快轉"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"快轉 %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"向後倒轉"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"倒帶 %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"移至下一個媒體項目"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"移至上一個媒體項目"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"更多動作"</string>
@@ -33,7 +37,7 @@
<string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"選取喜歡"</string>
<string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"取消選取不喜歡"</string>
<string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"選取不喜歡"</string>
- <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重複播放任何媒體項目"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重複播放"</string>
<string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重複播放所有媒體項目"</string>
<string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重複播放一個媒體項目"</string>
<string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"啟用隨機播放"</string>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
index c1378a2..67efc40 100644
--- a/v17/leanback/res/values-zh-rTW/strings.xml
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -22,20 +22,24 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"使用語音搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"播放"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"暫停"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"向前快轉"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"快轉 %1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"倒轉"</string>
- <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"跳至下一個媒體項目"</string>
- <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"跳至上一個媒體項目"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"倒轉 %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"跳至下一個項目"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"跳至上一個項目"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"更多動作"</string>
<string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"取消選取喜歡"</string>
<string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"選取喜歡"</string>
<string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"取消選取不喜歡"</string>
<string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"選取不喜歡"</string>
<string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重複播放"</string>
- <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重複播放所有媒體項目"</string>
- <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重複播放單一媒體項目"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重複播放所有項目"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重複播放單一項目"</string>
<string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"啟用隨機播放"</string>
<string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"停用隨機播放"</string>
<string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"啟用高品質播放"</string>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
index e4b7a8e..f17455d 100644
--- a/v17/leanback/res/values-zu/strings.xml
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -22,10 +22,14 @@
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Khuluma ukuze useshe"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Sesha i-<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Khuluma ukuze useshe i-<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Dlala"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"Misa isikhashana"</string>
<string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Iya phambili ngokushesha"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Mikisa phambili ngokushesha i-%1$dX"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Buyisela emuva"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Mikisa emuva i-%1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Yeqa okulandelayo"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Yeqa kwangaphambilini"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Izenzo eziningi"</string>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index 47211ea..7038fff 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -148,12 +148,49 @@
<attr name="closed_captioning" format="reference"/>
</declare-styleable>
+ <declare-styleable name="lbSlide">
+ <!-- A duplication of Slide attribute slideEdge for KitKat -->
+ <attr name="lb_slideEdge">
+ <!-- Slide to and from the left edge of the Scene. -->
+ <enum name="left" value="0x03" />
+ <!-- Slide to and from the top edge of the Scene. -->
+ <enum name="top" value="0x30" />
+ <!-- Slide to and from the right edge of the Scene. -->
+ <enum name="right" value="0x05" />
+ <!-- Slide to and from the bottom edge of the Scene. -->
+ <enum name="bottom" value="0x50" />
+ <!-- Slide to and from the x-axis position at the start of the Scene root. -->
+ <enum name="start" value="0x00800003"/>
+ <!-- Slide to and from the x-axis position at the end of the Scene root. -->
+ <enum name="end" value="0x00800005"/>
+ </attr>
+ <attr name="android:duration" />
+ <attr name="android:startDelay" />
+ <attr name="android:interpolator" />
+ </declare-styleable>
+
+ <declare-styleable name="lbResizingTextView">
+ <!-- Conditions used to trigger text resizing -->
+ <attr name="resizeTrigger">
+ <!-- Resize text whenever it lays out into the maximum number of lines -->
+ <flag name="maxLines" value="0x01" />
+ </attr>
+ <!-- Text size for resized text -->
+ <attr name="resizedTextSize" format="dimension" />
+ <!-- Whether to maintain the same line spacing when text is resized, default is false -->
+ <attr name="maintainLineSpacing" format="boolean" />
+ <!-- Adjustment to top padding for resized text -->
+ <attr name="resizedPaddingAdjustmentTop" format="dimension" />
+ <!-- Adjustment to bottom padding for resized text -->
+ <attr name="resizedPaddingAdjustmentBottom" format="dimension" />
+ </declare-styleable>
+
<declare-styleable name="LeanbackTheme">
- <!-- left padding of BrowseFragment, RowsFragment, DetailsFragment -->
- <attr name="browsePaddingLeft" format="dimension" />
- <!-- right padding of BrowseFragment, RowsFragment, DetailsFragment -->
- <attr name="browsePaddingRight" format="dimension" />
+ <!-- start padding of BrowseFragment, RowsFragment, DetailsFragment -->
+ <attr name="browsePaddingStart" format="dimension" />
+ <!-- end padding of BrowseFragment, RowsFragment, DetailsFragment -->
+ <attr name="browsePaddingEnd" format="dimension" />
<!-- top padding of BrowseFragment -->
<attr name="browsePaddingTop" format="dimension" />
<!-- bottom padding of BrowseFragment -->
@@ -204,6 +241,7 @@
<!-- for playback controls -->
<attr name="playbackControlsButtonStyle" format="reference" />
+ <attr name="playbackControlButtonLabelStyle" format="reference" />
<attr name="playbackControlsTimeStyle" format="reference" />
<!-- style for a vertical grid of items -->
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
index 46a5fde..ba65d2f 100644
--- a/v17/leanback/res/values/colors.xml
+++ b/v17/leanback/res/values/colors.xml
@@ -32,12 +32,11 @@
<color name="lb_error_background_color_opaque">#262626</color>
<color name="lb_error_background_color_translucent">#E6000000</color>
- <color name="lb_error_message_color_on_opaque">#80EEEEEE</color>
- <color name="lb_error_message_color_on_translucent">#80555555</color>
+ <color name="lb_error_message">#80EEEEEE</color>
<color name="lb_action_text_color">#EEEEEE</color>
- <color name="lb_search_bar_text">#FFEEEEEE</color>
+ <color name="lb_search_bar_text">#80EEEEEE</color>
<color name="lb_search_bar_text_speech_mode">#FF444444</color>
<color name="lb_search_bar_hint">#FF888888</color>
<color name="lb_search_bar_hint_speech_mode">#66222222</color>
@@ -50,12 +49,13 @@
<color name="lb_basic_card_bg_color">#FF3B3B3B</color>
<color name="lb_basic_card_info_bg_color">#FF3B3B3B</color>
<color name="lb_basic_card_title_text_color">#FFEEEEEE</color>
- <color name="lb_basic_card_content_text_color">#FF888888</color>
+ <color name="lb_basic_card_content_text_color">#99EEEEEE</color>
<color name="lb_default_brand_color">#FF455A64</color>
<color name="lb_default_search_color">#FFFFAA3F</color>
<color name="lb_control_button_color">#66EEEEEE</color>
+ <color name="lb_control_button_text">#EEEEEE</color>
<color name="lb_playback_progress_color_no_theme">#ff40c4ff</color>
<color name="lb_playback_icon_highlight_no_theme">#ff40c4ff</color>
<color name="lb_playback_secondary_progress_color">#33FFFFFF</color>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 5e0b4ee..3fef5ee 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -17,9 +17,9 @@
<resources>
<dimen name="lb_list_row_height">224dp</dimen>
- <dimen name="lb_browse_padding_left">56dp</dimen>
+ <dimen name="lb_browse_padding_start">56dp</dimen>
<dimen name="lb_browse_padding_top">27dp</dimen>
- <dimen name="lb_browse_padding_right">56dp</dimen>
+ <dimen name="lb_browse_padding_end">56dp</dimen>
<dimen name="lb_browse_padding_bottom">48dp</dimen>
<dimen name="lb_browse_rows_margin_start">238dp</dimen>
<dimen name="lb_browse_rows_margin_top">167dp</dimen>
@@ -27,9 +27,8 @@
<dimen name="lb_vertical_grid_padding_bottom">87dp</dimen>
<dimen name="lb_browse_title_height">60dp</dimen>
- <dimen name="lb_browse_title_icon_height">52dp</dimen>
- <dimen name="lb_browse_title_icon_width">52dp</dimen>
- <dimen name="lb_browse_title_icon_margin_right">52dp</dimen>
+ <dimen name="lb_browse_title_icon_max_width">584dp</dimen>
+ <dimen name="lb_browse_title_icon_height">60dp</dimen>
<dimen name="lb_browse_title_text_size">44sp</dimen>
<dimen name="lb_browse_title_text_width">584dp</dimen>
@@ -44,7 +43,7 @@
<dimen name="lb_browse_header_text_size">20sp</dimen>
<dimen name="lb_browse_header_height">24dp</dimen>
<dimen name="lb_browse_header_fading_length">12dp</dimen>
- <dimen name="lb_browse_header_padding_right">8dp</dimen>
+ <dimen name="lb_browse_header_padding_end">8dp</dimen>
<item name="lb_browse_header_select_duration" format="integer" type="dimen">150</item>
<item name="lb_browse_header_unselect_alpha" type="fraction">50%</item>
@@ -65,25 +64,28 @@
<dimen name="lb_details_overview_height_large">274dp</dimen>
<dimen name="lb_details_overview_height_small">159dp</dimen>
- <dimen name="lb_details_overview_margin_left">132dp</dimen>
- <dimen name="lb_details_overview_margin_right">132dp</dimen>
+ <dimen name="lb_details_overview_margin_start">132dp</dimen>
+ <dimen name="lb_details_overview_margin_end">132dp</dimen>
<dimen name="lb_details_overview_margin_bottom">40dp</dimen>
<dimen name="lb_details_overview_description_margin_top">24dp</dimen>
- <dimen name="lb_details_overview_description_margin_left">24dp</dimen>
- <dimen name="lb_details_overview_description_margin_right">24dp</dimen>
+ <dimen name="lb_details_overview_description_margin_start">24dp</dimen>
+ <dimen name="lb_details_overview_description_margin_end">24dp</dimen>
<dimen name="lb_details_overview_description_margin_bottom">12dp</dimen>
<dimen name="lb_details_overview_image_margin_horizontal">24dp</dimen>
<dimen name="lb_details_overview_image_margin_vertical">24dp</dimen>
<dimen name="lb_details_overview_action_items_margin">16dp</dimen>
<item name="lb_details_overview_action_select_duration" format="integer" type="dimen">150</item>
- <dimen name="lb_details_overview_actions_padding_left">294dp</dimen>
- <dimen name="lb_details_overview_actions_padding_right">132dp</dimen>
+ <dimen name="lb_details_overview_actions_padding_start">294dp</dimen>
+ <dimen name="lb_details_overview_actions_padding_end">132dp</dimen>
<dimen name="lb_details_overview_actions_height">56dp</dimen>
<dimen name="lb_details_overview_actions_fade_size">16dp</dimen>
<dimen name="lb_details_rows_align_top">167dp</dimen>
<dimen name="lb_details_description_title_text_size">34sp</dimen>
+ <dimen name="lb_details_description_title_resized_text_size">28sp</dimen>
+ <dimen name="lb_details_description_title_padding_adjust_top">-1dp</dimen>
+ <dimen name="lb_details_description_title_padding_adjust_bottom">2dp</dimen>
<dimen name="lb_details_description_subtitle_text_size">16sp</dimen>
<dimen name="lb_details_description_body_text_size">14sp</dimen>
<dimen name="lb_details_description_title_line_spacing">40dp</dimen>
@@ -100,8 +102,8 @@
<dimen name="lb_action_1_line_height">36dp</dimen>
<dimen name="lb_action_2_lines_height">56dp</dimen>
<dimen name="lb_action_padding_horizontal">24dp</dimen>
- <dimen name="lb_action_with_icon_padding_left">14dp</dimen>
- <dimen name="lb_action_with_icon_padding_right">20dp</dimen>
+ <dimen name="lb_action_with_icon_padding_start">14dp</dimen>
+ <dimen name="lb_action_with_icon_padding_end">20dp</dimen>
<dimen name="lb_action_icon_margin">12dp</dimen>
<dimen name="lb_action_text_size">16sp</dimen>
<dimen name="lb_action_button_corner_radius">2dp</dimen>
@@ -111,8 +113,8 @@
<dimen name="lb_playback_major_fade_translate_y">200dp</dimen>
<dimen name="lb_playback_minor_fade_translate_y">16dp</dimen>
<dimen name="lb_playback_controls_card_height">176dp</dimen>
- <dimen name="lb_playback_controls_margin_left">132dp</dimen>
- <dimen name="lb_playback_controls_margin_right">132dp</dimen>
+ <dimen name="lb_playback_controls_margin_start">132dp</dimen>
+ <dimen name="lb_playback_controls_margin_end">132dp</dimen>
<dimen name="lb_playback_controls_margin_bottom">20dp</dimen>
<dimen name="lb_playback_description_margin_top">24dp</dimen>
<dimen name="lb_playback_description_margin_start">24dp</dimen>
@@ -131,6 +133,7 @@
<dimen name="lb_control_button_secondary_height">48dp</dimen>
<dimen name="lb_control_icon_width">32dp</dimen>
<dimen name="lb_control_icon_height">32dp</dimen>
+ <dimen name="lb_control_button_text_size">22sp</dimen>
<dimen name="lb_error_image_max_height">120dp</dimen>
<integer name="lb_error_message_max_lines">1</integer>
@@ -144,27 +147,27 @@
<!-- Search bar -->
<dimen name="lb_search_bar_height">60dp</dimen>
- <dimen name="lb_search_bar_padding_left">56dp</dimen>
+ <dimen name="lb_search_bar_padding_start">56dp</dimen>
<dimen name="lb_search_bar_padding_top">27dp</dimen>
- <dimen name="lb_search_bar_text_size">22sp</dimen>
+ <dimen name="lb_search_bar_text_size">18sp</dimen>
<dimen name="lb_search_bar_unfocused_text_size">18sp</dimen>
<dimen name="lb_search_bar_items_layout_margin_top">27dp</dimen>
<dimen name="lb_search_bar_items_width">600dp</dimen>
<dimen name="lb_search_bar_items_height">56dp</dimen>
- <dimen name="lb_search_bar_items_margin_left">70dp</dimen>
+ <dimen name="lb_search_bar_items_margin_start">70dp</dimen>
<dimen name="lb_search_bar_inner_margin_top">2dp</dimen>
<dimen name="lb_search_bar_inner_margin_bottom">2dp</dimen>
<dimen name="lb_search_bar_icon_height">32dp</dimen>
<dimen name="lb_search_bar_icon_width">32dp</dimen>
- <dimen name="lb_search_bar_icon_margin_left">16dp</dimen>
- <dimen name="lb_search_bar_edit_text_margin_left">24dp</dimen>
- <dimen name="lb_search_bar_hint_margin_left">52dp</dimen>
+ <dimen name="lb_search_bar_icon_margin_start">16dp</dimen>
+ <dimen name="lb_search_bar_edit_text_margin_start">24dp</dimen>
+ <dimen name="lb_search_bar_hint_margin_start">52dp</dimen>
<!-- Search Fragment -->
<dimen name="lb_search_browse_rows_align_top">120dp</dimen>
- <dimen name="lb_search_browse_row_padding_left">56dp</dimen>
+ <dimen name="lb_search_browse_row_padding_start">56dp</dimen>
<dimen name="lb_search_orb_size">52dp</dimen>
<item name="lb_search_orb_focused_zoom" type="fraction">120%</item>
@@ -173,12 +176,12 @@
<dimen name="lb_search_orb_margin_top">4dp</dimen>
<dimen name="lb_search_orb_margin_bottom">4dp</dimen>
- <dimen name="lb_search_orb_margin_left">4dp</dimen>
- <dimen name="lb_search_orb_margin_right">4dp</dimen>
+ <dimen name="lb_search_orb_margin_start">4dp</dimen>
+ <dimen name="lb_search_orb_margin_end">4dp</dimen>
<dimen name="lb_search_bar_speech_orb_size">52dp</dimen>
<item name="lb_search_bar_speech_orb_max_level_zoom" type="fraction">144%</item>
- <dimen name="lb_search_bar_speech_orb_margin_left">56dp</dimen>
+ <dimen name="lb_search_bar_speech_orb_margin_start">56dp</dimen>
<!-- BasicCardView -->
<dimen name="lb_basic_card_main_width">140dp</dimen>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
index 289927f..2cd0ff11 100644
--- a/v17/leanback/res/values/strings.xml
+++ b/v17/leanback/res/values/strings.xml
@@ -25,6 +25,10 @@
<string name="lb_search_bar_hint_with_title">Search <xliff:g id="search context">%1$s</xliff:g></string>
<!-- Hint showing in the empty search bar using a provided context (usually the application name) while in voice input mode [CHAR LIMIT=40] -->
<string name="lb_search_bar_hint_with_title_speech">Speak to search <xliff:g id="search context">%1$s</xliff:g></string>
+ <!-- Onscreen label for the control button to fast forward media playback at a given speed multiplier -->
+ <string name="lb_control_display_fast_forward_multiplier">%1$dX</string>
+ <!-- Onscreen label for the control button to rewind media playback at a given speed multiplier -->
+ <string name="lb_control_display_rewind_multiplier">%1$dX</string>
<!-- Talkback label for the control button to start media playback -->
<string name="lb_playback_controls_play">Play</string>
@@ -32,8 +36,12 @@
<string name="lb_playback_controls_pause">Pause</string>
<!-- Talkback label for the control button to fast forward media playback -->
<string name="lb_playback_controls_fast_forward">Fast Forward</string>
+ <!-- Talkback label for the control button to fast forward media playback at a given speed multiplier -->
+ <string name="lb_playback_controls_fast_forward_multiplier">Fast Forward %1$dX</string>
<!-- Talkback label for the control button to start rewind playback -->
<string name="lb_playback_controls_rewind">Rewind</string>
+ <!-- Talkback label for the control button to rewind media playback at a given speed multiplier -->
+ <string name="lb_playback_controls_rewind_multiplier">Rewind %1$dX</string>
<!-- Talkback label for the control button to skip to the next media item -->
<string name="lb_playback_controls_skip_next">Skip Next</string>
<!-- Talkback label for the control button to skip to the previous media item -->
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index f1e883e..03be3ac 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -71,9 +71,15 @@
<item name="android:fontFamily">sans-serif</item>
</style>
+ <style name="TextAppearance.Leanback.PlaybackControlLabel">
+ <item name="android:textSize">@dimen/lb_control_button_text_size</item>
+ <item name="android:textColor">@color/lb_control_button_text</item>
+ <item name="android:fontFamily">sans-serif</item>
+ </style>
+
<style name="TextAppearance.Leanback.ErrorMessage">
<item name="android:textSize">@dimen/lb_error_message_text_size</item>
- <item name="android:textColor">@color/lb_error_message_color_on_opaque</item>
+ <item name="android:textColor">@color/lb_error_message</item>
<item name="android:fontFamily">sans-serif</item>
</style>
@@ -98,13 +104,12 @@
<style name="Widget.Leanback.Title.Text">
<item name="android:singleLine">true</item>
- <item name="android:gravity">right</item>
+ <item name="android:gravity">end</item>
<item name="android:ellipsize">end</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.Title</item>
</style>
<style name="Widget.Leanback.Title.Icon">
- <item name="android:scaleType">fitEnd</item>
</style>
<!-- HeadersFragment -->
@@ -121,7 +126,7 @@
<style name="Widget.Leanback.GridItems" />
<style name="Widget.Leanback.Headers.VerticalGridView" >
- <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
+ <item name="android:paddingStart">?attr/browsePaddingStart</item>
<item name="android:clipToPadding">false</item>
<item name="focusOutFront">true</item>
<item name="focusOutEnd">true</item>
@@ -150,8 +155,8 @@
<item name="android:clipToPadding">false</item>
<item name="android:focusable">true</item>
<item name="android:focusableInTouchMode">true</item>
- <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
- <item name="android:paddingRight">?attr/browsePaddingRight</item>
+ <item name="android:paddingStart">?attr/browsePaddingStart</item>
+ <item name="android:paddingEnd">?attr/browsePaddingEnd</item>
<item name="android:paddingBottom">@dimen/lb_browse_item_vertical_margin</item>
<item name="android:paddingTop">@dimen/lb_browse_item_vertical_margin</item>
<item name="horizontalMargin">@dimen/lb_browse_item_horizontal_margin</item>
@@ -164,8 +169,8 @@
<item name="android:clipToPadding">false</item>
<item name="android:focusable">true</item>
<item name="android:focusableInTouchMode">true</item>
- <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
- <item name="android:paddingRight">?attr/browsePaddingRight</item>
+ <item name="android:paddingStart">?attr/browsePaddingStart</item>
+ <item name="android:paddingEnd">?attr/browsePaddingEnd</item>
<item name="android:paddingBottom">@dimen/lb_vertical_grid_padding_bottom</item>
<item name="android:paddingTop">?attr/browseRowsMarginTop</item>
<item name="android:gravity">center_horizontal</item>
@@ -206,6 +211,10 @@
<item name="android:maxLines">@integer/lb_details_description_title_max_lines</item>
<item name="android:includeFontPadding">false</item>
<item name="android:ellipsize">end</item>
+ <item name="resizeTrigger">maxLines</item>
+ <item name="resizedTextSize">@dimen/lb_details_description_title_resized_text_size</item>
+ <item name="resizedPaddingAdjustmentTop">@dimen/lb_details_description_title_padding_adjust_top</item>
+ <item name="resizedPaddingAdjustmentBottom">@dimen/lb_details_description_title_padding_adjust_bottom</item>
</style>
<style name="Widget.Leanback.DetailsDescriptionSubtitleStyle">
@@ -230,8 +239,8 @@
<item name="android:drawablePadding">@dimen/lb_action_icon_margin</item>
<item name="android:focusable">true</item>
<item name="android:focusableInTouchMode">true</item>
- <item name="android:paddingLeft">@dimen/lb_action_padding_horizontal</item>
- <item name="android:paddingRight">@dimen/lb_action_padding_horizontal</item>
+ <item name="android:paddingStart">@dimen/lb_action_padding_horizontal</item>
+ <item name="android:paddingEnd">@dimen/lb_action_padding_horizontal</item>
</style>
<style name="Widget.Leanback.PlaybackControlsButtonStyle" >
@@ -239,6 +248,10 @@
<item name="android:focusableInTouchMode">true</item>
</style>
+ <style name="Widget.Leanback.PlaybackControlLabelStyle">
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackControlLabel</item>
+ </style>
+
<style name="Widget.Leanback.PlaybackControlsTimeStyle">
<item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackControlsTime</item>
</style>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 8b66edf..0503e17 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -31,8 +31,8 @@
<item name="baseCardViewStyle">@style/Widget.Leanback.BaseCardViewStyle</item>
<item name="imageCardViewStyle">@style/Widget.Leanback.ImageCardViewStyle</item>
- <item name="browsePaddingLeft">@dimen/lb_browse_padding_left</item>
- <item name="browsePaddingRight">@dimen/lb_browse_padding_right</item>
+ <item name="browsePaddingStart">@dimen/lb_browse_padding_start</item>
+ <item name="browsePaddingEnd">@dimen/lb_browse_padding_end</item>
<item name="browsePaddingTop">@dimen/lb_browse_padding_top</item>
<item name="browsePaddingBottom">@dimen/lb_browse_padding_bottom</item>
<item name="browseRowsMarginStart">@dimen/lb_browse_rows_margin_start</item>
@@ -61,6 +61,7 @@
<item name="detailsDescriptionBodyStyle">@style/Widget.Leanback.DetailsDescriptionBodyStyle</item>
<item name="detailsActionButtonStyle">@style/Widget.Leanback.DetailsActionButtonStyle</item>
<item name="playbackControlsButtonStyle">@style/Widget.Leanback.PlaybackControlsButtonStyle</item>
+ <item name="playbackControlButtonLabelStyle">@style/Widget.Leanback.PlaybackControlLabelStyle</item>
<item name="playbackControlsTimeStyle">@style/Widget.Leanback.PlaybackControlsTimeStyle</item>
<item name="playbackControlsActionIcons">@style/Widget.Leanback.PlaybackControlsActionIconsStyle</item>
@@ -71,14 +72,31 @@
<item name="defaultSearchBrightColor">@null</item>
<item name="defaultSearchIcon">@null</item>
+ <!-- android:windowSharedElementEnterTransition is kept for backward compatibility for apps still refer
+ to Theme.Leanback, app should use Theme.Leanback.Details instead -->
<item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
- <item name="android:windowEnterTransition">@transition/lb_enter_transition</item>
+ <!-- android:windowSharedElementReturnTransition is kept for backward compatibility for apps still refer
+ to Theme.Leanback, app should use Theme.Leanback.Details instead -->
<item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
+ <item name="android:windowEnterTransition">@transition/lb_enter_transition</item>
<item name="android:windowReturnTransition">@transition/lb_return_transition</item>
+ <item name="android:windowTransitionBackgroundFadeDuration">350</item>
<item name="overlayDimMaskColor">@color/lb_view_dim_mask_color</item>
<item name="overlayDimActiveLevel">@fraction/lb_view_active_level</item>
<item name="overlayDimDimmedLevel">@fraction/lb_view_dimmed_level</item>
</style>
+ <style name="Theme.Leanback.Browse" parent="Theme.Leanback">
+ <item name="android:windowEnterTransition">@transition/lb_browse_enter_transition</item>
+ <item name="android:windowReturnTransition">@transition/lb_browse_return_transition</item>
+ </style>
+
+ <style name="Theme.Leanback.Details" parent="Theme.Leanback">
+ <item name="android:windowEnterTransition">@transition/lb_details_enter_transition</item>
+ <item name="android:windowReturnTransition">@transition/lb_details_return_transition</item>
+ <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
+ <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
+ </style>
+
</resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
index 5cf75f3..7346753 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -40,6 +40,7 @@
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
+import android.support.v4.app.FragmentActivity;
import android.support.v4.content.ContextCompat;
/**
@@ -337,6 +338,9 @@
* for this Activity.
*/
public static BackgroundManager getInstance(Activity activity) {
+ if (activity instanceof FragmentActivity) {
+ return getSupportInstance((FragmentActivity) activity);
+ }
BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
.findFragmentByTag(FRAGMENT_TAG);
if (fragment != null) {
@@ -347,10 +351,24 @@
// manager is null: this is a fragment restored by FragmentManager,
// fall through to create a BackgroundManager attach to it.
}
- return new BackgroundManager(activity);
+ return new BackgroundManager(activity, false);
}
- private BackgroundManager(Activity activity) {
+ private static BackgroundManager getSupportInstance(FragmentActivity activity) {
+ BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
+ .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
+ if (fragment != null) {
+ BackgroundManager manager = fragment.getBackgroundManager();
+ if (manager != null) {
+ return manager;
+ }
+ // manager is null: this is a fragment restored by FragmentManager,
+ // fall through to create a BackgroundManager attach to it.
+ }
+ return new BackgroundManager(activity, true);
+ }
+
+ private BackgroundManager(Activity activity, boolean isSupportFragmentActivity) {
mContext = activity;
mService = BackgroundContinuityService.getInstance();
mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
@@ -365,7 +383,11 @@
}
ta.recycle();
- createFragment(activity);
+ if (isSupportFragmentActivity) {
+ createSupportFragment((FragmentActivity) activity);
+ } else {
+ createFragment(activity);
+ }
}
private void createFragment(Activity activity) {
@@ -384,6 +406,23 @@
fragment.setBackgroundManager(this);
}
+ private void createSupportFragment(FragmentActivity activity) {
+ // Use a fragment to ensure the background manager gets detached properly.
+ BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
+ .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
+ if (fragment == null) {
+ fragment = new BackgroundSupportFragment();
+ activity.getSupportFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG)
+ .commit();
+ } else {
+ if (fragment.getBackgroundManager() != null) {
+ throw new IllegalStateException("Created duplicated BackgroundManager for same " +
+ "activity, please use getInstance() instead");
+ }
+ }
+ fragment.setBackgroundManager(this);
+ }
+
/**
* Synchronizes state when the owning Activity is resumed.
*/
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java
new file mode 100644
index 0000000..3b6530a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java
@@ -0,0 +1,56 @@
+/* This file is auto-generated from BackgroundFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+
+/**
+ * Fragment used by the background manager.
+ * @hide
+ */
+public final class BackgroundSupportFragment extends Fragment {
+ private BackgroundManager mBackgroundManager;
+
+ void setBackgroundManager(BackgroundManager backgroundManager) {
+ mBackgroundManager = backgroundManager;
+ }
+
+ BackgroundManager getBackgroundManager() {
+ return mBackgroundManager;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // mBackgroundManager might be null:
+ // if BackgroundSupportFragment is just restored by FragmentManager,
+ // and user does not call BackgroundManager.getInstance() yet.
+ if (mBackgroundManager != null) {
+ mBackgroundManager.onActivityResume();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // mBackgroundManager might be null:
+ // if BackgroundSupportFragment is just restored by FragmentManager,
+ // and user does not call BackgroundManager.getInstance() yet.
+ if (mBackgroundManager != null) {
+ mBackgroundManager.detach();
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
new file mode 100644
index 0000000..07e6123
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * @hide
+ */
+class BaseFragment extends Fragment {
+
+ private boolean mEntranceTransitionEnabled = false;
+ private boolean mStartEntranceTransitionPending = false;
+ private Object mEntranceTransition;
+
+ static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (mStartEntranceTransitionPending) {
+ mStartEntranceTransitionPending = false;
+ startEntranceTransition();
+ }
+ }
+
+ /**
+ * Enables entrance transition.<p>
+ * Entrance transition is the standard slide-in transition that shows rows of data in
+ * browse screen and details screen.
+ * <p>
+ * The method is ignored before LOLLIPOP (API21).
+ * <p>
+ * This method must be called in or
+ * before onCreate(). Typically entrance transition should be enabled when savedInstance is
+ * null so that fragment restored from instanceState does not run an extra entrance transition.
+ * When the entrance transition is enabled, the fragment will make headers and content
+ * hidden initially.
+ * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
+ * the transition, otherwise the rows will be invisible forever.
+ * <p>
+ * It is similar to android:windowsEnterTransition and can be considered a late-executed
+ * android:windowsEnterTransition controlled by app. There are two reasons that app needs it:
+ * <li> Workaround the problem that activity transition is not available between launcher and
+ * app. Browse activity must programmatically start the slide-in transition.</li>
+ * <li> Separates DetailsOverviewRow transition from other rows transition. So that
+ * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
+ * to be loaded.</li>
+ * <p>
+ * Transition object is returned by createEntranceTransition(). Typically the app does not need
+ * override the default transition that browse and details provides.
+ */
+ public void prepareEntranceTransition() {
+ if (TransitionHelper.systemSupportsEntranceTransitions()) {
+ mEntranceTransitionEnabled = true;
+ }
+ }
+
+ /**
+ * Return true if entrance transition is enabled and not started yet.
+ * Entrance transition can only be executed once and isEntranceTransitionEnabled()
+ * is reset to false after entrance transition is started.
+ */
+ boolean isEntranceTransitionEnabled() {
+ return mEntranceTransitionEnabled;
+ }
+
+ /**
+ * Create entrance transition. Subclass can override to load transition from
+ * resource or construct manually. Typically app does not need to
+ * override the default transition that browse and details provides.
+ */
+ protected Object createEntranceTransition() {
+ return null;
+ }
+
+ /**
+ * Run entrance transition. Subclass may use TransitionManager to perform
+ * go(Scene) or beginDelayedTransition(). App should not override the default
+ * implementation of browse and details fragment.
+ */
+ protected void runEntranceTransition(Object entranceTransition) {
+ }
+
+ /**
+ * Callback when entrance transition is started.
+ */
+ protected void onEntranceTransitionStart() {
+ }
+
+ /**
+ * Callback when entrance transition is ended.
+ */
+ protected void onEntranceTransitionEnd() {
+ }
+
+ /**
+ * When fragment finishes loading data, it should call startEntranceTransition()
+ * to execute the entrance transition.
+ * startEntranceTransition() will start transition only if both two conditions
+ * are satisfied:
+ * <li> prepareEntranceTransition() was called.</li>
+ * <li> has not executed entrance transition yet.</li>
+ * <p>
+ * If startEntranceTransition() is called before onViewCreated(), it will be pending
+ * and executed when view is created.
+ */
+ public void startEntranceTransition() {
+ if (!mEntranceTransitionEnabled || mEntranceTransition != null) {
+ return;
+ }
+ // if view is not created yet, delay until onViewCreated()
+ if (getView() == null) {
+ mStartEntranceTransitionPending = true;
+ return;
+ }
+ // wait till views get their initial position before start transition
+ final View view = getView();
+ view.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ view.getViewTreeObserver().removeOnPreDrawListener(this);
+ internalCreateEntranceTransition();
+ mEntranceTransitionEnabled = false;
+ runEntranceTransition(mEntranceTransition);
+ return false;
+ }
+ });
+ view.invalidate();
+ }
+
+ void internalCreateEntranceTransition() {
+ mEntranceTransition = createEntranceTransition();
+ if (mEntranceTransition == null) {
+ return;
+ }
+ sTransitionHelper.setTransitionListener(mEntranceTransition, new TransitionListener() {
+ @Override
+ public void onTransitionStart(Object transition) {
+ onEntranceTransitionStart();
+ }
+ @Override
+ public void onTransitionEnd(Object transition) {
+ mEntranceTransition = null;
+ onEntranceTransitionEnd();
+ }
+ });
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
index 74778e3..48b81a6 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -35,10 +35,8 @@
private PresenterSelector mPresenterSelector;
private ItemBridgeAdapter mBridgeAdapter;
private int mSelectedPosition = -1;
- protected int mReparentHeaderId;
- protected boolean mInTransition;
- abstract protected int getLayoutResourceId();
+ abstract int getLayoutResourceId();
private final OnChildSelectedListener mRowSelectedListener = new OnChildSelectedListener() {
@Override
@@ -47,7 +45,7 @@
}
};
- protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+ void onRowSelected(ViewGroup parent, View view, int position, long id) {
}
@Override
@@ -58,7 +56,7 @@
return view;
}
- protected VerticalGridView findGridViewFromRoot(View view) {
+ VerticalGridView findGridViewFromRoot(View view) {
return (VerticalGridView) view;
}
@@ -112,17 +110,28 @@
/**
* Returns the bridge adapter.
*/
- protected final ItemBridgeAdapter getBridgeAdapter() {
+ final ItemBridgeAdapter getBridgeAdapter() {
return mBridgeAdapter;
}
/**
- * Set the selected item position.
+ * Sets the selected row position with smooth animation.
*/
public void setSelectedPosition(int position) {
+ setSelectedPosition(position, true);
+ }
+
+ /**
+ * Sets the selected row position.
+ */
+ public void setSelectedPosition(int position, boolean smooth) {
mSelectedPosition = position;
if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
- mVerticalGridView.setSelectedPositionSmooth(position);
+ if (smooth) {
+ mVerticalGridView.setSelectedPositionSmooth(position);
+ } else {
+ mVerticalGridView.setSelectedPosition(position);
+ }
}
}
@@ -130,7 +139,7 @@
return mVerticalGridView;
}
- protected void updateAdapter() {
+ void updateAdapter() {
mBridgeAdapter = null;
if (mAdapter != null) {
@@ -145,7 +154,7 @@
}
}
- protected Object getItem(Row row, int position) {
+ Object getItem(Row row, int position) {
if (row instanceof ListRow) {
return ((ListRow) row).getAdapter().get(position);
} else {
@@ -153,12 +162,7 @@
}
}
- void setReparentHeaderId(int reparentId) {
- mReparentHeaderId = reparentId;
- }
-
void onTransitionStart() {
- mInTransition = true;
if (mVerticalGridView != null) {
mVerticalGridView.setAnimateChildLayout(false);
mVerticalGridView.setPruneChild(false);
@@ -172,7 +176,6 @@
mVerticalGridView.setPruneChild(true);
mVerticalGridView.setFocusSearchDisabled(false);
}
- mInTransition = false;
}
void setItemAlignment() {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
new file mode 100644
index 0000000..08434fd
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -0,0 +1,201 @@
+/* This file is auto-generated from BaseRowFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An internal base class for a fragment containing a list of rows.
+ */
+abstract class BaseRowSupportFragment extends Fragment {
+ private ObjectAdapter mAdapter;
+ private VerticalGridView mVerticalGridView;
+ private PresenterSelector mPresenterSelector;
+ private ItemBridgeAdapter mBridgeAdapter;
+ private int mSelectedPosition = -1;
+
+ abstract int getLayoutResourceId();
+
+ private final OnChildSelectedListener mRowSelectedListener = new OnChildSelectedListener() {
+ @Override
+ public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+ onRowSelected(parent, view, position, id);
+ }
+ };
+
+ void onRowSelected(ViewGroup parent, View view, int position, long id) {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(getLayoutResourceId(), container, false);
+ mVerticalGridView = findGridViewFromRoot(view);
+ return view;
+ }
+
+ VerticalGridView findGridViewFromRoot(View view) {
+ return (VerticalGridView) view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ if (mBridgeAdapter != null) {
+ mVerticalGridView.setAdapter(mBridgeAdapter);
+ if (mSelectedPosition != -1) {
+ mVerticalGridView.setSelectedPosition(mSelectedPosition);
+ }
+ }
+ mVerticalGridView.setOnChildSelectedListener(mRowSelectedListener);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mVerticalGridView = null;
+ }
+
+ /**
+ * Set the presenter selector used to create and bind views.
+ */
+ public final void setPresenterSelector(PresenterSelector presenterSelector) {
+ mPresenterSelector = presenterSelector;
+ updateAdapter();
+ }
+
+ /**
+ * Get the presenter selector used to create and bind views.
+ */
+ public final PresenterSelector getPresenterSelector() {
+ return mPresenterSelector;
+ }
+
+ /**
+ * Sets the adapter for the fragment.
+ */
+ public final void setAdapter(ObjectAdapter rowsAdapter) {
+ mAdapter = rowsAdapter;
+ updateAdapter();
+ }
+
+ /**
+ * Returns the list of rows.
+ */
+ public final ObjectAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Returns the bridge adapter.
+ */
+ final ItemBridgeAdapter getBridgeAdapter() {
+ return mBridgeAdapter;
+ }
+
+ /**
+ * Sets the selected row position with smooth animation.
+ */
+ public void setSelectedPosition(int position) {
+ setSelectedPosition(position, true);
+ }
+
+ /**
+ * Sets the selected row position.
+ */
+ public void setSelectedPosition(int position, boolean smooth) {
+ mSelectedPosition = position;
+ if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+ if (smooth) {
+ mVerticalGridView.setSelectedPositionSmooth(position);
+ } else {
+ mVerticalGridView.setSelectedPosition(position);
+ }
+ }
+ }
+
+ final VerticalGridView getVerticalGridView() {
+ return mVerticalGridView;
+ }
+
+ void updateAdapter() {
+ mBridgeAdapter = null;
+
+ if (mAdapter != null) {
+ // If presenter selector is null, adapter ps will be used
+ mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
+ }
+ if (mVerticalGridView != null) {
+ mVerticalGridView.setAdapter(mBridgeAdapter);
+ if (mBridgeAdapter != null && mSelectedPosition != -1) {
+ mVerticalGridView.setSelectedPosition(mSelectedPosition);
+ }
+ }
+ }
+
+ Object getItem(Row row, int position) {
+ if (row instanceof ListRow) {
+ return ((ListRow) row).getAdapter().get(position);
+ } else {
+ return null;
+ }
+ }
+
+ void onTransitionStart() {
+ if (mVerticalGridView != null) {
+ mVerticalGridView.setAnimateChildLayout(false);
+ mVerticalGridView.setPruneChild(false);
+ mVerticalGridView.setFocusSearchDisabled(true);
+ }
+ }
+
+ void onTransitionEnd() {
+ if (mVerticalGridView != null) {
+ mVerticalGridView.setAnimateChildLayout(true);
+ mVerticalGridView.setPruneChild(true);
+ mVerticalGridView.setFocusSearchDisabled(false);
+ }
+ }
+
+ void setItemAlignment() {
+ if (mVerticalGridView != null) {
+ // align the top edge of item
+ mVerticalGridView.setItemAlignmentOffset(0);
+ mVerticalGridView.setItemAlignmentOffsetPercent(
+ VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+ }
+ }
+
+ void setWindowAlignmentFromTop(int alignedTop) {
+ if (mVerticalGridView != null) {
+ // align to a fixed position from top
+ mVerticalGridView.setWindowAlignmentOffset(alignedTop);
+ mVerticalGridView.setWindowAlignmentOffsetPercent(
+ VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+ mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
new file mode 100644
index 0000000..88439ef
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
@@ -0,0 +1,169 @@
+/* This file is auto-generated from BaseFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * @hide
+ */
+class BaseSupportFragment extends Fragment {
+
+ private boolean mEntranceTransitionEnabled = false;
+ private boolean mStartEntranceTransitionPending = false;
+ private Object mEntranceTransition;
+
+ static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (mStartEntranceTransitionPending) {
+ mStartEntranceTransitionPending = false;
+ startEntranceTransition();
+ }
+ }
+
+ /**
+ * Enables entrance transition.<p>
+ * Entrance transition is the standard slide-in transition that shows rows of data in
+ * browse screen and details screen.
+ * <p>
+ * The method is ignored before LOLLIPOP (API21).
+ * <p>
+ * This method must be called in or
+ * before onCreate(). Typically entrance transition should be enabled when savedInstance is
+ * null so that fragment restored from instanceState does not run an extra entrance transition.
+ * When the entrance transition is enabled, the fragment will make headers and content
+ * hidden initially.
+ * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
+ * the transition, otherwise the rows will be invisible forever.
+ * <p>
+ * It is similar to android:windowsEnterTransition and can be considered a late-executed
+ * android:windowsEnterTransition controlled by app. There are two reasons that app needs it:
+ * <li> Workaround the problem that activity transition is not available between launcher and
+ * app. Browse activity must programmatically start the slide-in transition.</li>
+ * <li> Separates DetailsOverviewRow transition from other rows transition. So that
+ * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
+ * to be loaded.</li>
+ * <p>
+ * Transition object is returned by createEntranceTransition(). Typically the app does not need
+ * override the default transition that browse and details provides.
+ */
+ public void prepareEntranceTransition() {
+ if (TransitionHelper.systemSupportsEntranceTransitions()) {
+ mEntranceTransitionEnabled = true;
+ }
+ }
+
+ /**
+ * Return true if entrance transition is enabled and not started yet.
+ * Entrance transition can only be executed once and isEntranceTransitionEnabled()
+ * is reset to false after entrance transition is started.
+ */
+ boolean isEntranceTransitionEnabled() {
+ return mEntranceTransitionEnabled;
+ }
+
+ /**
+ * Create entrance transition. Subclass can override to load transition from
+ * resource or construct manually. Typically app does not need to
+ * override the default transition that browse and details provides.
+ */
+ protected Object createEntranceTransition() {
+ return null;
+ }
+
+ /**
+ * Run entrance transition. Subclass may use TransitionManager to perform
+ * go(Scene) or beginDelayedTransition(). App should not override the default
+ * implementation of browse and details fragment.
+ */
+ protected void runEntranceTransition(Object entranceTransition) {
+ }
+
+ /**
+ * Callback when entrance transition is started.
+ */
+ protected void onEntranceTransitionStart() {
+ }
+
+ /**
+ * Callback when entrance transition is ended.
+ */
+ protected void onEntranceTransitionEnd() {
+ }
+
+ /**
+ * When fragment finishes loading data, it should call startEntranceTransition()
+ * to execute the entrance transition.
+ * startEntranceTransition() will start transition only if both two conditions
+ * are satisfied:
+ * <li> prepareEntranceTransition() was called.</li>
+ * <li> has not executed entrance transition yet.</li>
+ * <p>
+ * If startEntranceTransition() is called before onViewCreated(), it will be pending
+ * and executed when view is created.
+ */
+ public void startEntranceTransition() {
+ if (!mEntranceTransitionEnabled || mEntranceTransition != null) {
+ return;
+ }
+ // if view is not created yet, delay until onViewCreated()
+ if (getView() == null) {
+ mStartEntranceTransitionPending = true;
+ return;
+ }
+ // wait till views get their initial position before start transition
+ final View view = getView();
+ view.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ view.getViewTreeObserver().removeOnPreDrawListener(this);
+ internalCreateEntranceTransition();
+ mEntranceTransitionEnabled = false;
+ runEntranceTransition(mEntranceTransition);
+ return false;
+ }
+ });
+ view.invalidate();
+ }
+
+ void internalCreateEntranceTransition() {
+ mEntranceTransition = createEntranceTransition();
+ if (mEntranceTransition == null) {
+ return;
+ }
+ sTransitionHelper.setTransitionListener(mEntranceTransition, new TransitionListener() {
+ @Override
+ public void onTransitionStart(Object transition) {
+ onEntranceTransitionStart();
+ }
+ @Override
+ public void onTransitionEnd(Object transition) {
+ mEntranceTransition = null;
+ onEntranceTransitionEnd();
+ }
+ });
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index 1978277..2207ac6 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -14,8 +14,10 @@
package android.support.v17.leanback.app;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.LeanbackTransitionHelper;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
@@ -30,6 +32,7 @@
import android.support.v17.leanback.widget.OnItemSelectedListener;
import android.support.v17.leanback.widget.OnItemClickedListener;
import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.app.Activity;
import android.app.Fragment;
@@ -43,9 +46,11 @@
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+
import static android.support.v7.widget.RecyclerView.NO_POSITION;
/**
@@ -61,12 +66,12 @@
* <p>
* By default the BrowseFragment includes support for returning to the headers
* when the user presses Back. For Activities that customize {@link
- * Activity#onBackPressed()}, you must disable this default Back key support by
+ * android.app.Activity#onBackPressed()}, you must disable this default Back key support by
* calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
* use {@link BrowseFragment.BrowseTransitionListener} and
* {@link #startHeadersTransition(boolean)}.
*/
-public class BrowseFragment extends Fragment {
+public class BrowseFragment extends BaseFragment {
// BUNDLE attribute for saving header show/hide status when backstack is used:
static final String HEADER_STACK_INDEX = "headerStackIndex";
@@ -185,7 +190,7 @@
private String mWithHeadersBackStackName;
private boolean mShowingHeaders = true;
private boolean mCanShowHeaders = true;
- private int mContainerListMarginLeft;
+ private int mContainerListMarginStart;
private int mContainerListAlignTop;
private boolean mRowScaleEnabled = true;
private SearchOrbView.Colors mSearchAffordanceColors;
@@ -200,17 +205,14 @@
private PresenterSelector mHeaderPresenterSelector;
// transition related:
- private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
- private int mReparentHeaderId = View.generateViewId();
private Object mSceneWithTitle;
private Object mSceneWithoutTitle;
private Object mSceneWithHeaders;
private Object mSceneWithoutHeaders;
+ private Object mSceneAfterEntranceTransition;
private Object mTitleUpTransition;
private Object mTitleDownTransition;
private Object mHeadersTransition;
- private int mHeadersTransitionStartDelay;
- private int mHeadersTransitionDuration;
private BackStackListener mBackStackChangedListener;
private BrowseTransitionListener mBrowseTransitionListener;
@@ -521,31 +523,39 @@
new BrowseFrameLayout.OnFocusSearchListener() {
@Override
public View onFocusSearch(View focused, int direction) {
- // If headers fragment is disabled, just return null.
- if (!mCanShowHeaders) return null;
+ // if headers is running transition, focus stays
+ if (mCanShowHeaders && isInHeadersTransition()) {
+ return focused;
+ }
+ if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
final View searchOrbView = mTitleView.getSearchAffordanceView();
- // if headers is running transition, focus stays
- if (isInHeadersTransition()) return focused;
- if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
- if (direction == View.FOCUS_LEFT) {
+ if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
+ return mCanShowHeaders && mShowingHeaders ?
+ mHeadersFragment.getVerticalGridView() :
+ mRowsFragment.getVerticalGridView();
+ } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+ && direction == View.FOCUS_UP) {
+ return searchOrbView;
+ }
+
+ // If headers fragment is disabled, just return null.
+ if (!mCanShowHeaders) {
+ return null;
+ }
+ boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
+ int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+ if (direction == towardStart) {
if (isVerticalScrolling() || mShowingHeaders) {
return focused;
}
return mHeadersFragment.getVerticalGridView();
- } else if (direction == View.FOCUS_RIGHT) {
+ } else if (direction == towardEnd) {
if (isVerticalScrolling() || !mShowingHeaders) {
return focused;
}
return mRowsFragment.getVerticalGridView();
- } else if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
- return mShowingHeaders ? mHeadersFragment.getVerticalGridView() :
- mRowsFragment.getVerticalGridView();
-
- } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
- && direction == View.FOCUS_UP) {
- return searchOrbView;
-
} else {
return null;
}
@@ -557,22 +567,34 @@
@Override
public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (getChildFragmentManager().isDestroyed()) {
+ return true;
+ }
// Make sure not changing focus when requestFocus() is called.
if (mCanShowHeaders && mShowingHeaders) {
- if (mHeadersFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+ if (mHeadersFragment != null && mHeadersFragment.getView() != null &&
+ mHeadersFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
return true;
}
}
- if (mRowsFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+ if (mRowsFragment != null && mRowsFragment.getView() != null &&
+ mRowsFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
return true;
}
- return mTitleView.requestFocus(direction, previouslyFocusedRect);
+ if (mTitleView != null &&
+ mTitleView.requestFocus(direction, previouslyFocusedRect)) {
+ return true;
+ }
+ return false;
};
@Override
public void onRequestChildFocus(View child, View focused) {
- int childId = child.getId();
+ if (getChildFragmentManager().isDestroyed()) {
+ return;
+ }
if (!mCanShowHeaders || isInHeadersTransition()) return;
+ int childId = child.getId();
if (childId == R.id.browse_container_dock && mShowingHeaders) {
startHeadersTransitionInternal(false);
} else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
@@ -595,19 +617,27 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
- mContainerListMarginLeft = (int) ta.getDimension(
+ mContainerListMarginStart = (int) ta.getDimension(
R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
mContainerListAlignTop = (int) ta.getDimension(
R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
ta.recycle();
- mHeadersTransitionStartDelay = getResources()
- .getInteger(R.integer.lb_browse_headers_transition_delay);
- mHeadersTransitionDuration = getResources()
- .getInteger(R.integer.lb_browse_headers_transition_duration);
-
readArguments(getArguments());
+ if (mCanShowHeaders) {
+ if (mHeadersBackStackEnabled) {
+ mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
+ mBackStackChangedListener = new BackStackListener();
+ getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
+ mBackStackChangedListener.load(savedInstanceState);
+ } else {
+ if (savedInstanceState != null) {
+ mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
+ }
+ }
+ }
+
}
@Override
@@ -694,26 +724,18 @@
showHeaders(false);
}
});
- mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
- mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
-
- sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_headers, true);
- sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_headers, true);
- sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.container_list, true);
- sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.container_list, true);
-
- if (mCanShowHeaders) {
- if (mHeadersBackStackEnabled) {
- mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
- mBackStackChangedListener = new BackStackListener();
- getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
- mBackStackChangedListener.load(savedInstanceState);
- } else {
- if (savedInstanceState != null) {
- mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
- }
+ mSceneAfterEntranceTransition = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+ @Override
+ public void run() {
+ setEntranceTransitionEndState();
}
- }
+ });
+ Context context = getActivity();
+ mTitleUpTransition = LeanbackTransitionHelper.loadTitleOutTransition(context,
+ sTransitionHelper);
+ mTitleDownTransition = LeanbackTransitionHelper.loadTitleInTransition(context,
+ sTransitionHelper);
+
if (savedInstanceState != null) {
mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
}
@@ -723,40 +745,9 @@
}
private void createHeadersTransition() {
- mHeadersTransition = sTransitionHelper.createTransitionSet(false);
- sTransitionHelper.excludeChildren(mHeadersTransition, R.id.browse_title_group, true);
- Object changeBounds = sTransitionHelper.createChangeBounds(false);
- Object fadeIn = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_IN);
- Object fadeOut = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_OUT);
- Object scale = sTransitionHelper.createScale();
- if (TransitionHelper.systemSupportsTransitions()) {
- Context context = getView().getContext();
- sTransitionHelper.setInterpolator(changeBounds,
- sTransitionHelper.createDefaultInterpolator(context));
- sTransitionHelper.setInterpolator(fadeIn,
- sTransitionHelper.createDefaultInterpolator(context));
- sTransitionHelper.setInterpolator(fadeOut,
- sTransitionHelper.createDefaultInterpolator(context));
- sTransitionHelper.setInterpolator(scale,
- sTransitionHelper.createDefaultInterpolator(context));
- }
-
- sTransitionHelper.setDuration(fadeOut, mHeadersTransitionDuration);
- sTransitionHelper.addTransition(mHeadersTransition, fadeOut);
-
- if (mShowingHeaders) {
- sTransitionHelper.setStartDelay(changeBounds, mHeadersTransitionStartDelay);
- sTransitionHelper.setStartDelay(scale, mHeadersTransitionStartDelay);
- }
- sTransitionHelper.setDuration(changeBounds, mHeadersTransitionDuration);
- sTransitionHelper.addTransition(mHeadersTransition, changeBounds);
- sTransitionHelper.addTarget(scale, mRowsFragment.getVerticalGridView());
- sTransitionHelper.setDuration(scale, mHeadersTransitionDuration);
- sTransitionHelper.addTransition(mHeadersTransition, scale);
-
- sTransitionHelper.setDuration(fadeIn, mHeadersTransitionDuration);
- sTransitionHelper.setStartDelay(fadeIn, mHeadersTransitionStartDelay);
- sTransitionHelper.addTransition(mHeadersTransition, fadeIn);
+ mHeadersTransition = sTransitionHelper.loadTransition(getActivity(),
+ mShowingHeaders ?
+ R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
@Override
@@ -798,22 +789,29 @@
}
}
+ private void setRowsAlignedLeft(boolean alignLeft) {
+ MarginLayoutParams lp;
+ View containerList;
+ containerList = mRowsFragment.getView();
+ lp = (MarginLayoutParams) containerList.getLayoutParams();
+ lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
+ containerList.setLayoutParams(lp);
+ }
+
+ private void setHeadersOnScreen(boolean onScreen) {
+ MarginLayoutParams lp;
+ View containerList;
+ containerList = mHeadersFragment.getView();
+ lp = (MarginLayoutParams) containerList.getLayoutParams();
+ lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+ containerList.setLayoutParams(lp);
+ }
+
private void showHeaders(boolean show) {
if (DEBUG) Log.v(TAG, "showHeaders " + show);
mHeadersFragment.setHeadersEnabled(show);
- MarginLayoutParams lp;
- View containerList;
-
- containerList = mRowsFragment.getView();
- lp = (MarginLayoutParams) containerList.getLayoutParams();
- lp.leftMargin = show ? mContainerListMarginLeft : 0;
- containerList.setLayoutParams(lp);
-
- containerList = mHeadersFragment.getView();
- lp = (MarginLayoutParams) containerList.getLayoutParams();
- lp.leftMargin = show ? 0 : -mContainerListMarginLeft;
- containerList.setLayoutParams(lp);
-
+ setHeadersOnScreen(show);
+ setRowsAlignedLeft(!show);
mRowsFragment.setExpand(!show);
}
@@ -880,22 +878,39 @@
private class SetSelectionRunnable implements Runnable {
int mPosition;
+ boolean mSmooth = true;
@Override
public void run() {
- setSelection(mPosition);
+ setSelection(mPosition, mSmooth);
}
}
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
- private void setSelection(int position) {
+ private void setSelection(int position, boolean smooth) {
if (position != NO_POSITION) {
- mRowsFragment.setSelectedPosition(position);
- mHeadersFragment.setSelectedPosition(position);
+ mRowsFragment.setSelectedPosition(position, smooth);
+ mHeadersFragment.setSelectedPosition(position, smooth);
}
mSelectedPosition = position;
}
+ /**
+ * Sets the selected row position with smooth animation.
+ */
+ public void setSelectedPosition(int position) {
+ setSelectedPosition(position, true);
+ }
+
+ /**
+ * Sets the selected row position.
+ */
+ public void setSelectedPosition(int position, boolean smooth) {
+ mSetSelectionRunnable.mPosition = position;
+ mSetSelectionRunnable.mSmooth = smooth;
+ mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+ }
+
@Override
public void onStart() {
super.onStart();
@@ -904,8 +919,7 @@
mRowsFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
mRowsFragment.setItemAlignment();
- mRowsFragment.getVerticalGridView().setPivotX(0);
- mRowsFragment.getVerticalGridView().setPivotY(mContainerListAlignTop);
+ mRowsFragment.setScalePivots(0, mContainerListAlignTop);
if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
mHeadersFragment.getView().requestFocus();
@@ -916,6 +930,21 @@
if (mCanShowHeaders) {
showHeaders(mShowingHeaders);
}
+ if (isEntranceTransitionEnabled()) {
+ setEntranceTransitionStartState();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ mTitleView.enableAnimation(false);
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mTitleView.enableAnimation(true);
}
/**
@@ -1036,5 +1065,49 @@
public int getHeadersState() {
return mHeadersState;
}
+
+ @Override
+ protected Object createEntranceTransition() {
+ return sTransitionHelper.loadTransition(getActivity(),
+ R.transition.lb_browse_entrance_transition);
+ }
+
+ @Override
+ protected void runEntranceTransition(Object entranceTransition) {
+ sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+ entranceTransition);
+ }
+
+ @Override
+ protected void onEntranceTransitionStart() {
+ mHeadersFragment.onTransitionStart();
+ mRowsFragment.onTransitionStart();
+ }
+
+ @Override
+ protected void onEntranceTransitionEnd() {
+ mRowsFragment.onTransitionEnd();
+ mHeadersFragment.onTransitionEnd();
+ }
+
+ void setSearchOrbViewOnScreen(boolean onScreen) {
+ View searchOrbView = mTitleView.getSearchAffordanceView();
+ MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+ lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+ searchOrbView.setLayoutParams(lp);
+ }
+
+ void setEntranceTransitionStartState() {
+ setHeadersOnScreen(false);
+ setSearchOrbViewOnScreen(false);
+ mRowsFragment.setEntranceTransitionState(false);
+ }
+
+ void setEntranceTransitionEndState() {
+ setHeadersOnScreen(mShowingHeaders);
+ setSearchOrbViewOnScreen(true);
+ mRowsFragment.setEntranceTransitionState(true);
+ }
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
new file mode 100644
index 0000000..68b7af3
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -0,0 +1,1115 @@
+/* This file is auto-generated from BrowseFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.LeanbackTransitionHelper;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+/**
+ * A fragment for creating Leanback browse screens. It is composed of a
+ * RowsSupportFragment and a HeadersSupportFragment.
+ * <p>
+ * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set
+ * of rows in a vertical list. The elements in this adapter must be subclasses
+ * of {@link Row}.
+ * <p>
+ * The HeadersSupportFragment can be set to be either shown or hidden by default, or
+ * may be disabled entirely. See {@link #setHeadersState} for details.
+ * <p>
+ * By default the BrowseSupportFragment includes support for returning to the headers
+ * when the user presses Back. For Activities that customize {@link
+ * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by
+ * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
+ * use {@link BrowseSupportFragment.BrowseTransitionListener} and
+ * {@link #startHeadersTransition(boolean)}.
+ */
+public class BrowseSupportFragment extends BaseSupportFragment {
+
+ // BUNDLE attribute for saving header show/hide status when backstack is used:
+ static final String HEADER_STACK_INDEX = "headerStackIndex";
+ // BUNDLE attribute for saving header show/hide status when backstack is not used:
+ static final String HEADER_SHOW = "headerShow";
+ // BUNDLE attribute for title is showing
+ static final String TITLE_SHOW = "titleShow";
+
+ final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
+ int mLastEntryCount;
+ int mIndexOfHeadersBackStack;
+
+ BackStackListener() {
+ mLastEntryCount = getFragmentManager().getBackStackEntryCount();
+ mIndexOfHeadersBackStack = -1;
+ }
+
+ void load(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
+ mShowingHeaders = mIndexOfHeadersBackStack == -1;
+ } else {
+ if (!mShowingHeaders) {
+ getFragmentManager().beginTransaction()
+ .addToBackStack(mWithHeadersBackStackName).commit();
+ }
+ }
+ }
+
+ void save(Bundle outState) {
+ outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
+ }
+
+
+ @Override
+ public void onBackStackChanged() {
+ if (getFragmentManager() == null) {
+ Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
+ return;
+ }
+ int count = getFragmentManager().getBackStackEntryCount();
+ // if backstack is growing and last pushed entry is "headers" backstack,
+ // remember the index of the entry.
+ if (count > mLastEntryCount) {
+ BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
+ if (mWithHeadersBackStackName.equals(entry.getName())) {
+ mIndexOfHeadersBackStack = count - 1;
+ }
+ } else if (count < mLastEntryCount) {
+ // if popped "headers" backstack, initiate the show header transition if needed
+ if (mIndexOfHeadersBackStack >= count) {
+ mIndexOfHeadersBackStack = -1;
+ if (!mShowingHeaders) {
+ startHeadersTransitionInternal(true);
+ }
+ }
+ }
+ mLastEntryCount = count;
+ }
+ }
+
+ /**
+ * Listener for transitions between browse headers and rows.
+ */
+ public static class BrowseTransitionListener {
+ /**
+ * Callback when headers transition starts.
+ *
+ * @param withHeaders True if the transition will result in headers
+ * being shown, false otherwise.
+ */
+ public void onHeadersTransitionStart(boolean withHeaders) {
+ }
+ /**
+ * Callback when headers transition stops.
+ *
+ * @param withHeaders True if the transition will result in headers
+ * being shown, false otherwise.
+ */
+ public void onHeadersTransitionStop(boolean withHeaders) {
+ }
+ }
+
+ private static final String TAG = "BrowseSupportFragment";
+
+ private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
+
+ private static boolean DEBUG = false;
+
+ /** The headers fragment is enabled and shown by default. */
+ public static final int HEADERS_ENABLED = 1;
+
+ /** The headers fragment is enabled and hidden by default. */
+ public static final int HEADERS_HIDDEN = 2;
+
+ /** The headers fragment is disabled and will never be shown. */
+ public static final int HEADERS_DISABLED = 3;
+
+ private static final float SLIDE_DISTANCE_FACTOR = 2;
+
+ private RowsSupportFragment mRowsSupportFragment;
+ private HeadersSupportFragment mHeadersSupportFragment;
+
+ private ObjectAdapter mAdapter;
+
+ private String mTitle;
+ private Drawable mBadgeDrawable;
+ private int mHeadersState = HEADERS_ENABLED;
+ private int mBrandColor = Color.TRANSPARENT;
+ private boolean mBrandColorSet;
+
+ private BrowseFrameLayout mBrowseFrame;
+ private TitleView mTitleView;
+ private boolean mShowingTitle = true;
+ private boolean mHeadersBackStackEnabled = true;
+ private String mWithHeadersBackStackName;
+ private boolean mShowingHeaders = true;
+ private boolean mCanShowHeaders = true;
+ private int mContainerListMarginStart;
+ private int mContainerListAlignTop;
+ private boolean mRowScaleEnabled = true;
+ private SearchOrbView.Colors mSearchAffordanceColors;
+ private boolean mSearchAffordanceColorSet;
+ private OnItemSelectedListener mExternalOnItemSelectedListener;
+ private OnClickListener mExternalOnSearchClickedListener;
+ private OnItemClickedListener mOnItemClickedListener;
+ private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+ private OnItemViewClickedListener mOnItemViewClickedListener;
+ private int mSelectedPosition = -1;
+
+ private PresenterSelector mHeaderPresenterSelector;
+
+ // transition related:
+ private Object mSceneWithTitle;
+ private Object mSceneWithoutTitle;
+ private Object mSceneWithHeaders;
+ private Object mSceneWithoutHeaders;
+ private Object mSceneAfterEntranceTransition;
+ private Object mTitleUpTransition;
+ private Object mTitleDownTransition;
+ private Object mHeadersTransition;
+ private BackStackListener mBackStackChangedListener;
+ private BrowseTransitionListener mBrowseTransitionListener;
+
+ private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
+ private static final String ARG_BADGE_URI = BrowseSupportFragment.class.getCanonicalName() + ".badge";
+ private static final String ARG_HEADERS_STATE =
+ BrowseSupportFragment.class.getCanonicalName() + ".headersState";
+
+ /**
+ * Create arguments for a browse fragment.
+ *
+ * @param args The Bundle to place arguments into, or null if the method
+ * should return a new Bundle.
+ * @param title The title of the BrowseSupportFragment.
+ * @param headersState The initial state of the headers of the
+ * BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
+ * #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
+ * @return A Bundle with the given arguments for creating a BrowseSupportFragment.
+ */
+ public static Bundle createArgs(Bundle args, String title, int headersState) {
+ if (args == null) {
+ args = new Bundle();
+ }
+ args.putString(ARG_TITLE, title);
+ args.putInt(ARG_HEADERS_STATE, headersState);
+ return args;
+ }
+
+ /**
+ * Sets the brand color for the browse fragment. The brand color is used as
+ * the primary color for UI elements in the browse fragment. For example,
+ * the background color of the headers fragment uses the brand color.
+ *
+ * @param color The color to use as the brand color of the fragment.
+ */
+ public void setBrandColor(int color) {
+ mBrandColor = color;
+ mBrandColorSet = true;
+
+ if (mHeadersSupportFragment != null) {
+ mHeadersSupportFragment.setBackgroundColor(mBrandColor);
+ }
+ }
+
+ /**
+ * Returns the brand color for the browse fragment.
+ * The default is transparent.
+ */
+ public int getBrandColor() {
+ return mBrandColor;
+ }
+
+ /**
+ * Sets the adapter containing the rows for the fragment.
+ *
+ * <p>The items referenced by the adapter must be be derived from
+ * {@link Row}. These rows will be used by the rows fragment and the headers
+ * fragment (if not disabled) to render the browse rows.
+ *
+ * @param adapter An ObjectAdapter for the browse rows. All items must
+ * derive from {@link Row}.
+ */
+ public void setAdapter(ObjectAdapter adapter) {
+ mAdapter = adapter;
+ if (mRowsSupportFragment != null) {
+ mRowsSupportFragment.setAdapter(adapter);
+ mHeadersSupportFragment.setAdapter(adapter);
+ }
+ }
+
+ /**
+ * Returns the adapter containing the rows for the fragment.
+ */
+ public ObjectAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Sets an item selection listener. This listener will be called when an
+ * item or row is selected by a user.
+ *
+ * @param listener The listener to call when an item or row is selected.
+ * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+ */
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mExternalOnItemSelectedListener = listener;
+ }
+
+ /**
+ * Sets an item selection listener.
+ */
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ mExternalOnItemViewSelectedListener = listener;
+ }
+
+ /**
+ * Returns an item selection listener.
+ */
+ public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+ return mExternalOnItemViewSelectedListener;
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ *
+ * <p>OnItemClickedListener will override {@link View.OnClickListener} that
+ * an item presenter may set during
+ * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you
+ * should choose to use an {@link OnItemClickedListener} or a
+ * {@link View.OnClickListener} on your item views, but not both.
+ *
+ * @param listener The listener to call when an item is clicked.
+ * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+ */
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ mOnItemClickedListener = listener;
+ if (mRowsSupportFragment != null) {
+ mRowsSupportFragment.setOnItemClickedListener(listener);
+ }
+ }
+
+ /**
+ * Returns the item clicked listener.
+ * @deprecated Use {@link #getOnItemViewClickedListener()}
+ */
+ public OnItemClickedListener getOnItemClickedListener() {
+ return mOnItemClickedListener;
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ * OnItemViewClickedListener will override {@link View.OnClickListener} that
+ * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+ * So in general, developer should choose one of the listeners but not both.
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ mOnItemViewClickedListener = listener;
+ if (mRowsSupportFragment != null) {
+ mRowsSupportFragment.setOnItemViewClickedListener(listener);
+ }
+ }
+
+ /**
+ * Returns the item Clicked listener.
+ */
+ public OnItemViewClickedListener getOnItemViewClickedListener() {
+ return mOnItemViewClickedListener;
+ }
+
+ /**
+ * Sets a click listener for the search affordance.
+ *
+ * <p>The presence of a listener will change the visibility of the search
+ * affordance in the fragment title. When set to non-null, the title will
+ * contain an element that a user may click to begin a search.
+ *
+ * <p>The listener's {@link View.OnClickListener#onClick onClick} method
+ * will be invoked when the user clicks on the search element.
+ *
+ * @param listener The listener to call when the search element is clicked.
+ */
+ public void setOnSearchClickedListener(View.OnClickListener listener) {
+ mExternalOnSearchClickedListener = listener;
+ if (mTitleView != null) {
+ mTitleView.setOnSearchClickedListener(listener);
+ }
+ }
+
+ /**
+ * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
+ */
+ public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+ mSearchAffordanceColors = colors;
+ mSearchAffordanceColorSet = true;
+ if (mTitleView != null) {
+ mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ }
+ }
+
+ /**
+ * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
+ */
+ public SearchOrbView.Colors getSearchAffordanceColors() {
+ if (mSearchAffordanceColorSet) {
+ return mSearchAffordanceColors;
+ }
+ if (mTitleView == null) {
+ throw new IllegalStateException("Fragment views not yet created");
+ }
+ return mTitleView.getSearchAffordanceColors();
+ }
+
+ /**
+ * Sets the color used to draw the search affordance.
+ * A default brighter color will be set by the framework.
+ *
+ * @param color The color to use for the search affordance.
+ */
+ public void setSearchAffordanceColor(int color) {
+ setSearchAffordanceColors(new SearchOrbView.Colors(color));
+ }
+
+ /**
+ * Returns the color used to draw the search affordance.
+ */
+ public int getSearchAffordanceColor() {
+ return getSearchAffordanceColors().color;
+ }
+
+ /**
+ * Start a headers transition.
+ *
+ * <p>This method will begin a transition to either show or hide the
+ * headers, depending on the value of withHeaders. If headers are disabled
+ * for this browse fragment, this method will throw an exception.
+ *
+ * @param withHeaders True if the headers should transition to being shown,
+ * false if the transition should result in headers being hidden.
+ */
+ public void startHeadersTransition(boolean withHeaders) {
+ if (!mCanShowHeaders) {
+ throw new IllegalStateException("Cannot start headers transition");
+ }
+ if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
+ return;
+ }
+ startHeadersTransitionInternal(withHeaders);
+ }
+
+ /**
+ * Returns true if the headers transition is currently running.
+ */
+ public boolean isInHeadersTransition() {
+ return mHeadersTransition != null;
+ }
+
+ /**
+ * Returns true if headers are shown.
+ */
+ public boolean isShowingHeaders() {
+ return mShowingHeaders;
+ }
+
+ /**
+ * Set a listener for browse fragment transitions.
+ *
+ * @param listener The listener to call when a browse headers transition
+ * begins or ends.
+ */
+ public void setBrowseTransitionListener(BrowseTransitionListener listener) {
+ mBrowseTransitionListener = listener;
+ }
+
+ /**
+ * Enables scaling of rows when headers are present.
+ * By default enabled to increase density.
+ *
+ * @param enable true to enable row scaling
+ */
+ public void enableRowScaling(boolean enable) {
+ mRowScaleEnabled = enable;
+ if (mRowsSupportFragment != null) {
+ mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
+ }
+ }
+
+ private void startHeadersTransitionInternal(final boolean withHeaders) {
+ if (getFragmentManager().isDestroyed()) {
+ return;
+ }
+ mShowingHeaders = withHeaders;
+ mRowsSupportFragment.onExpandTransitionStart(!withHeaders, new Runnable() {
+ @Override
+ public void run() {
+ mHeadersSupportFragment.onTransitionStart();
+ createHeadersTransition();
+ if (mBrowseTransitionListener != null) {
+ mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
+ }
+ sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
+ mHeadersTransition);
+ if (mHeadersBackStackEnabled) {
+ if (!withHeaders) {
+ getFragmentManager().beginTransaction()
+ .addToBackStack(mWithHeadersBackStackName).commit();
+ } else {
+ int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
+ if (index >= 0) {
+ BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
+ getFragmentManager().popBackStackImmediate(entry.getId(),
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ private boolean isVerticalScrolling() {
+ // don't run transition
+ return mHeadersSupportFragment.getVerticalGridView().getScrollState()
+ != HorizontalGridView.SCROLL_STATE_IDLE
+ || mRowsSupportFragment.getVerticalGridView().getScrollState()
+ != HorizontalGridView.SCROLL_STATE_IDLE;
+ }
+
+ private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+ new BrowseFrameLayout.OnFocusSearchListener() {
+ @Override
+ public View onFocusSearch(View focused, int direction) {
+ // if headers is running transition, focus stays
+ if (mCanShowHeaders && isInHeadersTransition()) {
+ return focused;
+ }
+ if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+ final View searchOrbView = mTitleView.getSearchAffordanceView();
+ if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
+ return mCanShowHeaders && mShowingHeaders ?
+ mHeadersSupportFragment.getVerticalGridView() :
+ mRowsSupportFragment.getVerticalGridView();
+ } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+ && direction == View.FOCUS_UP) {
+ return searchOrbView;
+ }
+
+ // If headers fragment is disabled, just return null.
+ if (!mCanShowHeaders) {
+ return null;
+ }
+ boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
+ int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+ if (direction == towardStart) {
+ if (isVerticalScrolling() || mShowingHeaders) {
+ return focused;
+ }
+ return mHeadersSupportFragment.getVerticalGridView();
+ } else if (direction == towardEnd) {
+ if (isVerticalScrolling() || !mShowingHeaders) {
+ return focused;
+ }
+ return mRowsSupportFragment.getVerticalGridView();
+ } else {
+ return null;
+ }
+ }
+ };
+
+ private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
+ new BrowseFrameLayout.OnChildFocusListener() {
+
+ @Override
+ public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (getChildFragmentManager().isDestroyed()) {
+ return true;
+ }
+ // Make sure not changing focus when requestFocus() is called.
+ if (mCanShowHeaders && mShowingHeaders) {
+ if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null &&
+ mHeadersSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+ return true;
+ }
+ }
+ if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null &&
+ mRowsSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+ return true;
+ }
+ if (mTitleView != null &&
+ mTitleView.requestFocus(direction, previouslyFocusedRect)) {
+ return true;
+ }
+ return false;
+ };
+
+ @Override
+ public void onRequestChildFocus(View child, View focused) {
+ if (getChildFragmentManager().isDestroyed()) {
+ return;
+ }
+ if (!mCanShowHeaders || isInHeadersTransition()) return;
+ int childId = child.getId();
+ if (childId == R.id.browse_container_dock && mShowingHeaders) {
+ startHeadersTransitionInternal(false);
+ } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
+ startHeadersTransitionInternal(true);
+ }
+ }
+ };
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ if (mBackStackChangedListener != null) {
+ mBackStackChangedListener.save(outState);
+ } else {
+ outState.putBoolean(HEADER_SHOW, mShowingHeaders);
+ }
+ outState.putBoolean(TITLE_SHOW, mShowingTitle);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
+ mContainerListMarginStart = (int) ta.getDimension(
+ R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
+ mContainerListAlignTop = (int) ta.getDimension(
+ R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
+ ta.recycle();
+
+ readArguments(getArguments());
+
+ if (mCanShowHeaders) {
+ if (mHeadersBackStackEnabled) {
+ mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
+ mBackStackChangedListener = new BackStackListener();
+ getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
+ mBackStackChangedListener.load(savedInstanceState);
+ } else {
+ if (savedInstanceState != null) {
+ mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mBackStackChangedListener != null) {
+ getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
+ mRowsSupportFragment = new RowsSupportFragment();
+ mHeadersSupportFragment = new HeadersSupportFragment();
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.browse_headers_dock, mHeadersSupportFragment)
+ .replace(R.id.browse_container_dock, mRowsSupportFragment).commit();
+ } else {
+ mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
+ .findFragmentById(R.id.browse_headers_dock);
+ mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager()
+ .findFragmentById(R.id.browse_container_dock);
+ }
+
+ mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
+
+ mRowsSupportFragment.setAdapter(mAdapter);
+ if (mHeaderPresenterSelector != null) {
+ mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
+ }
+ mHeadersSupportFragment.setAdapter(mAdapter);
+
+ mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
+ mRowsSupportFragment.setOnItemSelectedListener(mRowSelectedListener);
+ mRowsSupportFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
+ mHeadersSupportFragment.setOnItemSelectedListener(mHeaderSelectedListener);
+ mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
+ mRowsSupportFragment.setOnItemClickedListener(mOnItemClickedListener);
+ mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
+ View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
+
+ mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+ mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+ mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
+
+ mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+ mTitleView.setTitle(mTitle);
+ mTitleView.setBadgeDrawable(mBadgeDrawable);
+ if (mSearchAffordanceColorSet) {
+ mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ }
+ if (mExternalOnSearchClickedListener != null) {
+ mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+ }
+
+ if (mBrandColorSet) {
+ mHeadersSupportFragment.setBackgroundColor(mBrandColor);
+ }
+
+ mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+ @Override
+ public void run() {
+ mTitleView.setVisibility(View.VISIBLE);
+ }
+ });
+ mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+ @Override
+ public void run() {
+ mTitleView.setVisibility(View.INVISIBLE);
+ }
+ });
+ mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+ @Override
+ public void run() {
+ showHeaders(true);
+ }
+ });
+ mSceneWithoutHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+ @Override
+ public void run() {
+ showHeaders(false);
+ }
+ });
+ mSceneAfterEntranceTransition = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+ @Override
+ public void run() {
+ setEntranceTransitionEndState();
+ }
+ });
+ Context context = getActivity();
+ mTitleUpTransition = LeanbackTransitionHelper.loadTitleOutTransition(context,
+ sTransitionHelper);
+ mTitleDownTransition = LeanbackTransitionHelper.loadTitleInTransition(context,
+ sTransitionHelper);
+
+ if (savedInstanceState != null) {
+ mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
+ }
+ mTitleView.setVisibility(mShowingTitle ? View.VISIBLE: View.INVISIBLE);
+
+ return root;
+ }
+
+ private void createHeadersTransition() {
+ mHeadersTransition = sTransitionHelper.loadTransition(getActivity(),
+ mShowingHeaders ?
+ R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
+
+ sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
+ @Override
+ public void onTransitionStart(Object transition) {
+ }
+ @Override
+ public void onTransitionEnd(Object transition) {
+ mHeadersTransition = null;
+ mRowsSupportFragment.onTransitionEnd();
+ mHeadersSupportFragment.onTransitionEnd();
+ if (mShowingHeaders) {
+ VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
+ if (headerGridView != null && !headerGridView.hasFocus()) {
+ headerGridView.requestFocus();
+ }
+ } else {
+ VerticalGridView rowsGridView = mRowsSupportFragment.getVerticalGridView();
+ if (rowsGridView != null && !rowsGridView.hasFocus()) {
+ rowsGridView.requestFocus();
+ }
+ }
+ if (mBrowseTransitionListener != null) {
+ mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets the {@link PresenterSelector} used to render the row headers.
+ *
+ * @param headerPresenterSelector The PresenterSelector that will determine
+ * the Presenter for each row header.
+ */
+ public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
+ mHeaderPresenterSelector = headerPresenterSelector;
+ if (mHeadersSupportFragment != null) {
+ mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
+ }
+ }
+
+ private void setRowsAlignedLeft(boolean alignLeft) {
+ MarginLayoutParams lp;
+ View containerList;
+ containerList = mRowsSupportFragment.getView();
+ lp = (MarginLayoutParams) containerList.getLayoutParams();
+ lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
+ containerList.setLayoutParams(lp);
+ }
+
+ private void setHeadersOnScreen(boolean onScreen) {
+ MarginLayoutParams lp;
+ View containerList;
+ containerList = mHeadersSupportFragment.getView();
+ lp = (MarginLayoutParams) containerList.getLayoutParams();
+ lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+ containerList.setLayoutParams(lp);
+ }
+
+ private void showHeaders(boolean show) {
+ if (DEBUG) Log.v(TAG, "showHeaders " + show);
+ mHeadersSupportFragment.setHeadersEnabled(show);
+ setHeadersOnScreen(show);
+ setRowsAlignedLeft(!show);
+ mRowsSupportFragment.setExpand(!show);
+ }
+
+ private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
+ new HeadersSupportFragment.OnHeaderClickedListener() {
+ @Override
+ public void onHeaderClicked() {
+ if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
+ return;
+ }
+ startHeadersTransitionInternal(false);
+ mRowsSupportFragment.getVerticalGridView().requestFocus();
+ }
+ };
+
+ private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
+ if (DEBUG) Log.v(TAG, "row selected position " + position);
+ onRowSelected(position);
+ if (mExternalOnItemViewSelectedListener != null) {
+ mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+ rowViewHolder, row);
+ }
+ }
+ };
+
+ private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(Object item, Row row) {
+ if (mExternalOnItemSelectedListener != null) {
+ mExternalOnItemSelectedListener.onItemSelected(item, row);
+ }
+ }
+ };
+
+ private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(Object item, Row row) {
+ int position = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition();
+ if (DEBUG) Log.v(TAG, "header selected position " + position);
+ onRowSelected(position);
+ }
+ };
+
+ private void onRowSelected(int position) {
+ if (position != mSelectedPosition) {
+ mSetSelectionRunnable.mPosition = position;
+ mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+
+ if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
+ if (!mShowingTitle) {
+ sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
+ mShowingTitle = true;
+ }
+ } else if (mShowingTitle) {
+ sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
+ mShowingTitle = false;
+ }
+ }
+ }
+
+ private class SetSelectionRunnable implements Runnable {
+ int mPosition;
+ boolean mSmooth = true;
+ @Override
+ public void run() {
+ setSelection(mPosition, mSmooth);
+ }
+ }
+
+ private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+ private void setSelection(int position, boolean smooth) {
+ if (position != NO_POSITION) {
+ mRowsSupportFragment.setSelectedPosition(position, smooth);
+ mHeadersSupportFragment.setSelectedPosition(position, smooth);
+ }
+ mSelectedPosition = position;
+ }
+
+ /**
+ * Sets the selected row position with smooth animation.
+ */
+ public void setSelectedPosition(int position) {
+ setSelectedPosition(position, true);
+ }
+
+ /**
+ * Sets the selected row position.
+ */
+ public void setSelectedPosition(int position, boolean smooth) {
+ mSetSelectionRunnable.mPosition = position;
+ mSetSelectionRunnable.mSmooth = smooth;
+ mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mHeadersSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
+ mHeadersSupportFragment.setItemAlignment();
+ mRowsSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
+ mRowsSupportFragment.setItemAlignment();
+
+ mRowsSupportFragment.setScalePivots(0, mContainerListAlignTop);
+
+ if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) {
+ mHeadersSupportFragment.getView().requestFocus();
+ } else if ((!mCanShowHeaders || !mShowingHeaders)
+ && mRowsSupportFragment.getView() != null) {
+ mRowsSupportFragment.getView().requestFocus();
+ }
+ if (mCanShowHeaders) {
+ showHeaders(mShowingHeaders);
+ }
+ if (isEntranceTransitionEnabled()) {
+ setEntranceTransitionStartState();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ mTitleView.enableAnimation(false);
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mTitleView.enableAnimation(true);
+ }
+
+ /**
+ * Enable/disable headers transition on back key support. This is enabled by
+ * default. The BrowseSupportFragment will add a back stack entry when headers are
+ * showing. Running a headers transition when the back key is pressed only
+ * works when the headers state is {@link #HEADERS_ENABLED} or
+ * {@link #HEADERS_HIDDEN}.
+ * <p>
+ * NOTE: If an Activity has its own onBackPressed() handling, you must
+ * disable this feature. You may use {@link #startHeadersTransition(boolean)}
+ * and {@link BrowseTransitionListener} in your own back stack handling.
+ */
+ public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
+ mHeadersBackStackEnabled = headersBackStackEnabled;
+ }
+
+ /**
+ * Returns true if headers transition on back key support is enabled.
+ */
+ public final boolean isHeadersTransitionOnBackEnabled() {
+ return mHeadersBackStackEnabled;
+ }
+
+ private void readArguments(Bundle args) {
+ if (args == null) {
+ return;
+ }
+ if (args.containsKey(ARG_TITLE)) {
+ setTitle(args.getString(ARG_TITLE));
+ }
+ if (args.containsKey(ARG_HEADERS_STATE)) {
+ setHeadersState(args.getInt(ARG_HEADERS_STATE));
+ }
+ }
+
+ /**
+ * Sets the drawable displayed in the browse fragment title.
+ *
+ * @param drawable The Drawable to display in the browse fragment title.
+ */
+ public void setBadgeDrawable(Drawable drawable) {
+ if (mBadgeDrawable != drawable) {
+ mBadgeDrawable = drawable;
+ if (mTitleView != null) {
+ mTitleView.setBadgeDrawable(drawable);
+ }
+ }
+ }
+
+ /**
+ * Returns the badge drawable used in the fragment title.
+ */
+ public Drawable getBadgeDrawable() {
+ return mBadgeDrawable;
+ }
+
+ /**
+ * Sets a title for the browse fragment.
+ *
+ * @param title The title of the browse fragment.
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ if (mTitleView != null) {
+ mTitleView.setTitle(title);
+ }
+ }
+
+ /**
+ * Returns the title for the browse fragment.
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the state for the headers column in the browse fragment. Must be one
+ * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
+ * {@link #HEADERS_DISABLED}.
+ *
+ * @param headersState The state of the headers for the browse fragment.
+ */
+ public void setHeadersState(int headersState) {
+ if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
+ throw new IllegalArgumentException("Invalid headers state: " + headersState);
+ }
+ if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
+
+ if (headersState != mHeadersState) {
+ mHeadersState = headersState;
+ switch (headersState) {
+ case HEADERS_ENABLED:
+ mCanShowHeaders = true;
+ mShowingHeaders = true;
+ break;
+ case HEADERS_HIDDEN:
+ mCanShowHeaders = true;
+ mShowingHeaders = false;
+ break;
+ case HEADERS_DISABLED:
+ mCanShowHeaders = false;
+ mShowingHeaders = false;
+ break;
+ default:
+ Log.w(TAG, "Unknown headers state: " + headersState);
+ break;
+ }
+ if (mHeadersSupportFragment != null) {
+ mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
+ }
+ }
+ }
+
+ /**
+ * Returns the state of the headers column in the browse fragment.
+ */
+ public int getHeadersState() {
+ return mHeadersState;
+ }
+
+ @Override
+ protected Object createEntranceTransition() {
+ return sTransitionHelper.loadTransition(getActivity(),
+ R.transition.lb_browse_entrance_transition);
+ }
+
+ @Override
+ protected void runEntranceTransition(Object entranceTransition) {
+ sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+ entranceTransition);
+ }
+
+ @Override
+ protected void onEntranceTransitionStart() {
+ mHeadersSupportFragment.onTransitionStart();
+ mRowsSupportFragment.onTransitionStart();
+ }
+
+ @Override
+ protected void onEntranceTransitionEnd() {
+ mRowsSupportFragment.onTransitionEnd();
+ mHeadersSupportFragment.onTransitionEnd();
+ }
+
+ void setSearchOrbViewOnScreen(boolean onScreen) {
+ View searchOrbView = mTitleView.getSearchAffordanceView();
+ MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+ lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+ searchOrbView.setLayoutParams(lp);
+ }
+
+ void setEntranceTransitionStartState() {
+ setHeadersOnScreen(false);
+ setSearchOrbViewOnScreen(false);
+ mRowsSupportFragment.setEntranceTransitionState(false);
+ }
+
+ void setEntranceTransitionEndState() {
+ setHeadersOnScreen(mShowingHeaders);
+ setSearchOrbViewOnScreen(true);
+ mRowsSupportFragment.setEntranceTransitionState(true);
+ }
+
+}
+
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
index 1cc278a..0c01c0d 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -32,10 +32,19 @@
/**
* Wrapper fragment for leanback details screens.
*/
-public class DetailsFragment extends Fragment {
+public class DetailsFragment extends BaseFragment {
private static final String TAG = "DetailsFragment";
private static boolean DEBUG = false;
+ private class SetSelectionRunnable implements Runnable {
+ int mPosition;
+ boolean mSmooth = true;
+ @Override
+ public void run() {
+ mRowsFragment.setSelectedPosition(mPosition, mSmooth);
+ }
+ }
+
private RowsFragment mRowsFragment;
private ObjectAdapter mAdapter;
@@ -46,6 +55,10 @@
private OnItemViewClickedListener mOnItemViewClickedListener;
private int mSelectedPosition = -1;
+ private Object mSceneAfterEntranceTransition;
+
+ private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
/**
* Sets the list of rows for the fragment.
*/
@@ -138,6 +151,13 @@
mRowsFragment.setOnItemViewSelectedListener(mExternalOnItemViewSelectedListener);
mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ mSceneAfterEntranceTransition = sTransitionHelper.createScene((ViewGroup) view,
+ new Runnable() {
+ @Override
+ public void run() {
+ mRowsFragment.setEntranceTransitionState(true);
+ }
+ });
return view;
}
@@ -166,10 +186,51 @@
setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
}
+ /**
+ * Sets the selected row position with smooth animation.
+ */
+ public void setSelectedPosition(int position) {
+ setSelectedPosition(position, true);
+ }
+
+ /**
+ * Sets the selected row position.
+ */
+ public void setSelectedPosition(int position, boolean smooth) {
+ mSetSelectionRunnable.mPosition = position;
+ mSetSelectionRunnable.mSmooth = smooth;
+ if (getView() != null && getView().getHandler() != null) {
+ getView().getHandler().post(mSetSelectionRunnable);
+ }
+ }
+
@Override
public void onStart() {
super.onStart();
setupChildFragmentLayout();
mRowsFragment.getView().requestFocus();
+ if (isEntranceTransitionEnabled()) {
+ // make sure recycler view animation is disabled
+ mRowsFragment.onTransitionStart();
+ mRowsFragment.setEntranceTransitionState(false);
+ }
}
+
+ @Override
+ protected Object createEntranceTransition() {
+ return sTransitionHelper.loadTransition(getActivity(),
+ R.transition.lb_details_enter_transition);
+ }
+
+ @Override
+ protected void runEntranceTransition(Object entranceTransition) {
+ sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+ entranceTransition);
+ }
+
+ @Override
+ protected void onEntranceTransitionEnd() {
+ mRowsFragment.onTransitionEnd();
+ }
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
new file mode 100644
index 0000000..d8ae895
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -0,0 +1,238 @@
+/* This file is auto-generated from DetailsFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Wrapper fragment for leanback details screens.
+ */
+public class DetailsSupportFragment extends BaseSupportFragment {
+ private static final String TAG = "DetailsSupportFragment";
+ private static boolean DEBUG = false;
+
+ private class SetSelectionRunnable implements Runnable {
+ int mPosition;
+ boolean mSmooth = true;
+ @Override
+ public void run() {
+ mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
+ }
+ }
+
+ private RowsSupportFragment mRowsSupportFragment;
+
+ private ObjectAdapter mAdapter;
+ private int mContainerListAlignTop;
+ private OnItemSelectedListener mExternalOnItemSelectedListener;
+ private OnItemClickedListener mOnItemClickedListener;
+ private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+ private OnItemViewClickedListener mOnItemViewClickedListener;
+ private int mSelectedPosition = -1;
+
+ private Object mSceneAfterEntranceTransition;
+
+ private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+ /**
+ * Sets the list of rows for the fragment.
+ */
+ public void setAdapter(ObjectAdapter adapter) {
+ mAdapter = adapter;
+ if (mRowsSupportFragment != null) {
+ mRowsSupportFragment.setAdapter(adapter);
+ }
+ }
+
+ /**
+ * Returns the list of rows.
+ */
+ public ObjectAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Sets an item selection listener.
+ * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+ */
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mExternalOnItemSelectedListener = listener;
+ }
+
+ /**
+ * Sets an item Clicked listener.
+ * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+ */
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ mOnItemClickedListener = listener;
+ if (mRowsSupportFragment != null) {
+ mRowsSupportFragment.setOnItemClickedListener(listener);
+ }
+ }
+
+ /**
+ * Sets an item selection listener.
+ */
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ mExternalOnItemViewSelectedListener = listener;
+ }
+
+ /**
+ * Sets an item Clicked listener.
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ mOnItemViewClickedListener = listener;
+ if (mRowsSupportFragment != null) {
+ mRowsSupportFragment.setOnItemViewClickedListener(listener);
+ }
+ }
+
+ /**
+ * Returns the item Clicked listener.
+ * @deprecated Use {@link #getOnItemViewClickedListener()}
+ */
+ public OnItemClickedListener getOnItemClickedListener() {
+ return mOnItemClickedListener;
+ }
+
+ /**
+ * Returns the item Clicked listener.
+ */
+ public OnItemViewClickedListener getOnItemViewClickedListener() {
+ return mOnItemViewClickedListener;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mContainerListAlignTop =
+ getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.lb_details_fragment, container, false);
+ mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
+ R.id.fragment_dock);
+ if (mRowsSupportFragment == null) {
+ mRowsSupportFragment = new RowsSupportFragment();
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.fragment_dock, mRowsSupportFragment).commit();
+ }
+ mRowsSupportFragment.setAdapter(mAdapter);
+ mRowsSupportFragment.setOnItemSelectedListener(mExternalOnItemSelectedListener);
+ mRowsSupportFragment.setOnItemViewSelectedListener(mExternalOnItemViewSelectedListener);
+ mRowsSupportFragment.setOnItemClickedListener(mOnItemClickedListener);
+ mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ mSceneAfterEntranceTransition = sTransitionHelper.createScene((ViewGroup) view,
+ new Runnable() {
+ @Override
+ public void run() {
+ mRowsSupportFragment.setEntranceTransitionState(true);
+ }
+ });
+ return view;
+ }
+
+ void setVerticalGridViewLayout(VerticalGridView listview) {
+ // align the top edge of item to a fixed position
+ listview.setItemAlignmentOffset(0);
+ listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+ listview.setWindowAlignmentOffset(mContainerListAlignTop);
+ listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+ listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ }
+
+ VerticalGridView getVerticalGridView() {
+ return mRowsSupportFragment == null ? null : mRowsSupportFragment.getVerticalGridView();
+ }
+
+ RowsSupportFragment getRowsSupportFragment() {
+ return mRowsSupportFragment;
+ }
+
+ /**
+ * Setup dimensions that are only meaningful when the child Fragments are inside
+ * DetailsSupportFragment.
+ */
+ private void setupChildFragmentLayout() {
+ setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
+ }
+
+ /**
+ * Sets the selected row position with smooth animation.
+ */
+ public void setSelectedPosition(int position) {
+ setSelectedPosition(position, true);
+ }
+
+ /**
+ * Sets the selected row position.
+ */
+ public void setSelectedPosition(int position, boolean smooth) {
+ mSetSelectionRunnable.mPosition = position;
+ mSetSelectionRunnable.mSmooth = smooth;
+ if (getView() != null && getView().getHandler() != null) {
+ getView().getHandler().post(mSetSelectionRunnable);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ setupChildFragmentLayout();
+ mRowsSupportFragment.getView().requestFocus();
+ if (isEntranceTransitionEnabled()) {
+ // make sure recycler view animation is disabled
+ mRowsSupportFragment.onTransitionStart();
+ mRowsSupportFragment.setEntranceTransitionState(false);
+ }
+ }
+
+ @Override
+ protected Object createEntranceTransition() {
+ return sTransitionHelper.loadTransition(getActivity(),
+ R.transition.lb_details_enter_transition);
+ }
+
+ @Override
+ protected void runEntranceTransition(Object entranceTransition) {
+ sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+ entranceTransition);
+ }
+
+ @Override
+ protected void onEntranceTransitionEnd() {
+ mRowsSupportFragment.onTransitionEnd();
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
index cc7e560..a9f2a3e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
@@ -249,9 +249,6 @@
private void updateMessage() {
if (mTextView != null) {
mTextView.setText(mMessage);
- mTextView.setTextColor(mTextView.getResources().getColor(mIsBackgroundTranslucent ?
- R.color.lb_error_message_color_on_translucent :
- R.color.lb_error_message_color_on_opaque));
mTextView.setVisibility(TextUtils.isEmpty(mMessage) ? View.GONE : View.VISIBLE);
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
new file mode 100644
index 0000000..2881921
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
@@ -0,0 +1,292 @@
+/* This file is auto-generated from ErrorFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.TitleView;
+import android.text.TextUtils;
+import android.util.Log;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A fragment for displaying an error indication.
+ */
+public class ErrorSupportFragment extends Fragment {
+
+ private View mErrorFrame;
+ private String mTitle;
+ private Drawable mBadgeDrawable;
+ private TitleView mTitleView;
+ private ImageView mImageView;
+ private TextView mTextView;
+ private Button mButton;
+ private Drawable mDrawable;
+ private CharSequence mMessage;
+ private String mButtonText;
+ private View.OnClickListener mButtonClickListener;
+ private Drawable mBackgroundDrawable;
+ private boolean mIsBackgroundTranslucent = true;
+
+ /**
+ * Sets the drawable displayed in the browse fragment title.
+ *
+ * @param drawable The drawable to display in the browse fragment title.
+ */
+ public void setBadgeDrawable(Drawable drawable) {
+ mBadgeDrawable = drawable;
+ updateTitle();
+ }
+
+ /**
+ * Returns the badge drawable used in the fragment title.
+ */
+ public Drawable getBadgeDrawable() {
+ return mBadgeDrawable;
+ }
+
+ /**
+ * Sets a title for the browse fragment.
+ *
+ * @param title The title of the browse fragment.
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ updateTitle();
+ }
+
+ /**
+ * Returns the title for the browse fragment.
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the default background.
+ *
+ * @param translucent True to set a translucent background.
+ */
+ public void setDefaultBackground(boolean translucent) {
+ mBackgroundDrawable = null;
+ mIsBackgroundTranslucent = translucent;
+ updateBackground();
+ updateMessage();
+ }
+
+ /**
+ * Returns true if the background is translucent.
+ */
+ public boolean isBackgroundTranslucent() {
+ return mIsBackgroundTranslucent;
+ }
+
+ /**
+ * Sets a drawable for the fragment background.
+ *
+ * @param drawable The drawable used for the background.
+ */
+ public void setBackgroundDrawable(Drawable drawable) {
+ mBackgroundDrawable = drawable;
+ if (drawable != null) {
+ final int opacity = drawable.getOpacity();
+ mIsBackgroundTranslucent = (opacity == PixelFormat.TRANSLUCENT ||
+ opacity == PixelFormat.TRANSPARENT);
+ }
+ updateBackground();
+ updateMessage();
+ }
+
+ /**
+ * Returns the background drawable. May be null if a default is used.
+ */
+ public Drawable getBackgroundDrawable() {
+ return mBackgroundDrawable;
+ }
+
+ /**
+ * Sets the drawable to be used for the error image.
+ *
+ * @param drawable The drawable used for the error image.
+ */
+ public void setImageDrawable(Drawable drawable) {
+ mDrawable = drawable;
+ updateImageDrawable();
+ }
+
+ /**
+ * Returns the drawable used for the error image.
+ */
+ public Drawable getImageDrawable() {
+ return mDrawable;
+ }
+
+ /**
+ * Sets the error message.
+ *
+ * @param message The error message.
+ */
+ public void setMessage(CharSequence message) {
+ mMessage = message;
+ updateMessage();
+ }
+
+ /**
+ * Returns the error message.
+ */
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Sets the button text.
+ *
+ * @param text The button text.
+ */
+ public void setButtonText(String text) {
+ mButtonText = text;
+ updateButton();
+ }
+
+ /**
+ * Returns the button text.
+ */
+ public String getButtonText() {
+ return mButtonText;
+ }
+
+ /**
+ * Set the button click listener.
+ *
+ * @param clickListener The click listener for the button.
+ */
+ public void setButtonClickListener(View.OnClickListener clickListener) {
+ mButtonClickListener = clickListener;
+ updateButton();
+ }
+
+ /**
+ * Returns the button click listener.
+ */
+ public View.OnClickListener getButtonClickListener() {
+ return mButtonClickListener;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.lb_error_fragment, container, false);
+
+ mErrorFrame = root.findViewById(R.id.error_frame);
+ updateBackground();
+
+ mImageView = (ImageView) root.findViewById(R.id.image);
+ updateImageDrawable();
+
+ mTextView = (TextView) root.findViewById(R.id.message);
+ updateMessage();
+
+ mButton = (Button) root.findViewById(R.id.button);
+ updateButton();
+
+ mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+ updateTitle();
+
+ FontMetricsInt metrics = getFontMetricsInt(mTextView);
+ int underImageBaselineMargin = container.getResources().getDimensionPixelSize(
+ R.dimen.lb_error_under_image_baseline_margin);
+ setTopMargin(mTextView, underImageBaselineMargin + metrics.ascent);
+
+ int underMessageBaselineMargin = container.getResources().getDimensionPixelSize(
+ R.dimen.lb_error_under_message_baseline_margin);
+ setTopMargin(mButton, underMessageBaselineMargin - metrics.descent);
+
+ return root;
+ }
+
+ private void updateBackground() {
+ if (mErrorFrame != null) {
+ if (mBackgroundDrawable != null) {
+ mErrorFrame.setBackground(mBackgroundDrawable);
+ } else {
+ mErrorFrame.setBackgroundColor(mErrorFrame.getResources().getColor(
+ mIsBackgroundTranslucent ?
+ R.color.lb_error_background_color_translucent :
+ R.color.lb_error_background_color_opaque));
+ }
+ }
+ }
+
+ private void updateTitle() {
+ if (mTitleView != null) {
+ mTitleView.setTitle(mTitle);
+ mTitleView.setBadgeDrawable(mBadgeDrawable);
+ }
+ }
+
+ private void updateMessage() {
+ if (mTextView != null) {
+ mTextView.setText(mMessage);
+ mTextView.setVisibility(TextUtils.isEmpty(mMessage) ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ private void updateImageDrawable() {
+ if (mImageView != null) {
+ mImageView.setImageDrawable(mDrawable);
+ mImageView.setVisibility(mDrawable == null ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ private void updateButton() {
+ if (mButton != null) {
+ mButton.setText(mButtonText);
+ mButton.setOnClickListener(mButtonClickListener);
+ mButton.setVisibility(TextUtils.isEmpty(mButtonText) ? View.GONE : View.VISIBLE);
+ mButton.requestFocus();
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mErrorFrame.requestFocus();
+ }
+
+ private static FontMetricsInt getFontMetricsInt(TextView textView) {
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setTextSize(textView.getTextSize());
+ paint.setTypeface(textView.getTypeface());
+ return paint.getFontMetricsInt();
+ }
+
+ private static void setTopMargin(TextView textView, int topMargin) {
+ ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
+ lp.topMargin = topMargin;
+ textView.setLayoutParams(lp);
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
index 486ea0e..a637553 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -66,12 +66,12 @@
}
@Override
- protected VerticalGridView findGridViewFromRoot(View view) {
+ VerticalGridView findGridViewFromRoot(View view) {
return (VerticalGridView) view.findViewById(R.id.browse_headers);
}
@Override
- protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+ void onRowSelected(ViewGroup parent, View view, int position, long id) {
if (mOnItemSelectedListener != null) {
if (position >= 0) {
Row row = (Row) getAdapter().get(position);
@@ -110,13 +110,13 @@
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
- v.setPivotX(0);
+ v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
v.setPivotY(v.getMeasuredHeight() / 2);
}
};
@Override
- protected int getLayoutResourceId() {
+ int getLayoutResourceId() {
return R.layout.lb_headers_fragment;
}
@@ -188,7 +188,7 @@
}
};
@Override
- protected void updateAdapter() {
+ void updateAdapter() {
super.updateAdapter();
ItemBridgeAdapter adapter = getBridgeAdapter();
if (adapter != null) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
new file mode 100644
index 0000000..229e5ad
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
@@ -0,0 +1,271 @@
+/* This file is auto-generated from HeadersFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.FrameLayout;
+
+/**
+ * An internal fragment containing a list of row headers.
+ */
+public class HeadersSupportFragment extends BaseRowSupportFragment {
+
+ interface OnHeaderClickedListener {
+ void onHeaderClicked();
+ }
+
+ private OnItemSelectedListener mOnItemSelectedListener;
+ private OnHeaderClickedListener mOnHeaderClickedListener;
+ private boolean mHeadersEnabled = true;
+ private boolean mHeadersGone = false;
+ private int mBackgroundColor;
+ private boolean mBackgroundColorSet;
+
+ private static final PresenterSelector sHeaderPresenter = new SinglePresenterSelector(
+ new RowHeaderPresenter(R.layout.lb_header));
+
+ public HeadersSupportFragment() {
+ setPresenterSelector(sHeaderPresenter);
+ }
+
+ public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
+ mOnHeaderClickedListener = listener;
+ }
+
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ }
+
+ @Override
+ VerticalGridView findGridViewFromRoot(View view) {
+ return (VerticalGridView) view.findViewById(R.id.browse_headers);
+ }
+
+ @Override
+ void onRowSelected(ViewGroup parent, View view, int position, long id) {
+ if (mOnItemSelectedListener != null) {
+ if (position >= 0) {
+ Row row = (Row) getAdapter().get(position);
+ mOnItemSelectedListener.onItemSelected(null, row);
+ } else {
+ mOnItemSelectedListener.onItemSelected(null, null);
+ }
+ }
+ }
+
+ private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+ new ItemBridgeAdapter.AdapterListener() {
+ @Override
+ public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+ View headerView = viewHolder.getViewHolder().view;
+ headerView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOnHeaderClickedListener != null) {
+ mOnHeaderClickedListener.onHeaderClicked();
+ }
+ }
+ });
+ headerView.setFocusable(true);
+ headerView.setFocusableInTouchMode(true);
+ if (mWrapper != null) {
+ viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
+ } else {
+ headerView.addOnLayoutChangeListener(sLayoutChangeListener);
+ }
+ }
+
+ };
+
+ private static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
+ v.setPivotY(v.getMeasuredHeight() / 2);
+ }
+ };
+
+ @Override
+ int getLayoutResourceId() {
+ return R.layout.lb_headers_fragment;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ final VerticalGridView listView = getVerticalGridView();
+ if (listView == null) {
+ return;
+ }
+ if (getBridgeAdapter() != null) {
+ FocusHighlightHelper.setupHeaderItemFocusHighlight(listView);
+ }
+ view.setBackgroundColor(getBackgroundColor());
+ updateFadingEdgeToBrandColor(getBackgroundColor());
+ updateListViewVisibility();
+ }
+
+ private void updateListViewVisibility() {
+ final VerticalGridView listView = getVerticalGridView();
+ if (listView != null) {
+ getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
+ if (!mHeadersGone) {
+ if (mHeadersEnabled) {
+ listView.setChildrenVisibility(View.VISIBLE);
+ } else {
+ listView.setChildrenVisibility(View.INVISIBLE);
+ }
+ }
+ }
+ }
+
+ void setHeadersEnabled(boolean enabled) {
+ mHeadersEnabled = enabled;
+ updateListViewVisibility();
+ }
+
+ void setHeadersGone(boolean gone) {
+ mHeadersGone = gone;
+ updateListViewVisibility();
+ }
+
+ static class NoOverlappingFrameLayout extends FrameLayout {
+
+ public NoOverlappingFrameLayout(Context context) {
+ super(context);
+ }
+
+ /**
+ * Avoid creating hardware layer for header dock.
+ */
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+ }
+
+ // Wrapper needed because of conflict between RecyclerView's use of alpha
+ // for ADD animations, and RowHeaderPresnter's use of alpha for selected level.
+ private final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
+ @Override
+ public void wrap(View wrapper, View wrapped) {
+ ((FrameLayout) wrapper).addView(wrapped);
+ }
+
+ @Override
+ public View createWrapper(View root) {
+ return new NoOverlappingFrameLayout(root.getContext());
+ }
+ };
+ @Override
+ void updateAdapter() {
+ super.updateAdapter();
+ ItemBridgeAdapter adapter = getBridgeAdapter();
+ if (adapter != null) {
+ adapter.setAdapterListener(mAdapterListener);
+ adapter.setWrapper(mWrapper);
+ }
+ if (adapter != null && getVerticalGridView() != null) {
+ FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView());
+ }
+ }
+
+ void setBackgroundColor(int color) {
+ mBackgroundColor = color;
+ mBackgroundColorSet = true;
+
+ if (getView() != null) {
+ getView().setBackgroundColor(mBackgroundColor);
+ updateFadingEdgeToBrandColor(mBackgroundColor);
+ }
+ }
+
+ private void updateFadingEdgeToBrandColor(int backgroundColor) {
+ View fadingView = getView().findViewById(R.id.fade_out_edge);
+ Drawable background = fadingView.getBackground();
+ if (background instanceof GradientDrawable) {
+ background.mutate();
+ ((GradientDrawable) background).setColors(
+ new int[] {Color.TRANSPARENT, backgroundColor});
+ }
+ }
+
+ int getBackgroundColor() {
+ if (getActivity() == null) {
+ throw new IllegalStateException("Activity must be attached");
+ }
+
+ if (mBackgroundColorSet) {
+ return mBackgroundColor;
+ }
+
+ TypedValue outValue = new TypedValue();
+ getActivity().getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
+ return getResources().getColor(outValue.resourceId);
+ }
+
+ @Override
+ void onTransitionStart() {
+ super.onTransitionStart();
+ if (!mHeadersEnabled) {
+ // When enabling headers fragment, the RowHeaderView gets a focus but
+ // isShown() is still false because its parent is INVSIBILE, accessibility
+ // event is not sent.
+ // Workaround is: prevent focus to a child view during transition and put
+ // focus on it after transition is done.
+ final VerticalGridView listView = getVerticalGridView();
+ if (listView != null) {
+ listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ if (listView.hasFocus()) {
+ listView.requestFocus();
+ }
+ }
+ }
+ }
+
+ @Override
+ void onTransitionEnd() {
+ if (mHeadersEnabled) {
+ final VerticalGridView listView = getVerticalGridView();
+ if (listView != null) {
+ listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ if (listView.hasFocus()) {
+ listView.requestFocus();
+ }
+ }
+ }
+ super.onTransitionEnd();
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
new file mode 100644
index 0000000..2f04229
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
@@ -0,0 +1,234 @@
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+/**
+ * A helper class for implementing a glue layer between a
+ * {@link PlaybackOverlayFragment} and a
+ * {@link android.support.v4.media.session.MediaControllerCompat}.
+ */
+public abstract class MediaControllerGlue extends PlaybackControlGlue {
+ private static final String TAG = "MediaControllerGlue";
+ private static final boolean DEBUG = false;
+
+ private MediaControllerCompat mMediaController;
+
+ private final MediaControllerCompat.Callback mCallback = new MediaControllerCompat.Callback() {
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) {
+ if (DEBUG) Log.v(TAG, "onMetadataChanged");
+ MediaControllerGlue.this.onMetadataChanged();
+ }
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ if (DEBUG) Log.v(TAG, "onPlaybackStateChanged");
+ onStateChanged();
+ }
+ @Override
+ public void onSessionDestroyed() {
+ if (DEBUG) Log.v(TAG, "onSessionDestroyed");
+ mMediaController = null;
+ }
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (DEBUG) Log.v(TAG, "onSessionEvent");
+ }
+ };
+
+ /**
+ * Constructor for the glue.
+ *
+ * <p>The {@link PlaybackOverlayFragment} must be passed in.
+ * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
+ * {@link PlaybackOverlayFragment.InputEventHandler}
+ * will be set on the fragment.
+ * </p>
+ *
+ * @param context
+ * @param fragment
+ * @param seekSpeeds Array of seek speeds for fast forward and rewind.
+ */
+ public MediaControllerGlue(Context context,
+ PlaybackOverlayFragment fragment,
+ int[] seekSpeeds) {
+ super(context, fragment, seekSpeeds);
+ }
+
+ /**
+ * Constructor for the glue.
+ *
+ * <p>The {@link PlaybackOverlayFragment} must be passed in.
+ * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
+ * {@link PlaybackOverlayFragment.InputEventHandler}
+ * will be set on the fragment.
+ * </p>
+ *
+ * @param context
+ * @param fragment
+ * @param fastForwardSpeeds Array of seek speeds for fast forward.
+ * @param rewindSpeeds Array of seek speeds for rewind.
+ */
+ public MediaControllerGlue(Context context,
+ PlaybackOverlayFragment fragment,
+ int[] fastForwardSpeeds,
+ int[] rewindSpeeds) {
+ super(context, fragment, fastForwardSpeeds, rewindSpeeds);
+ }
+
+ /**
+ * Attaches to the given media controller.
+ */
+ public void attachToMediaController(MediaControllerCompat mediaController) {
+ if (mediaController != mMediaController) {
+ if (DEBUG) Log.v(TAG, "New media controller " + mediaController);
+ detach();
+ mMediaController = mediaController;
+ if (mMediaController != null) {
+ mMediaController.registerCallback(mCallback);
+ }
+ onMetadataChanged();
+ onStateChanged();
+ }
+ }
+
+ /**
+ * Detaches from the media controller. Must be called when the object is no longer
+ * needed.
+ */
+ public void detach() {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mCallback);
+ }
+ mMediaController = null;
+ }
+
+ /**
+ * Returns the media controller currently attached.
+ */
+ public final MediaControllerCompat getMediaController() {
+ return mMediaController;
+ }
+
+ @Override
+ public boolean hasValidMedia() {
+ return mMediaController != null && mMediaController.getMetadata() != null;
+ }
+
+ @Override
+ public boolean isMediaPlaying() {
+ return mMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
+ }
+
+ @Override
+ public int getCurrentSpeedId() {
+ int speed = (int) mMediaController.getPlaybackState().getPlaybackSpeed();
+ if (speed == 0) {
+ return PLAYBACK_SPEED_PAUSED;
+ } else if (speed == 1) {
+ return PLAYBACK_SPEED_NORMAL;
+ } else if (speed > 0) {
+ int[] seekSpeeds = getFastForwardSpeeds();
+ for (int index = 0; index < seekSpeeds.length; index++) {
+ if (speed == seekSpeeds[index]) {
+ return PLAYBACK_SPEED_FAST_L0 + index;
+ }
+ }
+ } else {
+ int[] seekSpeeds = getRewindSpeeds();
+ for (int index = 0; index < seekSpeeds.length; index++) {
+ if (-speed == seekSpeeds[index]) {
+ return -PLAYBACK_SPEED_FAST_L0 - index;
+ }
+ }
+ }
+ Log.w(TAG, "Couldn't find index for speed " + speed);
+ return PLAYBACK_SPEED_INVALID;
+ }
+
+ @Override
+ public CharSequence getMediaTitle() {
+ return mMediaController.getMetadata().getDescription().getTitle();
+ }
+
+ @Override
+ public CharSequence getMediaSubtitle() {
+ return mMediaController.getMetadata().getDescription().getSubtitle();
+ }
+
+ @Override
+ public int getMediaDuration() {
+ return (int) mMediaController.getMetadata().getLong(
+ MediaMetadataCompat.METADATA_KEY_DURATION);
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ return (int) mMediaController.getPlaybackState().getPosition();
+ }
+
+ @Override
+ public Drawable getMediaArt() {
+ Bitmap bitmap = mMediaController.getMetadata().getDescription().getIconBitmap();
+ return bitmap == null ? null : new BitmapDrawable(getContext().getResources(), bitmap);
+ }
+
+ @Override
+ public long getSupportedActions() {
+ long result = 0;
+ long actions = mMediaController.getPlaybackState().getActions();
+ if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
+ result |= ACTION_PLAY_PAUSE;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
+ result |= ACTION_SKIP_TO_NEXT;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
+ result |= ACTION_SKIP_TO_PREVIOUS;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
+ result |= ACTION_FAST_FORWARD;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
+ result |= ACTION_REWIND;
+ }
+ return result;
+ }
+
+ @Override
+ protected void startPlayback(int speed) {
+ if (DEBUG) Log.v(TAG, "startPlayback speed " + speed);
+ if (speed == PLAYBACK_SPEED_NORMAL) {
+ mMediaController.getTransportControls().play();
+ } else if (speed > 0) {
+ mMediaController.getTransportControls().fastForward();
+ } else {
+ mMediaController.getTransportControls().rewind();
+ }
+ }
+
+ @Override
+ protected void pausePlayback() {
+ if (DEBUG) Log.v(TAG, "pausePlayback");
+ mMediaController.getTransportControls().pause();
+ }
+
+ @Override
+ protected void skipToNext() {
+ if (DEBUG) Log.v(TAG, "skipToNext");
+ mMediaController.getTransportControls().skipToNext();
+ }
+
+ @Override
+ protected void skipToPrevious() {
+ if (DEBUG) Log.v(TAG, "skipToPrevious");
+ mMediaController.getTransportControls().skipToPrevious();
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
new file mode 100644
index 0000000..41be7fc
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -0,0 +1,803 @@
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+
+
+/**
+ * A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow} and
+ * {@link PlaybackOverlayFragment} that implements a recommended approach to handling standard
+ * playback control actions such as play/pause, fast forward/rewind at progressive speed levels,
+ * and skip to next/previous. This helper class is a glue layer in that it manages the
+ * configuration of and interaction between the leanback UI components by defining a functional
+ * interface to the media player.
+ *
+ * <p>You can instantiate a concrete subclass such as {@link MediaControllerGlue} or you must
+ * subclass this abstract helper. To create a subclass you must implement all of the
+ * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
+ * {@link #onStateChanged()} appropriately.
+ * </p>
+ *
+ * <p>To use an instance of the glue layer, first construct an instance. Constructor parameters
+ * inform the glue what speed levels are supported for fast forward/rewind. If you have your own
+ * controls row you must pass it to {@link #setControlsRow}. The row will be updated by the glue
+ * layer based on the media metadata and playback state. Alternatively, you may call
+ * {@link #createControlsRowAndPresenter()} which will set a controls row and return
+ * a row presenter you can use to present the row.
+ * </p>
+ *
+ * <p>The helper sets a {@link android.support.v17.leanback.widget.SparseArrayObjectAdapter}
+ * on the controls row as the primary actions adapter, and adds actions to it. You can provide
+ * additional actions by overriding {@link #createPrimaryActionsAdapter}. This helper does not
+ * deal in secondary actions so those you may add separately.
+ * </p>
+ *
+ * <p>The helper sets an {@link android.support.v17.leanback.widget.OnItemViewClickedListener}
+ * on the fragment. To receive callbacks on clicks for elements unknown to the helper, pass
+ * a listener to {@link #setOnItemViewClickedListener}.
+ * </p>
+ *
+ * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
+ * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
+ * {@link #getUpdatePeriod()} provides a recommended update period.
+ * </p>
+ *
+ */
+public abstract class PlaybackControlGlue {
+ /**
+ * The adapter key for the first custom control on the right side
+ * of the predefined primary controls.
+ */
+ public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
+
+ /**
+ * The adapter key for the skip to previous control.
+ */
+ public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
+
+ /**
+ * The adapter key for the rewind control.
+ */
+ public static final int ACTION_REWIND = 0x20;
+
+ /**
+ * The adapter key for the play/pause control.
+ */
+ public static final int ACTION_PLAY_PAUSE = 0x40;
+
+ /**
+ * The adapter key for the fast forward control.
+ */
+ public static final int ACTION_FAST_FORWARD = 0x80;
+
+ /**
+ * The adapter key for the skip to next control.
+ */
+ public static final int ACTION_SKIP_TO_NEXT = 0x100;
+
+ /**
+ * The adapter key for the first custom control on the right side
+ * of the predefined primary controls.
+ */
+ public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
+
+ /**
+ * Invalid playback speed.
+ */
+ public static final int PLAYBACK_SPEED_INVALID = -1;
+
+ /**
+ * Speed representing playback state that is paused.
+ */
+ public static final int PLAYBACK_SPEED_PAUSED = 0;
+
+ /**
+ * Speed representing playback state that is playing normally.
+ */
+ public static final int PLAYBACK_SPEED_NORMAL = 1;
+
+ /**
+ * The initial (level 0) fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L0 = 10;
+
+ /**
+ * The level 1 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L1 = 11;
+
+ /**
+ * The level 2 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L2 = 12;
+
+ /**
+ * The level 3 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L3 = 13;
+
+ /**
+ * The level 4 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L4 = 14;
+
+ private static final String TAG = "PlaybackControlGlue";
+ private static final boolean DEBUG = false;
+
+ private static final int MSG_UPDATE_PLAYBACK_STATE = 100;
+ private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
+ private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4 -
+ PLAYBACK_SPEED_FAST_L0 + 1;
+
+ private final PlaybackOverlayFragment mFragment;
+ private final Context mContext;
+ private final int[] mFastForwardSpeeds;
+ private final int[] mRewindSpeeds;
+ private PlaybackControlsRow mControlsRow;
+ private SparseArrayObjectAdapter mPrimaryActionsAdapter;
+ private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
+ private PlaybackControlsRow.SkipNextAction mSkipNextAction;
+ private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
+ private PlaybackControlsRow.FastForwardAction mFastForwardAction;
+ private PlaybackControlsRow.RewindAction mRewindAction;
+ private OnItemViewClickedListener mExternalOnItemViewClickedListener;
+ private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+ private boolean mFadeWhenPlaying = true;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
+ updatePlaybackState();
+ }
+ }
+ };
+
+ private final OnItemViewClickedListener mOnItemViewClickedListener =
+ new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder viewHolder, Object object,
+ RowPresenter.ViewHolder viewHolder2, Row row) {
+ if (DEBUG) Log.v(TAG, "onItemClicked " + object);
+ boolean handled = false;
+ if (object instanceof Action) {
+ handled = handleActionClicked((Action) object);
+ }
+ if (!handled && mExternalOnItemViewClickedListener != null) {
+ mExternalOnItemViewClickedListener.onItemClicked(viewHolder, object,
+ viewHolder2, row);
+ }
+ }
+ };
+
+ private final PlaybackOverlayFragment.InputEventHandler mInputEventHandler =
+ new PlaybackOverlayFragment.InputEventHandler() {
+ @Override
+ public boolean handleInputEvent(InputEvent event) {
+ boolean result = false;
+ if (event instanceof KeyEvent &&
+ ((KeyEvent) event).getAction() == KeyEvent.ACTION_DOWN) {
+ int keyCode = ((KeyEvent) event).getKeyCode();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_BACK:
+ if (mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0 ||
+ mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
+ mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+ startPlayback(mPlaybackSpeed);
+ updatePlaybackStatusAfterUserAction();
+ result = (keyCode == KeyEvent.KEYCODE_BACK);
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ if (mPlayPauseAction != null) {
+ handleActionClicked(mPlayPauseAction);
+ result = true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ if (mFastForwardAction != null) {
+ handleActionClicked(mFastForwardAction);
+ result = true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ if (mRewindAction != null) {
+ handleActionClicked(mRewindAction);
+ result = true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ if (mSkipPreviousAction != null) {
+ handleActionClicked(mSkipPreviousAction);
+ result = true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ if (mSkipNextAction != null) {
+ handleActionClicked(mSkipNextAction);
+ result = true;
+ }
+ break;
+ }
+ }
+ return result;
+ }
+ };
+
+ /**
+ * Constructor for the glue.
+ *
+ * <p>The {@link PlaybackOverlayFragment} must be passed in.
+ * A {@link OnItemViewClickedListener} and {@link PlaybackOverlayFragment.InputEventHandler}
+ * will be set on the fragment.
+ * </p>
+ *
+ * @param context
+ * @param fragment
+ * @param seekSpeeds Array of seek speeds for fast forward and rewind.
+ */
+ public PlaybackControlGlue(Context context,
+ PlaybackOverlayFragment fragment,
+ int[] seekSpeeds) {
+ this(context, fragment, seekSpeeds, seekSpeeds);
+ }
+
+ /**
+ * Constructor for the glue.
+ *
+ * <p>The {@link PlaybackOverlayFragment} must be passed in.
+ * A {@link OnItemViewClickedListener} and {@link PlaybackOverlayFragment.InputEventHandler}
+ * will be set on the fragment.
+ * </p>
+ *
+ * @param context
+ * @param fragment
+ * @param fastForwardSpeeds Array of seek speeds for fast forward.
+ * @param rewindSpeeds Array of seek speeds for rewind.
+ */
+ public PlaybackControlGlue(Context context,
+ PlaybackOverlayFragment fragment,
+ int[] fastForwardSpeeds,
+ int[] rewindSpeeds) {
+ mContext = context;
+ mFragment = fragment;
+ if (mFragment.getOnItemViewClickedListener() != null) {
+ throw new IllegalStateException("Fragment OnItemViewClickedListener already present");
+ }
+ mFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ if (mFragment.getInputEventHandler() != null) {
+ throw new IllegalStateException("Fragment InputEventListener already present");
+ }
+ mFragment.setInputEventHandler(mInputEventHandler);
+ if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+ throw new IllegalStateException("invalid fastForwardSpeeds array size");
+ }
+ mFastForwardSpeeds = fastForwardSpeeds;
+ if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+ throw new IllegalStateException("invalid rewindSpeeds array size");
+ }
+ mRewindSpeeds = rewindSpeeds;
+ }
+
+ /**
+ * Helper method for instantiating a
+ * {@link android.support.v17.leanback.widget.PlaybackControlsRow} and corresponding
+ * {@link android.support.v17.leanback.widget.PlaybackControlsRowPresenter}.
+ */
+ public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+ PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
+ setControlsRow(controlsRow);
+
+ return new PlaybackControlsRowPresenter(new AbstractDetailsDescriptionPresenter() {
+ @Override
+ protected void onBindDescription(AbstractDetailsDescriptionPresenter.ViewHolder
+ viewHolder, Object object) {
+ PlaybackControlGlue glue = (PlaybackControlGlue) object;
+ if (glue.hasValidMedia()) {
+ viewHolder.getTitle().setText(glue.getMediaTitle());
+ viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
+ } else {
+ viewHolder.getTitle().setText("");
+ viewHolder.getSubtitle().setText("");
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns the fragment.
+ */
+ public PlaybackOverlayFragment getFragment() {
+ return mFragment;
+ }
+
+ /**
+ * Returns the context.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns the fast forward speeds.
+ */
+ public int[] getFastForwardSpeeds() {
+ return mFastForwardSpeeds;
+ }
+
+ /**
+ * Returns the rewind speeds.
+ */
+ public int[] getRewindSpeeds() {
+ return mRewindSpeeds;
+ }
+
+ /**
+ * Sets the controls to fade after a timeout when media is playing.
+ */
+ public void setFadingEnabled(boolean enable) {
+ mFadeWhenPlaying = enable;
+ if (!mFadeWhenPlaying) {
+ mFragment.setFadingEnabled(false);
+ }
+ }
+
+ /**
+ * Returns true if controls are set to fade when media is playing.
+ */
+ public boolean isFadingEnabled() {
+ return mFadeWhenPlaying;
+ }
+
+ /**
+ * Set the {@link OnItemViewClickedListener} to be called if the click event
+ * is not handled internally.
+ * @param listener
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ mExternalOnItemViewClickedListener = listener;
+ }
+
+ /**
+ * Returns the {@link OnItemViewClickedListener}.
+ */
+ public OnItemViewClickedListener getOnItemViewClickedListener() {
+ return mExternalOnItemViewClickedListener;
+ }
+
+ /**
+ * Sets the controls row to be managed by the glue layer.
+ * The primary actions and playback state related aspects of the row
+ * are updated by the glue.
+ */
+ public void setControlsRow(PlaybackControlsRow controlsRow) {
+ mControlsRow = controlsRow;
+ mPrimaryActionsAdapter = createPrimaryActionsAdapter(
+ new ControlButtonPresenterSelector());
+ mControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
+ updateControlsRow();
+ }
+
+ /**
+ * Returns the playback controls row managed by the glue layer.
+ */
+ public PlaybackControlsRow getControlsRow() {
+ return mControlsRow;
+ }
+
+ /**
+ * Override this to start/stop a runnable to call {@link #updateProgress} at
+ * an interval such as {@link #getUpdatePeriod}.
+ */
+ public void enableProgressUpdating(boolean enable) {
+ }
+
+ /**
+ * Returns the time period in milliseconds that should be used
+ * to update the progress. See {@link #updateProgress()}.
+ */
+ public int getUpdatePeriod() {
+ // TODO: calculate a better update period based on total duration and screen size
+ return 500;
+ }
+
+ /**
+ * Updates the progress bar based on the current media playback position.
+ */
+ public void updateProgress() {
+ int position = getCurrentPosition();
+ if (DEBUG) Log.v(TAG, "updateProgress " + position);
+ mControlsRow.setCurrentTime(position);
+ }
+
+ private boolean handleActionClicked(Action action) {
+ boolean handled = false;
+ if (action == mPlayPauseAction) {
+ if (mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
+ mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+ startPlayback(mPlaybackSpeed);
+ } else {
+ mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
+ pausePlayback();
+ }
+ updatePlaybackStatusAfterUserAction();
+ handled = true;
+ } else if (action == mSkipNextAction) {
+ skipToNext();
+ handled = true;
+ } else if (action == mSkipPreviousAction) {
+ skipToPrevious();
+ handled = true;
+ } else if (action == mFastForwardAction) {
+ if (mPlaybackSpeed < getMaxForwardSpeedId()) {
+ switch (mPlaybackSpeed) {
+ case PLAYBACK_SPEED_NORMAL:
+ case PLAYBACK_SPEED_PAUSED:
+ mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
+ break;
+ case PLAYBACK_SPEED_FAST_L0:
+ case PLAYBACK_SPEED_FAST_L1:
+ case PLAYBACK_SPEED_FAST_L2:
+ case PLAYBACK_SPEED_FAST_L3:
+ mPlaybackSpeed++;
+ break;
+ }
+ startPlayback(mPlaybackSpeed);
+ updatePlaybackStatusAfterUserAction();
+ }
+ handled = true;
+ } else if (action == mRewindAction) {
+ if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
+ switch (mPlaybackSpeed) {
+ case PLAYBACK_SPEED_NORMAL:
+ case PLAYBACK_SPEED_PAUSED:
+ mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
+ break;
+ case -PLAYBACK_SPEED_FAST_L0:
+ case -PLAYBACK_SPEED_FAST_L1:
+ case -PLAYBACK_SPEED_FAST_L2:
+ case -PLAYBACK_SPEED_FAST_L3:
+ mPlaybackSpeed--;
+ break;
+ }
+ startPlayback(mPlaybackSpeed);
+ updatePlaybackStatusAfterUserAction();
+ }
+ handled = true;
+ }
+ return handled;
+ }
+
+ private int getMaxForwardSpeedId() {
+ return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
+ }
+
+ private int getMaxRewindSpeedId() {
+ return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
+ }
+
+ private void updateControlsRow() {
+ updateRowMetadata();
+ mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
+ updatePlaybackState();
+ }
+
+ private void updatePlaybackStatusAfterUserAction() {
+ updatePlaybackState(mPlaybackSpeed);
+ // Sync playback state after a delay
+ mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE,
+ UPDATE_PLAYBACK_STATE_DELAY_MS);
+ }
+
+ private void updateRowMetadata() {
+ if (mControlsRow == null) {
+ return;
+ }
+
+ if (DEBUG) Log.v(TAG, "updateRowMetadata hasValidMedia " + hasValidMedia());
+
+ if (!hasValidMedia()) {
+ mControlsRow.setImageDrawable(null);
+ mControlsRow.setTotalTime(0);
+ mControlsRow.setCurrentTime(0);
+ } else {
+ mControlsRow.setImageDrawable(getMediaArt());
+ mControlsRow.setTotalTime(getMediaDuration());
+ mControlsRow.setCurrentTime(getCurrentPosition());
+ }
+
+ onRowChanged(mControlsRow);
+ }
+
+ private void updatePlaybackState() {
+ if (hasValidMedia()) {
+ mPlaybackSpeed = getCurrentSpeedId();
+ updatePlaybackState(mPlaybackSpeed);
+ }
+ }
+
+ private void updatePlaybackState(int playbackSpeed) {
+ if (mControlsRow == null) {
+ return;
+ }
+
+ final long actions = getSupportedActions();
+ if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0) {
+ if (mSkipPreviousAction == null) {
+ mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(mContext);
+ }
+ mPrimaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
+ } else {
+ mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
+ mSkipPreviousAction = null;
+ }
+ if ((actions & ACTION_REWIND) != 0) {
+ if (mRewindAction == null) {
+ mRewindAction = new PlaybackControlsRow.RewindAction(mContext,
+ mRewindSpeeds.length);
+ }
+ mPrimaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
+ } else {
+ mPrimaryActionsAdapter.clear(ACTION_REWIND);
+ mRewindAction = null;
+ }
+ if ((actions & ACTION_PLAY_PAUSE) != 0) {
+ if (mPlayPauseAction == null) {
+ mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(mContext);
+ }
+ mPrimaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
+ } else {
+ mPrimaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
+ mPlayPauseAction = null;
+ }
+ if ((actions & ACTION_FAST_FORWARD) != 0) {
+ if (mFastForwardAction == null) {
+ mFastForwardAction = new PlaybackControlsRow.FastForwardAction(mContext,
+ mFastForwardSpeeds.length);
+ }
+ mPrimaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
+ } else {
+ mPrimaryActionsAdapter.clear(ACTION_FAST_FORWARD);
+ mFastForwardAction = null;
+ }
+ if ((actions & ACTION_SKIP_TO_NEXT) != 0) {
+ if (mSkipNextAction == null) {
+ mSkipNextAction = new PlaybackControlsRow.SkipNextAction(mContext);
+ }
+ mPrimaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
+ } else {
+ mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
+ mSkipNextAction = null;
+ }
+
+ if (mFastForwardAction != null) {
+ int index = 0;
+ if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
+ index = playbackSpeed - PLAYBACK_SPEED_FAST_L0;
+ if (playbackSpeed < getMaxForwardSpeedId()) {
+ index++;
+ }
+ }
+ if (mFastForwardAction.getIndex() != index) {
+ mFastForwardAction.setIndex(index);
+ notifyItemChanged(mPrimaryActionsAdapter, mFastForwardAction);
+ }
+ }
+ if (mRewindAction != null) {
+ int index = 0;
+ if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
+ index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0;
+ if (-playbackSpeed < getMaxRewindSpeedId()) {
+ index++;
+ }
+ }
+ if (mRewindAction.getIndex() != index) {
+ mRewindAction.setIndex(index);
+ notifyItemChanged(mPrimaryActionsAdapter, mRewindAction);
+ }
+ }
+
+ if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
+ updateProgress();
+ enableProgressUpdating(false);
+ } else {
+ enableProgressUpdating(true);
+ }
+
+ if (mFadeWhenPlaying) {
+ mFragment.setFadingEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
+ }
+
+ if (mPlayPauseAction != null) {
+ int index = playbackSpeed == PLAYBACK_SPEED_PAUSED ?
+ PlaybackControlsRow.PlayPauseAction.PLAY :
+ PlaybackControlsRow.PlayPauseAction.PAUSE;
+ if (mPlayPauseAction.getIndex() != index) {
+ mPlayPauseAction.setIndex(index);
+ notifyItemChanged(mPrimaryActionsAdapter, mPlayPauseAction);
+ }
+ }
+ }
+
+ private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
+ int index = adapter.indexOf(object);
+ if (index >= 0) {
+ adapter.notifyArrayItemRangeChanged(index, 1);
+ }
+ }
+
+ private static String getSpeedString(int speed) {
+ switch (speed) {
+ case PLAYBACK_SPEED_INVALID:
+ return "PLAYBACK_SPEED_INVALID";
+ case PLAYBACK_SPEED_PAUSED:
+ return "PLAYBACK_SPEED_PAUSED";
+ case PLAYBACK_SPEED_NORMAL:
+ return "PLAYBACK_SPEED_NORMAL";
+ case PLAYBACK_SPEED_FAST_L0:
+ return "PLAYBACK_SPEED_FAST_L0";
+ case PLAYBACK_SPEED_FAST_L1:
+ return "PLAYBACK_SPEED_FAST_L1";
+ case PLAYBACK_SPEED_FAST_L2:
+ return "PLAYBACK_SPEED_FAST_L2";
+ case PLAYBACK_SPEED_FAST_L3:
+ return "PLAYBACK_SPEED_FAST_L3";
+ case PLAYBACK_SPEED_FAST_L4:
+ return "PLAYBACK_SPEED_FAST_L4";
+ case -PLAYBACK_SPEED_FAST_L0:
+ return "-PLAYBACK_SPEED_FAST_L0";
+ case -PLAYBACK_SPEED_FAST_L1:
+ return "-PLAYBACK_SPEED_FAST_L1";
+ case -PLAYBACK_SPEED_FAST_L2:
+ return "-PLAYBACK_SPEED_FAST_L2";
+ case -PLAYBACK_SPEED_FAST_L3:
+ return "-PLAYBACK_SPEED_FAST_L3";
+ case -PLAYBACK_SPEED_FAST_L4:
+ return "-PLAYBACK_SPEED_FAST_L4";
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if there is a valid media item.
+ */
+ public abstract boolean hasValidMedia();
+
+ /**
+ * Returns true if media is currently playing.
+ */
+ public abstract boolean isMediaPlaying();
+
+ /**
+ * Returns the title of the media item.
+ */
+ public abstract CharSequence getMediaTitle();
+
+ /**
+ * Returns the subtitle of the media item.
+ */
+ public abstract CharSequence getMediaSubtitle();
+
+ /**
+ * Returns the duration of the media item in milliseconds.
+ */
+ public abstract int getMediaDuration();
+
+ /**
+ * Returns a bitmap of the art for the media item.
+ */
+ public abstract Drawable getMediaArt();
+
+ /**
+ * Returns a bitmask of actions supported by the media player.
+ */
+ public abstract long getSupportedActions();
+
+ /**
+ * Returns the current playback speed. When playing normally,
+ * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
+ */
+ public abstract int getCurrentSpeedId();
+
+ /**
+ * Returns the current position of the media item in milliseconds.
+ */
+ public abstract int getCurrentPosition();
+
+ /**
+ * Start playback at the given speed.
+ * @param speed The desired playback speed. For normal playback this will be
+ * {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
+ * and negative values for rewind.
+ */
+ protected abstract void startPlayback(int speed);
+
+ /**
+ * Pause playback.
+ */
+ protected abstract void pausePlayback();
+
+ /**
+ * Skip to the next track.
+ */
+ protected abstract void skipToNext();
+
+ /**
+ * Skip to the previous track.
+ */
+ protected abstract void skipToPrevious();
+
+ /**
+ * Invoked when the playback controls row has changed. The adapter containing this row
+ * should be notified.
+ */
+ protected abstract void onRowChanged(PlaybackControlsRow row);
+
+ /**
+ * Creates the primary action adapter. May be overridden to add additional primary
+ * actions to the adapter.
+ */
+ protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+ PresenterSelector presenterSelector) {
+ return new SparseArrayObjectAdapter(presenterSelector);
+ }
+
+ /**
+ * Must be called appropriately by a subclass when the playback state has changed.
+ */
+ protected void onStateChanged() {
+ if (DEBUG) Log.v(TAG, "onStateChanged");
+ // If a pending control button update is present, delay
+ // the update until the state settles.
+ if (!hasValidMedia()) {
+ return;
+ }
+ if (mHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE)) {
+ mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
+ if (getCurrentSpeedId() != mPlaybackSpeed) {
+ if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE,
+ UPDATE_PLAYBACK_STATE_DELAY_MS);
+ } else {
+ if (DEBUG) Log.v(TAG, "Update state matches expectation");
+ updatePlaybackState();
+ }
+ } else {
+ updatePlaybackState();
+ }
+ }
+
+ /**
+ * Must be called appropriately by a subclass when the metadata state has changed.
+ */
+ protected void onMetadataChanged() {
+ if (DEBUG) Log.v(TAG, "onMetadataChanged");
+ updateRowMetadata();
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
index be88c0d..0d81005 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -19,6 +19,7 @@
import android.animation.AnimatorInflater;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
+import android.view.InputEvent;
import android.view.animation.AccelerateInterpolator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
@@ -41,9 +42,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
import java.util.ArrayList;
@@ -76,6 +74,16 @@
}
}
+ public interface InputEventHandler {
+ /**
+ * Called when an {@link InputEvent} is received.
+ *
+ * @return If the event should be consumed, return true. To allow the event to
+ * continue on to the next handler, return false.
+ */
+ public boolean handleInputEvent(InputEvent event);
+ }
+
private static final String TAG = "PlaybackOverlayFragment";
private static final boolean DEBUG = false;
private static final int ANIMATION_MULTIPLIER = 1;
@@ -97,6 +105,7 @@
private int mMajorFadeTranslateY, mMinorFadeTranslateY;
private int mAnimationTranslateY;
private OnFadeCompleteListener mFadeCompleteListener;
+ private InputEventHandler mInputEventHandler;
private boolean mFadingEnabled = true;
private int mFadingStatus = IDLE;
private int mBgAlpha;
@@ -155,21 +164,14 @@
private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
new VerticalGridView.OnTouchInterceptListener() {
public boolean onInterceptTouchEvent(MotionEvent event) {
- return onInterceptInputEvent();
- }
- };
-
- private final VerticalGridView.OnMotionInterceptListener mOnMotionInterceptListener =
- new VerticalGridView.OnMotionInterceptListener() {
- public boolean onInterceptMotionEvent(MotionEvent event) {
- return onInterceptInputEvent();
+ return onInterceptInputEvent(event);
}
};
private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
new VerticalGridView.OnKeyInterceptListener() {
public boolean onInterceptKeyEvent(KeyEvent event) {
- return onInterceptInputEvent();
+ return onInterceptInputEvent(event);
}
};
@@ -210,16 +212,15 @@
if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
if (enabled != mFadingEnabled) {
mFadingEnabled = enabled;
- if (isResumed()) {
- if (mFadingEnabled) {
- if (mFadingStatus == IDLE && !mHandler.hasMessages(START_FADE_OUT)) {
- startFadeTimer();
- }
- } else {
- // Ensure fully opaque
- mHandler.removeMessages(START_FADE_OUT);
- fade(true);
+ if (mFadingEnabled) {
+ if (isResumed() && mFadingStatus == IDLE
+ && !mHandler.hasMessages(START_FADE_OUT)) {
+ startFadeTimer();
}
+ } else {
+ // Ensure fully opaque
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(true);
}
}
}
@@ -246,6 +247,20 @@
}
/**
+ * Sets the input event handler.
+ */
+ public final void setInputEventHandler(InputEventHandler handler) {
+ mInputEventHandler = handler;
+ }
+
+ /**
+ * Returns the input event handler.
+ */
+ public final InputEventHandler getInputEventHandler() {
+ return mInputEventHandler;
+ }
+
+ /**
* Tickles the playback controls. Fades in the view if it was faded out,
* otherwise resets the fade out timer. Tickling on input events is handled
* by the fragment.
@@ -263,10 +278,43 @@
}
}
- private boolean onInterceptInputEvent() {
- if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus);
- boolean consumeEvent = (mFadingStatus == IDLE && mBgAlpha == 0);
- tickle();
+ private static boolean isConsumableKey(KeyEvent keyEvent) {
+ if (keyEvent.isSystem()) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean onInterceptInputEvent(InputEvent event) {
+ if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus +
+ " mBgAlpha " + mBgAlpha + " event " + event);
+ final boolean controlsHidden = (mFadingStatus == IDLE && mBgAlpha == 0);
+ boolean consumeEvent = controlsHidden;
+ int keyCode = KeyEvent.KEYCODE_UNKNOWN;
+
+ if (event instanceof KeyEvent) {
+ if (consumeEvent) {
+ consumeEvent = isConsumableKey((KeyEvent) event);
+ }
+ keyCode = ((KeyEvent) event).getKeyCode();
+ }
+ if (!consumeEvent && mInputEventHandler != null) {
+ consumeEvent = mInputEventHandler.handleInputEvent(event);
+ }
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // If fading enabled and controls are not hidden, back will be consumed to fade
+ // them out (even if the key was consumed by the handler).
+ if (mFadingEnabled && !controlsHidden) {
+ consumeEvent = true;
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(false);
+ } else if (consumeEvent) {
+ tickle();
+ }
+ } else {
+ // Any other key will show the controls
+ tickle();
+ }
return consumeEvent;
}
@@ -278,7 +326,6 @@
fade(true);
}
getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
- getVerticalGridView().setOnMotionInterceptListener(mOnMotionInterceptListener);
getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
}
@@ -381,6 +428,9 @@
final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
+ if (getVerticalGridView() == null) {
+ return;
+ }
final float fraction = (Float) arg0.getAnimatedValue();
for (View view : listener.mViews) {
if (getVerticalGridView().getChildPosition(view) > 0) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
new file mode 100644
index 0000000..a453f24
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -0,0 +1,741 @@
+/* This file is auto-generated from PlaybackOverlayFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.view.InputEvent;
+import android.view.animation.AccelerateInterpolator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v7.widget.RecyclerView;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.animation.LogAccelerateInterpolator;
+import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * A fragment for displaying playback controls and related content.
+ * The {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
+ * at position 0 in the adapter.
+ */
+public class PlaybackOverlaySupportFragment extends DetailsSupportFragment {
+
+ /**
+ * No background.
+ */
+ public static final int BG_NONE = 0;
+
+ /**
+ * A dark translucent background.
+ */
+ public static final int BG_DARK = 1;
+
+ /**
+ * A light translucent background.
+ */
+ public static final int BG_LIGHT = 2;
+
+ public static class OnFadeCompleteListener {
+ public void onFadeInComplete() {
+ }
+ public void onFadeOutComplete() {
+ }
+ }
+
+ public interface InputEventHandler {
+ /**
+ * Called when an {@link InputEvent} is received.
+ *
+ * @return If the event should be consumed, return true. To allow the event to
+ * continue on to the next handler, return false.
+ */
+ public boolean handleInputEvent(InputEvent event);
+ }
+
+ private static final String TAG = "PlaybackOverlaySupportFragment";
+ private static final boolean DEBUG = false;
+ private static final int ANIMATION_MULTIPLIER = 1;
+
+ private static int START_FADE_OUT = 1;
+
+ // Fading status
+ private static final int IDLE = 0;
+ private static final int IN = 1;
+ private static final int OUT = 2;
+
+ private int mAlignPosition;
+ private int mPaddingBottom;
+ private View mRootView;
+ private int mBackgroundType = BG_DARK;
+ private int mBgDarkColor;
+ private int mBgLightColor;
+ private int mShowTimeMs;
+ private int mMajorFadeTranslateY, mMinorFadeTranslateY;
+ private int mAnimationTranslateY;
+ private OnFadeCompleteListener mFadeCompleteListener;
+ private InputEventHandler mInputEventHandler;
+ private boolean mFadingEnabled = true;
+ private int mFadingStatus = IDLE;
+ private int mBgAlpha;
+ private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
+ private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
+ private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
+ private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
+ private boolean mTranslateAnimationEnabled;
+ private boolean mResetControlsToPrimaryActionsPending;
+ private RecyclerView.ItemAnimator mItemAnimator;
+
+ private final Animator.AnimatorListener mFadeListener =
+ new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ enableVerticalGridAnimations(false);
+ }
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
+ if (mBgAlpha > 0) {
+ enableVerticalGridAnimations(true);
+ startFadeTimer();
+ if (mFadeCompleteListener != null) {
+ mFadeCompleteListener.onFadeInComplete();
+ }
+ } else {
+ if (getVerticalGridView() != null) {
+ // Reset focus to the controls row
+ getVerticalGridView().setSelectedPosition(0);
+ resetControlsToPrimaryActions(null);
+ }
+ if (mFadeCompleteListener != null) {
+ mFadeCompleteListener.onFadeOutComplete();
+ }
+ }
+ mFadingStatus = IDLE;
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == START_FADE_OUT && mFadingEnabled) {
+ fade(false);
+ }
+ }
+ };
+
+ private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
+ new VerticalGridView.OnTouchInterceptListener() {
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return onInterceptInputEvent(event);
+ }
+ };
+
+ private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
+ new VerticalGridView.OnKeyInterceptListener() {
+ public boolean onInterceptKeyEvent(KeyEvent event) {
+ return onInterceptInputEvent(event);
+ }
+ };
+
+ private void setBgAlpha(int alpha) {
+ mBgAlpha = alpha;
+ if (mRootView != null) {
+ mRootView.getBackground().setAlpha(alpha);
+ }
+ }
+
+ private void enableVerticalGridAnimations(boolean enable) {
+ if (getVerticalGridView() != null) {
+ getVerticalGridView().setAnimateChildLayout(enable);
+ }
+ }
+
+ private void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
+ if (vh == null && getVerticalGridView() != null) {
+ vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
+ }
+ if (vh == null) {
+ mResetControlsToPrimaryActionsPending = true;
+ } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
+ mResetControlsToPrimaryActionsPending = false;
+ ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
+ (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
+ }
+ }
+
+ /**
+ * Enables or disables view fading. If enabled,
+ * the view will be faded in when the fragment starts,
+ * and will fade out after a time period. The timeout
+ * period is reset each time {@link #tickle} is called.
+ *
+ */
+ public void setFadingEnabled(boolean enabled) {
+ if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
+ if (enabled != mFadingEnabled) {
+ mFadingEnabled = enabled;
+ if (mFadingEnabled) {
+ if (isResumed() && mFadingStatus == IDLE
+ && !mHandler.hasMessages(START_FADE_OUT)) {
+ startFadeTimer();
+ }
+ } else {
+ // Ensure fully opaque
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(true);
+ }
+ }
+ }
+
+ /**
+ * Returns true if view fading is enabled.
+ */
+ public boolean isFadingEnabled() {
+ return mFadingEnabled;
+ }
+
+ /**
+ * Sets the listener to be called when fade in or out has completed.
+ */
+ public void setFadeCompleteListener(OnFadeCompleteListener listener) {
+ mFadeCompleteListener = listener;
+ }
+
+ /**
+ * Returns the listener to be called when fade in or out has completed.
+ */
+ public OnFadeCompleteListener getFadeCompleteListener() {
+ return mFadeCompleteListener;
+ }
+
+ /**
+ * Sets the input event handler.
+ */
+ public final void setInputEventHandler(InputEventHandler handler) {
+ mInputEventHandler = handler;
+ }
+
+ /**
+ * Returns the input event handler.
+ */
+ public final InputEventHandler getInputEventHandler() {
+ return mInputEventHandler;
+ }
+
+ /**
+ * Tickles the playback controls. Fades in the view if it was faded out,
+ * otherwise resets the fade out timer. Tickling on input events is handled
+ * by the fragment.
+ */
+ public void tickle() {
+ if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
+ if (!mFadingEnabled || !isResumed()) {
+ return;
+ }
+ if (mHandler.hasMessages(START_FADE_OUT)) {
+ // Restart the timer
+ startFadeTimer();
+ } else {
+ fade(true);
+ }
+ }
+
+ private static boolean isConsumableKey(KeyEvent keyEvent) {
+ if (keyEvent.isSystem()) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean onInterceptInputEvent(InputEvent event) {
+ if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus +
+ " mBgAlpha " + mBgAlpha + " event " + event);
+ final boolean controlsHidden = (mFadingStatus == IDLE && mBgAlpha == 0);
+ boolean consumeEvent = controlsHidden;
+ int keyCode = KeyEvent.KEYCODE_UNKNOWN;
+
+ if (event instanceof KeyEvent) {
+ if (consumeEvent) {
+ consumeEvent = isConsumableKey((KeyEvent) event);
+ }
+ keyCode = ((KeyEvent) event).getKeyCode();
+ }
+ if (!consumeEvent && mInputEventHandler != null) {
+ consumeEvent = mInputEventHandler.handleInputEvent(event);
+ }
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // If fading enabled and controls are not hidden, back will be consumed to fade
+ // them out (even if the key was consumed by the handler).
+ if (mFadingEnabled && !controlsHidden) {
+ consumeEvent = true;
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(false);
+ } else if (consumeEvent) {
+ tickle();
+ }
+ } else {
+ // Any other key will show the controls
+ tickle();
+ }
+ return consumeEvent;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mFadingEnabled) {
+ setBgAlpha(0);
+ fade(true);
+ }
+ getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
+ getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+ }
+
+ private void startFadeTimer() {
+ if (mHandler != null) {
+ mHandler.removeMessages(START_FADE_OUT);
+ mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
+ }
+ }
+
+ private static ValueAnimator loadAnimator(Context context, int resId) {
+ ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
+ animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
+ return animator;
+ }
+
+ private void loadBgAnimator() {
+ AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator arg0) {
+ setBgAlpha((Integer) arg0.getAnimatedValue());
+ }
+ };
+
+ mBgFadeInAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_in);
+ mBgFadeInAnimator.addUpdateListener(listener);
+ mBgFadeInAnimator.addListener(mFadeListener);
+
+ mBgFadeOutAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_out);
+ mBgFadeOutAnimator.addUpdateListener(listener);
+ mBgFadeOutAnimator.addListener(mFadeListener);
+ }
+
+ private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
+ private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
+
+ private View getControlRowView() {
+ if (getVerticalGridView() == null) {
+ return null;
+ }
+ RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
+ if (vh == null) {
+ return null;
+ }
+ return vh.itemView;
+ }
+
+ private void loadControlRowAnimator() {
+ final AnimatorListener listener = new AnimatorListener() {
+ @Override
+ void getViews(ArrayList<View> views) {
+ View view = getControlRowView();
+ if (view != null) {
+ views.add(view);
+ }
+ }
+ };
+ final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator arg0) {
+ View view = getControlRowView();
+ if (view != null) {
+ final float fraction = (Float) arg0.getAnimatedValue();
+ if (DEBUG) Log.v(TAG, "fraction " + fraction);
+ view.setAlpha(fraction);
+ view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+ }
+ }
+ };
+
+ mControlRowFadeInAnimator = loadAnimator(
+ getActivity(), R.animator.lb_playback_controls_fade_in);
+ mControlRowFadeInAnimator.addUpdateListener(updateListener);
+ mControlRowFadeInAnimator.addListener(listener);
+ mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+ mControlRowFadeOutAnimator = loadAnimator(
+ getActivity(), R.animator.lb_playback_controls_fade_out);
+ mControlRowFadeOutAnimator.addUpdateListener(updateListener);
+ mControlRowFadeOutAnimator.addListener(listener);
+ mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
+ }
+
+ private void loadOtherRowAnimator() {
+ final AnimatorListener listener = new AnimatorListener() {
+ @Override
+ void getViews(ArrayList<View> views) {
+ if (getVerticalGridView() == null) {
+ return;
+ }
+ final int count = getVerticalGridView().getChildCount();
+ for (int i = 0; i < count; i++) {
+ View view = getVerticalGridView().getChildAt(i);
+ if (view != null) {
+ views.add(view);
+ }
+ }
+ }
+ };
+ final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator arg0) {
+ if (getVerticalGridView() == null) {
+ return;
+ }
+ final float fraction = (Float) arg0.getAnimatedValue();
+ for (View view : listener.mViews) {
+ if (getVerticalGridView().getChildPosition(view) > 0) {
+ view.setAlpha(fraction);
+ view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+ }
+ }
+ }
+ };
+
+ mOtherRowFadeInAnimator = loadAnimator(
+ getActivity(), R.animator.lb_playback_controls_fade_in);
+ mOtherRowFadeInAnimator.addListener(listener);
+ mOtherRowFadeInAnimator.addUpdateListener(updateListener);
+ mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+ mOtherRowFadeOutAnimator = loadAnimator(
+ getActivity(), R.animator.lb_playback_controls_fade_out);
+ mOtherRowFadeOutAnimator.addListener(listener);
+ mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
+ mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
+ }
+
+ private void loadDescriptionAnimator() {
+ AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator arg0) {
+ if (getVerticalGridView() == null) {
+ return;
+ }
+ ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
+ getVerticalGridView().findViewHolderForPosition(0);
+ if (adapterVh != null && adapterVh.getViewHolder()
+ instanceof PlaybackControlsRowPresenter.ViewHolder) {
+ final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
+ adapterVh.getViewHolder()).mDescriptionViewHolder;
+ if (vh != null) {
+ vh.view.setAlpha((Float) arg0.getAnimatedValue());
+ }
+ }
+ }
+ };
+
+ mDescriptionFadeInAnimator = loadAnimator(
+ getActivity(), R.animator.lb_playback_description_fade_in);
+ mDescriptionFadeInAnimator.addUpdateListener(listener);
+ mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+ mDescriptionFadeOutAnimator = loadAnimator(
+ getActivity(), R.animator.lb_playback_description_fade_out);
+ mDescriptionFadeOutAnimator.addUpdateListener(listener);
+ }
+
+ private void fade(boolean fadeIn) {
+ if (DEBUG) Log.v(TAG, "fade " + fadeIn);
+ if (getView() == null) {
+ return;
+ }
+ if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
+ if (DEBUG) Log.v(TAG, "requested fade in progress");
+ return;
+ }
+ if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
+ if (DEBUG) Log.v(TAG, "fade is no-op");
+ return;
+ }
+
+ mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0 ?
+ mMajorFadeTranslateY : mMinorFadeTranslateY;
+
+ if (mFadingStatus == IDLE) {
+ if (fadeIn) {
+ mBgFadeInAnimator.start();
+ mControlRowFadeInAnimator.start();
+ mOtherRowFadeInAnimator.start();
+ mDescriptionFadeInAnimator.start();
+ } else {
+ mBgFadeOutAnimator.start();
+ mControlRowFadeOutAnimator.start();
+ mOtherRowFadeOutAnimator.start();
+ mDescriptionFadeOutAnimator.start();
+ }
+ } else {
+ if (fadeIn) {
+ mBgFadeOutAnimator.reverse();
+ mControlRowFadeOutAnimator.reverse();
+ mOtherRowFadeOutAnimator.reverse();
+ mDescriptionFadeOutAnimator.reverse();
+ } else {
+ mBgFadeInAnimator.reverse();
+ mControlRowFadeInAnimator.reverse();
+ mOtherRowFadeInAnimator.reverse();
+ mDescriptionFadeInAnimator.reverse();
+ }
+ }
+
+ // If fading in while control row is focused, set initial translationY so
+ // views slide in from below.
+ if (fadeIn && mFadingStatus == IDLE) {
+ final int count = getVerticalGridView().getChildCount();
+ for (int i = 0; i < count; i++) {
+ getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
+ }
+ }
+
+ mFadingStatus = fadeIn ? IN : OUT;
+ }
+
+ /**
+ * Sets the list of rows for the fragment.
+ */
+ @Override
+ public void setAdapter(ObjectAdapter adapter) {
+ if (getAdapter() != null) {
+ getAdapter().unregisterObserver(mObserver);
+ }
+ super.setAdapter(adapter);
+ if (adapter != null) {
+ adapter.registerObserver(mObserver);
+ }
+ }
+
+ @Override
+ void setVerticalGridViewLayout(VerticalGridView listview) {
+ if (listview == null) {
+ return;
+ }
+ // Padding affects alignment when last row is focused
+ // (last is first when there's only one row).
+ setBottomPadding(listview, mPaddingBottom);
+
+ // Item alignment affects focused row that isn't the last.
+ listview.setItemAlignmentOffset(mAlignPosition);
+ listview.setItemAlignmentOffsetPercent(100);
+
+ // Push rows to the bottom.
+ listview.setWindowAlignmentOffset(0);
+ listview.setWindowAlignmentOffsetPercent(100);
+ listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
+ }
+
+ private static void setBottomPadding(View view, int padding) {
+ view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
+ view.getPaddingRight(), padding);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mAlignPosition =
+ getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
+ mPaddingBottom =
+ getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
+ mBgDarkColor =
+ getResources().getColor(R.color.lb_playback_controls_background_dark);
+ mBgLightColor =
+ getResources().getColor(R.color.lb_playback_controls_background_light);
+ mShowTimeMs =
+ getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
+ mMajorFadeTranslateY =
+ getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
+ mMinorFadeTranslateY =
+ getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
+
+ loadBgAnimator();
+ loadControlRowAnimator();
+ loadOtherRowAnimator();
+ loadDescriptionAnimator();
+ }
+
+ /**
+ * Sets the background type.
+ *
+ * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
+ */
+ public void setBackgroundType(int type) {
+ switch (type) {
+ case BG_LIGHT:
+ case BG_DARK:
+ case BG_NONE:
+ if (type != mBackgroundType) {
+ mBackgroundType = type;
+ updateBackground();
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid background type");
+ }
+ }
+
+ /**
+ * Returns the background type.
+ */
+ public int getBackgroundType() {
+ return mBackgroundType;
+ }
+
+ private void updateBackground() {
+ if (mRootView != null) {
+ int color = mBgDarkColor;
+ switch (mBackgroundType) {
+ case BG_DARK: break;
+ case BG_LIGHT: color = mBgLightColor; break;
+ case BG_NONE: color = Color.TRANSPARENT; break;
+ }
+ mRootView.setBackground(new ColorDrawable(color));
+ }
+ }
+
+ private void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
+ // Add extra space between rows 0 and 1
+ if (vh == null && getVerticalGridView() != null) {
+ vh = (ItemBridgeAdapter.ViewHolder)
+ getVerticalGridView().findViewHolderForPosition(0);
+ }
+ if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
+ final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
+ ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
+ (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
+ adapterSize > 1);
+ }
+ }
+
+ private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+ new ItemBridgeAdapter.AdapterListener() {
+ @Override
+ public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+ if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
+ if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
+ if (DEBUG) Log.v(TAG, "setting alpha to 0");
+ vh.getViewHolder().view.setAlpha(0);
+ }
+ if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
+ resetControlsToPrimaryActions(vh);
+ }
+ }
+ @Override
+ public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+ if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
+ // Reset animation state
+ vh.getViewHolder().view.setAlpha(1f);
+ vh.getViewHolder().view.setTranslationY(0);
+ if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
+ Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
+ vh.getViewHolder()).mDescriptionViewHolder;
+ if (descriptionVh != null) {
+ descriptionVh.view.setAlpha(1f);
+ }
+ }
+ }
+ @Override
+ public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+ if (vh.getPosition() == 0) {
+ updateControlsBottomSpace(vh);
+ }
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mRootView = super.onCreateView(inflater, container, savedInstanceState);
+ mBgAlpha = 255;
+ updateBackground();
+ getRowsSupportFragment().setExternalAdapterListener(mAdapterListener);
+ return mRootView;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mRootView = null;
+ super.onDestroyView();
+ }
+
+ private final DataObserver mObserver = new DataObserver() {
+ public void onChanged() {
+ updateControlsBottomSpace(null);
+ }
+ };
+
+ static abstract class AnimatorListener implements Animator.AnimatorListener {
+ ArrayList<View> mViews = new ArrayList<View>();
+ ArrayList<Integer> mLayerType = new ArrayList<Integer>();
+
+ public void onAnimationCancel(Animator animation) {
+ }
+ public void onAnimationRepeat(Animator animation) {
+ }
+ public void onAnimationStart(Animator animation) {
+ getViews(mViews);
+ for (View view : mViews) {
+ mLayerType.add(view.getLayerType());
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ }
+ public void onAnimationEnd(Animator animation) {
+ for (int i = 0; i < mViews.size(); i++) {
+ mViews.get(i).setLayerType(mLayerType.get(i), null);
+ }
+ mLayerType.clear();
+ mViews.clear();
+ }
+ abstract void getViews(ArrayList<View> views);
+ };
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index 4e79ed3..ff61927 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -23,6 +23,7 @@
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.OnItemSelectedListener;
@@ -32,6 +33,7 @@
import android.support.v17.leanback.widget.Presenter;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -117,7 +119,11 @@
private boolean mExpand = true;
private boolean mViewsCreated;
private float mRowScaleFactor;
+ private int mAlignedTop;
private boolean mRowScaleEnabled;
+ private ScaleFrameLayout mScaleFrameLayout;
+ private boolean mInTransition;
+ private boolean mAfterEntranceTransition = true;
private OnItemSelectedListener mOnItemSelectedListener;
private OnItemViewSelectedListener mOnItemViewSelectedListener;
@@ -135,6 +141,11 @@
private ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
+ @Override
+ protected VerticalGridView findGridViewFromRoot(View view) {
+ return (VerticalGridView) view.findViewById(R.id.container_list);
+ }
+
/**
* Sets an item clicked listener on the fragment.
* OnItemClickedListener will override {@link View.OnClickListener} that
@@ -186,7 +197,7 @@
mExpand = expand;
VerticalGridView listView = getVerticalGridView();
if (listView != null) {
- updateRowScaling(!expand);
+ updateRowScaling();
final int count = listView.getChildCount();
if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
for (int i = 0; i < count; i++) {
@@ -249,7 +260,7 @@
}
@Override
- protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+ void onRowSelected(ViewGroup parent, View view, int position, long id) {
VerticalGridView listView = getVerticalGridView();
if (listView == null) {
return;
@@ -271,7 +282,7 @@
}
@Override
- protected int getLayoutResourceId() {
+ int getLayoutResourceId() {
return R.layout.lb_rows_fragment;
}
@@ -285,6 +296,14 @@
}
@Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+ mScaleFrameLayout = (ScaleFrameLayout) view.findViewById(R.id.scale_frame);
+ return view;
+ }
+
+ @Override
public void onViewCreated(View view, Bundle savedInstanceState) {
if (DEBUG) Log.v(TAG, "onViewCreated");
super.onViewCreated(view, savedInstanceState);
@@ -298,6 +317,12 @@
}
@Override
+ public void onDestroyView() {
+ mViewsCreated = false;
+ super.onDestroyView();
+ }
+
+ @Override
void setItemAlignment() {
super.setItemAlignment();
if (getVerticalGridView() != null) {
@@ -309,6 +334,23 @@
mExternalAdapterListener = listener;
}
+ /**
+ * Get the view that will change scale.
+ */
+ View getScaleView() {
+ return getVerticalGridView();
+ }
+
+ /**
+ * Set pivots to scale rows fragment.
+ */
+ void setScalePivots(float pivotX, float pivotY) {
+ // set pivot on ScaleFrameLayout, it will be propagated to its child VerticalGridView
+ // where we actually change scale.
+ mScaleFrameLayout.setPivotX(pivotX);
+ mScaleFrameLayout.setPivotY(pivotY);
+ }
+
private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
}
@@ -343,7 +385,8 @@
@Override
public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
VerticalGridView listView = getVerticalGridView();
- if (listView != null && ((RowPresenter) vh.getPresenter()).canDrawOutOfBounds()) {
+ if (listView != null) {
+ // set clip children false for slide animation
listView.setClipChildren(false);
}
setupSharedViewPool(vh);
@@ -368,6 +411,9 @@
setRowViewExpanded(vh, mExpand);
setOnItemSelectedListener(vh, mOnItemSelectedListener);
setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+ RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
+ RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+ rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
if (mExternalAdapterListener != null) {
mExternalAdapterListener.onAttachedToWindow(vh);
}
@@ -422,7 +468,7 @@
}
@Override
- protected void updateAdapter() {
+ void updateAdapter() {
super.updateAdapter();
mSelectedViewHolder = null;
mViewsCreated = false;
@@ -436,6 +482,7 @@
@Override
void onTransitionStart() {
super.onTransitionStart();
+ mInTransition = true;
freezeRows(true);
}
@@ -485,19 +532,43 @@
new ExpandPreLayout(callback).execute();
}
- private void updateRowScaling(boolean scale) {
- VerticalGridView view = getVerticalGridView();
- view.setClipChildren(!mRowScaleEnabled && scale);
- view.setPrimaryOverReach((mRowScaleEnabled && scale) ? 1f / mRowScaleFactor : 1f);
+ private boolean needsScale() {
+ return mRowScaleEnabled && !mExpand;
+ }
- final float scaleFactor = (mRowScaleEnabled && scale) ? mRowScaleFactor : 1f;
- view.setScaleX(scaleFactor);
- view.setScaleY(scaleFactor);
+ private void updateRowScaling() {
+ final float scaleFactor = needsScale() ? mRowScaleFactor : 1f;
+ mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+ getScaleView().setScaleY(scaleFactor);
+ getScaleView().setScaleX(scaleFactor);
+ updateWindowAlignOffset();
+ }
+
+ private void updateWindowAlignOffset() {
+ int alignOffset = mAlignedTop;
+ if (needsScale()) {
+ alignOffset = (int) (alignOffset / mRowScaleFactor + 0.5f);
+ }
+ getVerticalGridView().setWindowAlignmentOffset(alignOffset);
+ }
+
+ @Override
+ void setWindowAlignmentFromTop(int alignedTop) {
+ mAlignedTop = alignedTop;
+ final VerticalGridView gridView = getVerticalGridView();
+ if (gridView != null) {
+ updateWindowAlignOffset();
+ // align to a fixed position from top
+ gridView.setWindowAlignmentOffsetPercent(
+ VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+ gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ }
}
@Override
void onTransitionEnd() {
super.onTransitionEnd();
+ mInTransition = false;
freezeRows(false);
}
@@ -514,4 +585,23 @@
}
}
}
+
+ /**
+ * For rows that willing to participate entrance transition, this function
+ * hide views if afterTransition is true, show views if afterTransition is false.
+ */
+ void setEntranceTransitionState(boolean afterTransition) {
+ mAfterEntranceTransition = afterTransition;
+ VerticalGridView verticalView = getVerticalGridView();
+ if (verticalView != null) {
+ final int count = verticalView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+ verticalView.getChildViewHolder(verticalView.getChildAt(i));
+ RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+ RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+ rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
+ }
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
new file mode 100644
index 0000000..4e95878
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -0,0 +1,609 @@
+/* This file is auto-generated from RowsFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import java.util.ArrayList;
+
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * An ordered set of rows of leanback widgets.
+ */
+public class RowsSupportFragment extends BaseRowSupportFragment {
+
+ /**
+ * Internal helper class that manages row select animation and apply a default
+ * dim to each row.
+ */
+ final class RowViewHolderExtra implements TimeListener {
+ final RowPresenter mRowPresenter;
+ final Presenter.ViewHolder mRowViewHolder;
+
+ final TimeAnimator mSelectAnimator = new TimeAnimator();
+
+ int mSelectAnimatorDurationInUse;
+ Interpolator mSelectAnimatorInterpolatorInUse;
+ float mSelectLevelAnimStart;
+ float mSelectLevelAnimDelta;
+
+ RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) {
+ mRowPresenter = (RowPresenter) ibvh.getPresenter();
+ mRowViewHolder = ibvh.getViewHolder();
+ mSelectAnimator.setTimeListener(this);
+ }
+
+ @Override
+ public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+ if (mSelectAnimator.isRunning()) {
+ updateSelect(totalTime, deltaTime);
+ }
+ }
+
+ void updateSelect(long totalTime, long deltaTime) {
+ float fraction;
+ if (totalTime >= mSelectAnimatorDurationInUse) {
+ fraction = 1;
+ mSelectAnimator.end();
+ } else {
+ fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse);
+ }
+ if (mSelectAnimatorInterpolatorInUse != null) {
+ fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
+ }
+ float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+ mRowPresenter.setSelectLevel(mRowViewHolder, level);
+ }
+
+ void animateSelect(boolean select, boolean immediate) {
+ endSelectAnimation();
+ final float end = select ? 1 : 0;
+ if (immediate) {
+ mRowPresenter.setSelectLevel(mRowViewHolder, end);
+ } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) {
+ mSelectAnimatorDurationInUse = mSelectAnimatorDuration;
+ mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator;
+ mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder);
+ mSelectLevelAnimDelta = end - mSelectLevelAnimStart;
+ mSelectAnimator.start();
+ }
+ }
+
+ void endAnimations() {
+ endSelectAnimation();
+ }
+
+ void endSelectAnimation() {
+ mSelectAnimator.end();
+ }
+
+ }
+
+ private static final String TAG = "RowsSupportFragment";
+ private static final boolean DEBUG = false;
+
+ private ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
+ private boolean mExpand = true;
+ private boolean mViewsCreated;
+ private float mRowScaleFactor;
+ private int mAlignedTop;
+ private boolean mRowScaleEnabled;
+ private ScaleFrameLayout mScaleFrameLayout;
+ private boolean mInTransition;
+ private boolean mAfterEntranceTransition = true;
+
+ private OnItemSelectedListener mOnItemSelectedListener;
+ private OnItemViewSelectedListener mOnItemViewSelectedListener;
+ private OnItemClickedListener mOnItemClickedListener;
+ private OnItemViewClickedListener mOnItemViewClickedListener;
+
+ // Select animation and interpolator are not intended to be
+ // exposed at this moment. They might be synced with vertical scroll
+ // animation later.
+ int mSelectAnimatorDuration;
+ Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
+
+ private RecyclerView.RecycledViewPool mRecycledViewPool;
+ private ArrayList<Presenter> mPresenterMapper;
+
+ private ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
+
+ @Override
+ protected VerticalGridView findGridViewFromRoot(View view) {
+ return (VerticalGridView) view.findViewById(R.id.container_list);
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ * OnItemClickedListener will override {@link View.OnClickListener} that
+ * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+ * So in general, developer should choose one of the listeners but not both.
+ * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+ */
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ mOnItemClickedListener = listener;
+ if (mViewsCreated) {
+ throw new IllegalStateException(
+ "Item clicked listener must be set before views are created");
+ }
+ }
+
+ /**
+ * Returns the item clicked listener.
+ * @deprecated Use {@link #getOnItemClickedListener()}
+ */
+ public OnItemClickedListener getOnItemClickedListener() {
+ return mOnItemClickedListener;
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ * OnItemViewClickedListener will override {@link View.OnClickListener} that
+ * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+ * So in general, developer should choose one of the listeners but not both.
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ mOnItemViewClickedListener = listener;
+ if (mViewsCreated) {
+ throw new IllegalStateException(
+ "Item clicked listener must be set before views are created");
+ }
+ }
+
+ /**
+ * Returns the item clicked listener.
+ */
+ public OnItemViewClickedListener getOnItemViewClickedListener() {
+ return mOnItemViewClickedListener;
+ }
+
+ /**
+ * Set the visibility of titles/hovercard of browse rows.
+ */
+ public void setExpand(boolean expand) {
+ mExpand = expand;
+ VerticalGridView listView = getVerticalGridView();
+ if (listView != null) {
+ updateRowScaling();
+ final int count = listView.getChildCount();
+ if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
+ for (int i = 0; i < count; i++) {
+ View view = listView.getChildAt(i);
+ ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+ setRowViewExpanded(vh, mExpand);
+ }
+ }
+ }
+
+ /**
+ * Sets an item selection listener.
+ * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+ */
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ VerticalGridView listView = getVerticalGridView();
+ if (listView != null) {
+ final int count = listView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View view = listView.getChildAt(i);
+ ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+ listView.getChildViewHolder(view);
+ setOnItemSelectedListener(vh, mOnItemSelectedListener);
+ }
+ }
+ }
+
+ /**
+ * Sets an item selection listener.
+ */
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ mOnItemViewSelectedListener = listener;
+ VerticalGridView listView = getVerticalGridView();
+ if (listView != null) {
+ final int count = listView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View view = listView.getChildAt(i);
+ ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+ listView.getChildViewHolder(view);
+ setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+ }
+ }
+ }
+
+ /**
+ * Returns an item selection listener.
+ */
+ public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+ return mOnItemViewSelectedListener;
+ }
+
+ /**
+ * Enables scaling of rows.
+ *
+ * @param enable true to enable row scaling
+ */
+ public void enableRowScaling(boolean enable) {
+ mRowScaleEnabled = enable;
+ }
+
+ @Override
+ void onRowSelected(ViewGroup parent, View view, int position, long id) {
+ VerticalGridView listView = getVerticalGridView();
+ if (listView == null) {
+ return;
+ }
+ ItemBridgeAdapter.ViewHolder vh = (view == null) ? null :
+ (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+
+ if (mSelectedViewHolder != vh) {
+ if (DEBUG) Log.v(TAG, "new row selected position " + position + " view " + view);
+
+ if (mSelectedViewHolder != null) {
+ setRowViewSelected(mSelectedViewHolder, false, false);
+ }
+ mSelectedViewHolder = vh;
+ if (mSelectedViewHolder != null) {
+ setRowViewSelected(mSelectedViewHolder, true, false);
+ }
+ }
+ }
+
+ @Override
+ int getLayoutResourceId() {
+ return R.layout.lb_rows_fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSelectAnimatorDuration = getResources().getInteger(
+ R.integer.lb_browse_rows_anim_duration);
+ mRowScaleFactor = getResources().getFraction(
+ R.fraction.lb_browse_rows_scale, 1, 1);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+ mScaleFrameLayout = (ScaleFrameLayout) view.findViewById(R.id.scale_frame);
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ if (DEBUG) Log.v(TAG, "onViewCreated");
+ super.onViewCreated(view, savedInstanceState);
+ // Align the top edge of child with id row_content.
+ // Need set this for directly using RowsSupportFragment.
+ getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
+ getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
+
+ mRecycledViewPool = null;
+ mPresenterMapper = null;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mViewsCreated = false;
+ super.onDestroyView();
+ }
+
+ @Override
+ void setItemAlignment() {
+ super.setItemAlignment();
+ if (getVerticalGridView() != null) {
+ getVerticalGridView().setItemAlignmentOffsetWithPadding(true);
+ }
+ }
+
+ void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
+ mExternalAdapterListener = listener;
+ }
+
+ /**
+ * Get the view that will change scale.
+ */
+ View getScaleView() {
+ return getVerticalGridView();
+ }
+
+ /**
+ * Set pivots to scale rows fragment.
+ */
+ void setScalePivots(float pivotX, float pivotY) {
+ // set pivot on ScaleFrameLayout, it will be propagated to its child VerticalGridView
+ // where we actually change scale.
+ mScaleFrameLayout.setPivotX(pivotX);
+ mScaleFrameLayout.setPivotY(pivotY);
+ }
+
+ private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
+ ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
+ }
+
+ private static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected,
+ boolean immediate) {
+ RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+ extra.animateSelect(selected, immediate);
+ ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
+ }
+
+ private static void setOnItemSelectedListener(ItemBridgeAdapter.ViewHolder vh,
+ OnItemSelectedListener listener) {
+ ((RowPresenter) vh.getPresenter()).setOnItemSelectedListener(listener);
+ }
+
+ private static void setOnItemViewSelectedListener(ItemBridgeAdapter.ViewHolder vh,
+ OnItemViewSelectedListener listener) {
+ ((RowPresenter) vh.getPresenter()).setOnItemViewSelectedListener(listener);
+ }
+
+ private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
+ new ItemBridgeAdapter.AdapterListener() {
+ @Override
+ public void onAddPresenter(Presenter presenter, int type) {
+ ((RowPresenter) presenter).setOnItemClickedListener(mOnItemClickedListener);
+ ((RowPresenter) presenter).setOnItemViewClickedListener(mOnItemViewClickedListener);
+ if (mExternalAdapterListener != null) {
+ mExternalAdapterListener.onAddPresenter(presenter, type);
+ }
+ }
+ @Override
+ public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
+ VerticalGridView listView = getVerticalGridView();
+ if (listView != null) {
+ // set clip children false for slide animation
+ listView.setClipChildren(false);
+ }
+ setupSharedViewPool(vh);
+ mViewsCreated = true;
+ vh.setExtraObject(new RowViewHolderExtra(vh));
+ // selected state is initialized to false, then driven by grid view onChildSelected
+ // events. When there is rebind, grid view fires onChildSelected event properly.
+ // So we don't need do anything special later in onBind or onAttachedToWindow.
+ setRowViewSelected(vh, false, true);
+ if (mExternalAdapterListener != null) {
+ mExternalAdapterListener.onCreate(vh);
+ }
+ }
+ @Override
+ public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+ if (DEBUG) Log.v(TAG, "onAttachToWindow");
+ // All views share the same mExpand value. When we attach a view to grid view,
+ // we should make sure it pick up the latest mExpand value we set early on other
+ // attached views. For no-structure-change update, the view is rebound to new data,
+ // but again it should use the unchanged mExpand value, so we don't need do any
+ // thing in onBind.
+ setRowViewExpanded(vh, mExpand);
+ setOnItemSelectedListener(vh, mOnItemSelectedListener);
+ setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+ RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
+ RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+ rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
+ if (mExternalAdapterListener != null) {
+ mExternalAdapterListener.onAttachedToWindow(vh);
+ }
+ }
+ @Override
+ public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+ if (mSelectedViewHolder == vh) {
+ setRowViewSelected(mSelectedViewHolder, false, true);
+ mSelectedViewHolder = null;
+ }
+ if (mExternalAdapterListener != null) {
+ mExternalAdapterListener.onDetachedFromWindow(vh);
+ }
+ }
+ @Override
+ public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+ if (mExternalAdapterListener != null) {
+ mExternalAdapterListener.onBind(vh);
+ }
+ }
+ @Override
+ public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
+ RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+ extra.endAnimations();
+ if (mExternalAdapterListener != null) {
+ mExternalAdapterListener.onUnbind(vh);
+ }
+ }
+ };
+
+ private void setupSharedViewPool(ItemBridgeAdapter.ViewHolder bridgeVh) {
+ RowPresenter rowPresenter = (RowPresenter) bridgeVh.getPresenter();
+ RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(bridgeVh.getViewHolder());
+
+ if (rowVh instanceof ListRowPresenter.ViewHolder) {
+ HorizontalGridView view = ((ListRowPresenter.ViewHolder) rowVh).getGridView();
+ // Recycled view pool is shared between all list rows
+ if (mRecycledViewPool == null) {
+ mRecycledViewPool = view.getRecycledViewPool();
+ } else {
+ view.setRecycledViewPool(mRecycledViewPool);
+ }
+
+ ItemBridgeAdapter bridgeAdapter =
+ ((ListRowPresenter.ViewHolder) rowVh).getBridgeAdapter();
+ if (mPresenterMapper == null) {
+ mPresenterMapper = bridgeAdapter.getPresenterMapper();
+ } else {
+ bridgeAdapter.setPresenterMapper(mPresenterMapper);
+ }
+ }
+ }
+
+ @Override
+ void updateAdapter() {
+ super.updateAdapter();
+ mSelectedViewHolder = null;
+ mViewsCreated = false;
+
+ ItemBridgeAdapter adapter = getBridgeAdapter();
+ if (adapter != null) {
+ adapter.setAdapterListener(mBridgeAdapterListener);
+ }
+ }
+
+ @Override
+ void onTransitionStart() {
+ super.onTransitionStart();
+ mInTransition = true;
+ freezeRows(true);
+ }
+
+ class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
+
+ final View mVerticalView;
+ final Runnable mCallback;
+ int mState;
+
+ final static int STATE_INIT = 0;
+ final static int STATE_FIRST_DRAW = 1;
+ final static int STATE_SECOND_DRAW = 2;
+
+ ExpandPreLayout(Runnable callback) {
+ mVerticalView = getVerticalGridView();
+ mCallback = callback;
+ }
+
+ void execute() {
+ mVerticalView.getViewTreeObserver().addOnPreDrawListener(this);
+ setExpand(false);
+ mState = STATE_INIT;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ if (mState == STATE_INIT) {
+ setExpand(true);
+ mState = STATE_FIRST_DRAW;
+ } else if (mState == STATE_FIRST_DRAW) {
+ mCallback.run();
+ mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mState = STATE_SECOND_DRAW;
+ }
+ return false;
+ }
+ }
+
+ void onExpandTransitionStart(boolean expand, final Runnable callback) {
+ onTransitionStart();
+ if (expand) {
+ callback.run();
+ return;
+ }
+ // Run a "pre" layout when we go non-expand, in order to get the initial
+ // positions of added rows.
+ new ExpandPreLayout(callback).execute();
+ }
+
+ private boolean needsScale() {
+ return mRowScaleEnabled && !mExpand;
+ }
+
+ private void updateRowScaling() {
+ final float scaleFactor = needsScale() ? mRowScaleFactor : 1f;
+ mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+ getScaleView().setScaleY(scaleFactor);
+ getScaleView().setScaleX(scaleFactor);
+ updateWindowAlignOffset();
+ }
+
+ private void updateWindowAlignOffset() {
+ int alignOffset = mAlignedTop;
+ if (needsScale()) {
+ alignOffset = (int) (alignOffset / mRowScaleFactor + 0.5f);
+ }
+ getVerticalGridView().setWindowAlignmentOffset(alignOffset);
+ }
+
+ @Override
+ void setWindowAlignmentFromTop(int alignedTop) {
+ mAlignedTop = alignedTop;
+ final VerticalGridView gridView = getVerticalGridView();
+ if (gridView != null) {
+ updateWindowAlignOffset();
+ // align to a fixed position from top
+ gridView.setWindowAlignmentOffsetPercent(
+ VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+ gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ }
+ }
+
+ @Override
+ void onTransitionEnd() {
+ super.onTransitionEnd();
+ mInTransition = false;
+ freezeRows(false);
+ }
+
+ private void freezeRows(boolean freeze) {
+ VerticalGridView verticalView = getVerticalGridView();
+ if (verticalView != null) {
+ final int count = verticalView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+ verticalView.getChildViewHolder(verticalView.getChildAt(i));
+ RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+ RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+ rowPresenter.freeze(vh, freeze);
+ }
+ }
+ }
+
+ /**
+ * For rows that willing to participate entrance transition, this function
+ * hide views if afterTransition is true, show views if afterTransition is false.
+ */
+ void setEntranceTransitionState(boolean afterTransition) {
+ mAfterEntranceTransition = afterTransition;
+ VerticalGridView verticalView = getVerticalGridView();
+ if (verticalView != null) {
+ final int count = verticalView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+ verticalView.getChildViewHolder(verticalView.getChildAt(i));
+ RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+ RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+ rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
+ }
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
index f2d83b4..2299b5b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -64,12 +64,16 @@
private static final String TAG = SearchFragment.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final String EXTRA_LEANBACK_BADGE_PRESENT = "LEANBACK_BADGE_PRESENT";
private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName();
private static final String ARG_QUERY = ARG_PREFIX + ".query";
private static final String ARG_TITLE = ARG_PREFIX + ".title";
private static final long SPEECH_RECOGNITION_DELAY_MS = 300;
+ private static final int RESULTS_CHANGED = 0x1;
+ private static final int QUERY_COMPLETE = 0x2;
+
/**
* Search API to be provided by the application.
*/
@@ -124,26 +128,35 @@
private final Runnable mResultsChangedCallback = new Runnable() {
@Override
public void run() {
- if (DEBUG) Log.v(TAG, "adapter size " + mResultAdapter.size());
+ if (DEBUG) Log.v(TAG, "results changed, new size " + mResultAdapter.size());
if (mRowsFragment != null
&& mRowsFragment.getAdapter() != mResultAdapter) {
if (!(mRowsFragment.getAdapter() == null && mResultAdapter.size() == 0)) {
mRowsFragment.setAdapter(mResultAdapter);
+ mRowsFragment.setSelectedPosition(0);
}
}
mStatus |= RESULTS_CHANGED;
if ((mStatus & QUERY_COMPLETE) != 0) {
- focusOnResults();
+ updateFocus();
}
updateSearchBarNextFocusId();
}
};
+ /**
+ * Runs when a new provider is set AND when the fragment view is created.
+ */
private final Runnable mSetSearchResultProvider = new Runnable() {
@Override
public void run() {
+ if (mRowsFragment == null) {
+ // We'll retry once we have a rows fragment
+ return;
+ }
// Retrieve the result adapter
ObjectAdapter adapter = mProvider.getResultsAdapter();
+ if (DEBUG) Log.v(TAG, "Got results adapter " + adapter);
if (adapter != mResultAdapter) {
boolean firstTime = mResultAdapter == null;
releaseAdapter();
@@ -151,16 +164,34 @@
if (mResultAdapter != null) {
mResultAdapter.registerObserver(mAdapterObserver);
}
- if (null != mRowsFragment) {
- // delay the first time to avoid setting a empty result adapter
- // until we got first onChange() from the provider
- if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
- mRowsFragment.setAdapter(mResultAdapter);
- }
- executePendingQuery();
+ if (DEBUG) Log.v(TAG, "mResultAdapter " + mResultAdapter + " size " +
+ (mResultAdapter == null ? 0 : mResultAdapter.size()));
+ // delay the first time to avoid setting a empty result adapter
+ // until we got first onChange() from the provider
+ if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
+ mRowsFragment.setAdapter(mResultAdapter);
}
- updateSearchBarNextFocusId();
+ executePendingQuery();
}
+ updateSearchBarNextFocusId();
+
+ if (DEBUG) Log.v(TAG, "mAutoStartRecognition " + mAutoStartRecognition +
+ " mResultAdapter " + mResultAdapter +
+ " adapter " + mRowsFragment.getAdapter());
+ if (mAutoStartRecognition) {
+ mHandler.removeCallbacks(mStartRecognitionRunnable);
+ mHandler.postDelayed(mStartRecognitionRunnable, SPEECH_RECOGNITION_DELAY_MS);
+ } else {
+ updateFocus();
+ }
+ }
+ };
+
+ private final Runnable mStartRecognitionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mAutoStartRecognition = false;
+ mSearchBar.startRecognition();
}
};
@@ -178,13 +209,12 @@
private String mTitle;
private Drawable mBadgeDrawable;
+ private ExternalQuery mExternalQuery;
private SpeechRecognizer mSpeechRecognizer;
- private final int RESULTS_CHANGED = 0x1;
- private final int QUERY_COMPLETE = 0x2;
-
private int mStatus;
+ private boolean mAutoStartRecognition = true;
/**
* @param args Bundle to use for the arguments, if null a new Bundle will be created.
@@ -220,6 +250,9 @@
@Override
public void onCreate(Bundle savedInstanceState) {
+ if (mAutoStartRecognition) {
+ mAutoStartRecognition = savedInstanceState == null;
+ }
super.onCreate(savedInstanceState);
}
@@ -245,10 +278,7 @@
@Override
public void onSearchQuerySubmit(String query) {
if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
- queryComplete();
- if (null != mProvider) {
- mProvider.onQueryTextSubmit(query);
- }
+ submitQuery(query);
}
@Override
@@ -258,6 +288,7 @@
}
});
mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+ applyExternalQuery();
readArguments(getArguments());
if (null != mBadgeDrawable) {
@@ -311,18 +342,16 @@
if (null != mProvider) {
onSetSearchResultProvider();
}
- if (savedInstanceState == null) {
- // auto start recognition if this is the first time create fragment
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mSearchBar.startRecognition();
- }
- }, SPEECH_RECOGNITION_DELAY_MS);
- }
return root;
}
+ private void resultsAvailable() {
+ if ((mStatus & QUERY_COMPLETE) != 0) {
+ focusOnResults();
+ }
+ updateSearchBarNextFocusId();
+ }
+
@Override
public void onStart() {
super.onStart();
@@ -344,6 +373,8 @@
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
}
+ // Ensure search bar state consistency when using external recognizer
+ mSearchBar.stopRecognition();
}
@Override
@@ -508,10 +539,15 @@
* @param submit Whether to submit the query.
*/
public void setSearchQuery(String query, boolean submit) {
- // setSearchQuery will call onQueryTextChange
- mSearchBar.setSearchQuery(query);
- if (submit) {
- mProvider.onQueryTextSubmit(query);
+ if (DEBUG) Log.v(TAG, "setSearchQuery " + query + " submit " + submit);
+ if (query == null) {
+ return;
+ }
+ mExternalQuery = new ExternalQuery(query, submit);
+ applyExternalQuery();
+ if (mAutoStartRecognition) {
+ mAutoStartRecognition = false;
+ mHandler.removeCallbacks(mStartRecognitionRunnable);
}
}
@@ -553,16 +589,26 @@
if (mSearchBar != null && mSearchBar.getHint() != null) {
recognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, mSearchBar.getHint());
}
+ recognizerIntent.putExtra(EXTRA_LEANBACK_BADGE_PRESENT, mBadgeDrawable != null);
return recognizerIntent;
}
private void retrieveResults(String searchQuery) {
- if (DEBUG) Log.v(TAG, String.format("retrieveResults %s", searchQuery));
- mProvider.onQueryTextChange(searchQuery);
- mStatus &= ~QUERY_COMPLETE;
+ if (DEBUG) Log.v(TAG, "retrieveResults " + searchQuery);
+ if (mProvider.onQueryTextChange(searchQuery)) {
+ mStatus &= ~QUERY_COMPLETE;
+ }
+ }
+
+ private void submitQuery(String query) {
+ queryComplete();
+ if (null != mProvider) {
+ mProvider.onQueryTextSubmit(query);
+ }
}
private void queryComplete() {
+ if (DEBUG) Log.v(TAG, "queryComplete");
mStatus |= QUERY_COMPLETE;
focusOnResults();
}
@@ -577,13 +623,21 @@
mSearchBar.setNextFocusDownId(viewId);
}
+ private void updateFocus() {
+ if (mResultAdapter != null && mResultAdapter.size() > 0 &&
+ mRowsFragment != null && mRowsFragment.getAdapter() == mResultAdapter) {
+ focusOnResults();
+ } else {
+ mSearchBar.requestFocus();
+ }
+ }
+
private void focusOnResults() {
if (mRowsFragment == null ||
mRowsFragment.getVerticalGridView() == null ||
mResultAdapter.size() == 0) {
return;
}
- mRowsFragment.setSelectedPosition(0);
if (mRowsFragment.getVerticalGridView().requestFocus()) {
mStatus &= ~RESULTS_CHANGED;
}
@@ -609,6 +663,17 @@
}
}
+ private void applyExternalQuery() {
+ if (mExternalQuery == null || mSearchBar == null) {
+ return;
+ }
+ mSearchBar.setSearchQuery(mExternalQuery.mQuery);
+ if (mExternalQuery.mSubmit) {
+ submitQuery(mExternalQuery.mQuery);
+ }
+ mExternalQuery = null;
+ }
+
private void readArguments(Bundle args) {
if (null == args) {
return;
@@ -625,4 +690,14 @@
private void setSearchQuery(String query) {
mSearchBar.setSearchQuery(query);
}
+
+ static class ExternalQuery {
+ String mQuery;
+ boolean mSubmit;
+
+ ExternalQuery(String query, boolean submit) {
+ mQuery = query;
+ mSubmit = submit;
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
new file mode 100644
index 0000000..b3c280f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -0,0 +1,705 @@
+/* This file is auto-generated from SearchFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.speech.SpeechRecognizer;
+import android.speech.RecognizerIntent;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SearchBar;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Presenter.ViewHolder;
+import android.support.v17.leanback.widget.SpeechRecognitionCallback;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.support.v17.leanback.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fragment to handle searches. An application will supply an implementation
+ * of the {@link SearchResultProvider} interface to handle the search and return
+ * an {@link ObjectAdapter} containing the results. The results are rendered
+ * into a {@link RowsSupportFragment}, in the same way that they are in a {@link
+ * BrowseSupportFragment}.
+ *
+ * <p>If you do not supply a callback via
+ * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
+ * recognizer will be used for which your application will need to request
+ * android.permission.RECORD_AUDIO.
+ * </p>
+ * <p>
+ * Speech recognition is automatically started when fragment is created, but
+ * not when fragment is restored from an instance state. Activity may manually
+ * call {@link #startRecognition()}, typically in onNewIntent().
+ * </p>
+ */
+public class SearchSupportFragment extends Fragment {
+ private static final String TAG = SearchSupportFragment.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final String EXTRA_LEANBACK_BADGE_PRESENT = "LEANBACK_BADGE_PRESENT";
+ private static final String ARG_PREFIX = SearchSupportFragment.class.getCanonicalName();
+ private static final String ARG_QUERY = ARG_PREFIX + ".query";
+ private static final String ARG_TITLE = ARG_PREFIX + ".title";
+
+ private static final long SPEECH_RECOGNITION_DELAY_MS = 300;
+
+ private static final int RESULTS_CHANGED = 0x1;
+ private static final int QUERY_COMPLETE = 0x2;
+
+ /**
+ * Search API to be provided by the application.
+ */
+ public static interface SearchResultProvider {
+ /**
+ * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve
+ * an ObjectAdapter that will contain the results to future updates of the search query.</p>
+ *
+ * <p>As results are retrieved, the application should use the data set notification methods
+ * on the ObjectAdapter to instruct the SearchSupportFragment to update the results.</p>
+ *
+ * @return ObjectAdapter The result object adapter.
+ */
+ public ObjectAdapter getResultsAdapter();
+
+ /**
+ * <p>Method invoked when the search query is updated.</p>
+ *
+ * <p>This is called as soon as the query changes; it is up to the application to add a
+ * delay before actually executing the queries if needed.
+ *
+ * <p>This method might not always be called before onQueryTextSubmit gets called, in
+ * particular for voice input.
+ *
+ * @param newQuery The current search query.
+ * @return whether the results changed as a result of the new query.
+ */
+ public boolean onQueryTextChange(String newQuery);
+
+ /**
+ * Method invoked when the search query is submitted, either by dismissing the keyboard,
+ * pressing search or next on the keyboard or when voice has detected the end of the query.
+ *
+ * @param query The query entered.
+ * @return whether the results changed as a result of the query.
+ */
+ public boolean onQueryTextSubmit(String query);
+ }
+
+ private final DataObserver mAdapterObserver = new DataObserver() {
+ @Override
+ public void onChanged() {
+ // onChanged() may be called multiple times e.g. the provider add
+ // rows to ArrayObjectAdapter one by one.
+ mHandler.removeCallbacks(mResultsChangedCallback);
+ mHandler.post(mResultsChangedCallback);
+ }
+ };
+
+ private final Handler mHandler = new Handler();
+
+ private final Runnable mResultsChangedCallback = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.v(TAG, "results changed, new size " + mResultAdapter.size());
+ if (mRowsSupportFragment != null
+ && mRowsSupportFragment.getAdapter() != mResultAdapter) {
+ if (!(mRowsSupportFragment.getAdapter() == null && mResultAdapter.size() == 0)) {
+ mRowsSupportFragment.setAdapter(mResultAdapter);
+ mRowsSupportFragment.setSelectedPosition(0);
+ }
+ }
+ mStatus |= RESULTS_CHANGED;
+ if ((mStatus & QUERY_COMPLETE) != 0) {
+ updateFocus();
+ }
+ updateSearchBarNextFocusId();
+ }
+ };
+
+ /**
+ * Runs when a new provider is set AND when the fragment view is created.
+ */
+ private final Runnable mSetSearchResultProvider = new Runnable() {
+ @Override
+ public void run() {
+ if (mRowsSupportFragment == null) {
+ // We'll retry once we have a rows fragment
+ return;
+ }
+ // Retrieve the result adapter
+ ObjectAdapter adapter = mProvider.getResultsAdapter();
+ if (DEBUG) Log.v(TAG, "Got results adapter " + adapter);
+ if (adapter != mResultAdapter) {
+ boolean firstTime = mResultAdapter == null;
+ releaseAdapter();
+ mResultAdapter = adapter;
+ if (mResultAdapter != null) {
+ mResultAdapter.registerObserver(mAdapterObserver);
+ }
+ if (DEBUG) Log.v(TAG, "mResultAdapter " + mResultAdapter + " size " +
+ (mResultAdapter == null ? 0 : mResultAdapter.size()));
+ // delay the first time to avoid setting a empty result adapter
+ // until we got first onChange() from the provider
+ if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
+ mRowsSupportFragment.setAdapter(mResultAdapter);
+ }
+ executePendingQuery();
+ }
+ updateSearchBarNextFocusId();
+
+ if (DEBUG) Log.v(TAG, "mAutoStartRecognition " + mAutoStartRecognition +
+ " mResultAdapter " + mResultAdapter +
+ " adapter " + mRowsSupportFragment.getAdapter());
+ if (mAutoStartRecognition) {
+ mHandler.removeCallbacks(mStartRecognitionRunnable);
+ mHandler.postDelayed(mStartRecognitionRunnable, SPEECH_RECOGNITION_DELAY_MS);
+ } else {
+ updateFocus();
+ }
+ }
+ };
+
+ private final Runnable mStartRecognitionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mAutoStartRecognition = false;
+ mSearchBar.startRecognition();
+ }
+ };
+
+ private RowsSupportFragment mRowsSupportFragment;
+ private SearchBar mSearchBar;
+ private SearchResultProvider mProvider;
+ private String mPendingQuery = null;
+
+ private OnItemSelectedListener mOnItemSelectedListener;
+ private OnItemClickedListener mOnItemClickedListener;
+ private OnItemViewSelectedListener mOnItemViewSelectedListener;
+ private OnItemViewClickedListener mOnItemViewClickedListener;
+ private ObjectAdapter mResultAdapter;
+ private SpeechRecognitionCallback mSpeechRecognitionCallback;
+
+ private String mTitle;
+ private Drawable mBadgeDrawable;
+ private ExternalQuery mExternalQuery;
+
+ private SpeechRecognizer mSpeechRecognizer;
+
+ private int mStatus;
+ private boolean mAutoStartRecognition = true;
+
+ /**
+ * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+ */
+ public static Bundle createArgs(Bundle args, String query) {
+ return createArgs(args, query, null);
+ }
+
+ public static Bundle createArgs(Bundle args, String query, String title) {
+ if (args == null) {
+ args = new Bundle();
+ }
+ args.putString(ARG_QUERY, query);
+ args.putString(ARG_TITLE, title);
+ return args;
+ }
+
+ /**
+ * Create a search fragment with a given search query.
+ *
+ * <p>You should only use this if you need to start the search fragment with a
+ * pre-filled query.
+ *
+ * @param query The search query to begin with.
+ * @return A new SearchSupportFragment.
+ */
+ public static SearchSupportFragment newInstance(String query) {
+ SearchSupportFragment fragment = new SearchSupportFragment();
+ Bundle args = createArgs(null, query);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if (mAutoStartRecognition) {
+ mAutoStartRecognition = savedInstanceState == null;
+ }
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
+
+ FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame);
+ mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar);
+ mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
+ @Override
+ public void onSearchQueryChange(String query) {
+ if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s %s", query,
+ null == mProvider ? "(null)" : mProvider));
+ if (null != mProvider) {
+ retrieveResults(query);
+ } else {
+ mPendingQuery = query;
+ }
+ }
+
+ @Override
+ public void onSearchQuerySubmit(String query) {
+ if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
+ submitQuery(query);
+ }
+
+ @Override
+ public void onKeyboardDismiss(String query) {
+ if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query));
+ queryComplete();
+ }
+ });
+ mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+ applyExternalQuery();
+
+ readArguments(getArguments());
+ if (null != mBadgeDrawable) {
+ setBadgeDrawable(mBadgeDrawable);
+ }
+ if (null != mTitle) {
+ setTitle(mTitle);
+ }
+
+ // Inject the RowsSupportFragment in the results container
+ if (getChildFragmentManager().findFragmentById(R.id.lb_results_frame) == null) {
+ mRowsSupportFragment = new RowsSupportFragment();
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.lb_results_frame, mRowsSupportFragment).commit();
+ } else {
+ mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager()
+ .findFragmentById(R.id.lb_results_frame);
+ }
+ mRowsSupportFragment.setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
+ if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position));
+ mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE);
+ if (null != mOnItemSelectedListener) {
+ mOnItemSelectedListener.onItemSelected(item, row);
+ }
+ if (null != mOnItemViewSelectedListener) {
+ mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+ rowViewHolder, row);
+ }
+ }
+ });
+ mRowsSupportFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
+ if (DEBUG) Log.v(TAG, String.format("onItemClicked %d", position));
+ if (null != mOnItemClickedListener) {
+ mOnItemClickedListener.onItemClicked(item, row);
+ }
+ if (null != mOnItemViewClickedListener) {
+ mOnItemViewClickedListener.onItemClicked(itemViewHolder, item,
+ rowViewHolder, row);
+ }
+ }
+ });
+ mRowsSupportFragment.setExpand(true);
+ if (null != mProvider) {
+ onSetSearchResultProvider();
+ }
+ return root;
+ }
+
+ private void resultsAvailable() {
+ if ((mStatus & QUERY_COMPLETE) != 0) {
+ focusOnResults();
+ }
+ updateSearchBarNextFocusId();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ VerticalGridView list = mRowsSupportFragment.getVerticalGridView();
+ int mContainerListAlignTop =
+ getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top);
+ list.setItemAlignmentOffset(0);
+ list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+ list.setWindowAlignmentOffset(mContainerListAlignTop);
+ list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+ list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
+ mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
+ mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
+ }
+ // Ensure search bar state consistency when using external recognizer
+ mSearchBar.stopRecognition();
+ }
+
+ @Override
+ public void onPause() {
+ releaseRecognizer();
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy() {
+ releaseAdapter();
+ super.onDestroy();
+ }
+
+ private void releaseRecognizer() {
+ if (null != mSpeechRecognizer) {
+ mSearchBar.setSpeechRecognizer(null);
+ mSpeechRecognizer.destroy();
+ mSpeechRecognizer = null;
+ }
+ }
+
+ /**
+ * Starts speech recognition. Typical use case is that
+ * activity receives onNewIntent() call when user clicks a MIC button.
+ * Note that SearchSupportFragment automatically starts speech recognition
+ * at first time created, there is no need to call startRecognition()
+ * when fragment is created.
+ */
+ public void startRecognition() {
+ mSearchBar.startRecognition();
+ }
+
+ /**
+ * Set the search provider that is responsible for returning results for the
+ * search query.
+ */
+ public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
+ if (mProvider != searchResultProvider) {
+ mProvider = searchResultProvider;
+ onSetSearchResultProvider();
+ }
+ }
+
+ /**
+ * Sets an item selection listener for the results.
+ *
+ * @param listener The item selection listener to be invoked when an item in
+ * the search results is selected.
+ * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+ */
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ }
+
+ /**
+ * Sets an item clicked listener for the results.
+ *
+ * @param listener The item clicked listener to be invoked when an item in
+ * the search results is clicked.
+ * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+ */
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ mOnItemClickedListener = listener;
+ }
+
+ /**
+ * Sets an item selection listener for the results.
+ *
+ * @param listener The item selection listener to be invoked when an item in
+ * the search results is selected.
+ */
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ mOnItemViewSelectedListener = listener;
+ }
+
+ /**
+ * Sets an item clicked listener for the results.
+ *
+ * @param listener The item clicked listener to be invoked when an item in
+ * the search results is clicked.
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ mOnItemViewClickedListener = listener;
+ }
+
+ /**
+ * Sets the title string to be be shown in an empty search bar. The title
+ * may be placed in a call-to-action, such as "Search <i>title</i>" or
+ * "Speak to search <i>title</i>".
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ if (null != mSearchBar) {
+ mSearchBar.setTitle(title);
+ }
+ }
+
+ /**
+ * Returns the title set in the search bar.
+ */
+ public String getTitle() {
+ if (null != mSearchBar) {
+ return mSearchBar.getTitle();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the badge drawable that will be shown inside the search bar next to
+ * the title.
+ */
+ public void setBadgeDrawable(Drawable drawable) {
+ mBadgeDrawable = drawable;
+ if (null != mSearchBar) {
+ mSearchBar.setBadgeDrawable(drawable);
+ }
+ }
+
+ /**
+ * Returns the badge drawable in the search bar.
+ */
+ public Drawable getBadgeDrawable() {
+ if (null != mSearchBar) {
+ return mSearchBar.getBadgeDrawable();
+ }
+ return null;
+ }
+
+ /**
+ * Display the completions shown by the IME. An application may provide
+ * a list of query completions that the system will show in the IME.
+ *
+ * @param completions A list of completions to show in the IME. Setting to
+ * null or empty will clear the list.
+ */
+ public void displayCompletions(List<String> completions) {
+ mSearchBar.displayCompletions(completions);
+ }
+
+ /**
+ * Set this callback to have the fragment pass speech recognition requests
+ * to the activity rather than using an internal recognizer.
+ */
+ public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
+ mSpeechRecognitionCallback = callback;
+ if (mSearchBar != null) {
+ mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+ }
+ if (callback != null) {
+ releaseRecognizer();
+ }
+ }
+
+ /**
+ * Sets the text of the search query and optionally submits the query. Either
+ * {@link SearchResultProvider#onQueryTextChange onQueryTextChange} or
+ * {@link SearchResultProvider#onQueryTextSubmit onQueryTextSubmit} will be
+ * called on the provider if it is set.
+ *
+ * @param query The search query to set.
+ * @param submit Whether to submit the query.
+ */
+ public void setSearchQuery(String query, boolean submit) {
+ if (DEBUG) Log.v(TAG, "setSearchQuery " + query + " submit " + submit);
+ if (query == null) {
+ return;
+ }
+ mExternalQuery = new ExternalQuery(query, submit);
+ applyExternalQuery();
+ if (mAutoStartRecognition) {
+ mAutoStartRecognition = false;
+ mHandler.removeCallbacks(mStartRecognitionRunnable);
+ }
+ }
+
+ /**
+ * Sets the text of the search query based on the {@link RecognizerIntent#EXTRA_RESULTS} in
+ * the given intent, and optionally submit the query. If more than one result is present
+ * in the results list, the first will be used.
+ *
+ * @param intent Intent received from a speech recognition service.
+ * @param submit Whether to submit the query.
+ */
+ public void setSearchQuery(Intent intent, boolean submit) {
+ ArrayList<String> matches = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
+ if (matches != null && matches.size() > 0) {
+ setSearchQuery(matches.get(0), submit);
+ }
+ }
+
+ /**
+ * Returns an intent that can be used to request speech recognition.
+ * Built from the base {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH} plus
+ * extras:
+ *
+ * <ul>
+ * <li>{@link RecognizerIntent#EXTRA_LANGUAGE_MODEL} set to
+ * {@link RecognizerIntent#LANGUAGE_MODEL_FREE_FORM}</li>
+ * <li>{@link RecognizerIntent#EXTRA_PARTIAL_RESULTS} set to true</li>
+ * <li>{@link RecognizerIntent#EXTRA_PROMPT} set to the search bar hint text</li>
+ * </ul>
+ *
+ * For handling the intent returned from the service, see
+ * {@link #setSearchQuery(Intent, boolean)}.
+ */
+ public Intent getRecognizerIntent() {
+ Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+ RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+ recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
+ if (mSearchBar != null && mSearchBar.getHint() != null) {
+ recognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, mSearchBar.getHint());
+ }
+ recognizerIntent.putExtra(EXTRA_LEANBACK_BADGE_PRESENT, mBadgeDrawable != null);
+ return recognizerIntent;
+ }
+
+ private void retrieveResults(String searchQuery) {
+ if (DEBUG) Log.v(TAG, "retrieveResults " + searchQuery);
+ if (mProvider.onQueryTextChange(searchQuery)) {
+ mStatus &= ~QUERY_COMPLETE;
+ }
+ }
+
+ private void submitQuery(String query) {
+ queryComplete();
+ if (null != mProvider) {
+ mProvider.onQueryTextSubmit(query);
+ }
+ }
+
+ private void queryComplete() {
+ if (DEBUG) Log.v(TAG, "queryComplete");
+ mStatus |= QUERY_COMPLETE;
+ focusOnResults();
+ }
+
+ private void updateSearchBarNextFocusId() {
+ if (mSearchBar == null || mResultAdapter == null) {
+ return;
+ }
+ final int viewId = (mResultAdapter.size() == 0 || mRowsSupportFragment == null ||
+ mRowsSupportFragment.getVerticalGridView() == null) ? 0 :
+ mRowsSupportFragment.getVerticalGridView().getId();
+ mSearchBar.setNextFocusDownId(viewId);
+ }
+
+ private void updateFocus() {
+ if (mResultAdapter != null && mResultAdapter.size() > 0 &&
+ mRowsSupportFragment != null && mRowsSupportFragment.getAdapter() == mResultAdapter) {
+ focusOnResults();
+ } else {
+ mSearchBar.requestFocus();
+ }
+ }
+
+ private void focusOnResults() {
+ if (mRowsSupportFragment == null ||
+ mRowsSupportFragment.getVerticalGridView() == null ||
+ mResultAdapter.size() == 0) {
+ return;
+ }
+ if (mRowsSupportFragment.getVerticalGridView().requestFocus()) {
+ mStatus &= ~RESULTS_CHANGED;
+ }
+ }
+
+ private void onSetSearchResultProvider() {
+ mHandler.removeCallbacks(mSetSearchResultProvider);
+ mHandler.post(mSetSearchResultProvider);
+ }
+
+ private void releaseAdapter() {
+ if (mResultAdapter != null) {
+ mResultAdapter.unregisterObserver(mAdapterObserver);
+ mResultAdapter = null;
+ }
+ }
+
+ private void executePendingQuery() {
+ if (null != mPendingQuery && null != mResultAdapter) {
+ String query = mPendingQuery;
+ mPendingQuery = null;
+ retrieveResults(query);
+ }
+ }
+
+ private void applyExternalQuery() {
+ if (mExternalQuery == null || mSearchBar == null) {
+ return;
+ }
+ mSearchBar.setSearchQuery(mExternalQuery.mQuery);
+ if (mExternalQuery.mSubmit) {
+ submitQuery(mExternalQuery.mQuery);
+ }
+ mExternalQuery = null;
+ }
+
+ private void readArguments(Bundle args) {
+ if (null == args) {
+ return;
+ }
+ if (args.containsKey(ARG_QUERY)) {
+ setSearchQuery(args.getString(ARG_QUERY));
+ }
+
+ if (args.containsKey(ARG_TITLE)) {
+ setTitle(args.getString(ARG_TITLE));
+ }
+ }
+
+ private void setSearchQuery(String query) {
+ mSearchBar.setSearchQuery(query);
+ }
+
+ static class ExternalQuery {
+ String mQuery;
+ boolean mSubmit;
+
+ ExternalQuery(String query, boolean submit) {
+ mQuery = query;
+ mSubmit = submit;
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
deleted file mode 100644
index 69d80fe..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.SlideCallback;
-import android.view.View;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-class TitleTransitionHelper {
-
- final static SlideCallback sSlideCallback = new SlideCallback() {
- @Override
- public boolean getSlide(View view, boolean appear, int[] edge, float[] distance) {
- edge[0] = TransitionHelper.SLIDE_TOP;
- distance[0] = view.getHeight();
- return true;
- }
- };
-
- private static Interpolator createTransitionInterpolatorUp() {
- return new DecelerateInterpolator(4);
- }
-
- private static Interpolator createTransitionInterpolatorDown() {
- return new DecelerateInterpolator();
- }
-
- static public Object createTransitionTitleUp(TransitionHelper helper) {
- Object transition = helper.createSlide(sSlideCallback);
- helper.setInterpolator(transition, createTransitionInterpolatorUp());
- return transition;
- }
-
- static public Object createTransitionTitleDown(TransitionHelper helper) {
- Object transition = helper.createSlide(sSlideCallback);
- helper.setInterpolator(transition, createTransitionInterpolatorDown());
- return transition;
- }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
index 4ee594b..a5e5f0a 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -15,6 +15,7 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
@@ -26,7 +27,9 @@
import android.support.v17.leanback.widget.OnItemClickedListener;
import android.support.v17.leanback.widget.OnItemSelectedListener;
import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
import android.app.Fragment;
+import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
@@ -300,8 +303,11 @@
if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
final View searchOrbView = mTitleView.getSearchAffordanceView();
+ final boolean isRtl = ViewCompat.getLayoutDirection(focused) ==
+ View.LAYOUT_DIRECTION_RTL;
+ final int forward = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
if (focused == searchOrbView && (
- direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)) {
+ direction == View.FOCUS_DOWN || direction == forward)) {
return mGridViewHolder.view;
} else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
@@ -345,10 +351,9 @@
mTitleView.setVisibility(View.INVISIBLE);
}
});
- mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
- mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
- sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_grid_dock, true);
- sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_grid_dock, true);
+ Context context = getActivity();
+ mTitleUpTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_out);
+ mTitleDownTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_in);
return root;
}
@@ -369,6 +374,18 @@
}
@Override
+ public void onPause() {
+ mTitleView.enableAnimation(false);
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mTitleView.enableAnimation(true);
+ }
+
+ @Override
public void onDestroyView() {
super.onDestroyView();
mGridViewHolder = null;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
new file mode 100644
index 0000000..4353a5d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -0,0 +1,414 @@
+/* This file is auto-generated from VerticalGridFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.app.Fragment;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * A fragment for creating leanback vertical grids.
+ *
+ * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
+ * an {@link ObjectAdapter}.
+ */
+public class VerticalGridSupportFragment extends Fragment {
+ private static final String TAG = "VerticalGridSupportFragment";
+ private static boolean DEBUG = false;
+
+ private BrowseFrameLayout mBrowseFrame;
+ private String mTitle;
+ private Drawable mBadgeDrawable;
+ private ObjectAdapter mAdapter;
+ private VerticalGridPresenter mGridPresenter;
+ private VerticalGridPresenter.ViewHolder mGridViewHolder;
+ private OnItemSelectedListener mOnItemSelectedListener;
+ private OnItemClickedListener mOnItemClickedListener;
+ private OnItemViewSelectedListener mOnItemViewSelectedListener;
+ private OnItemViewClickedListener mOnItemViewClickedListener;
+ private View.OnClickListener mExternalOnSearchClickedListener;
+ private int mSelectedPosition = -1;
+
+ private TitleView mTitleView;
+ private SearchOrbView.Colors mSearchAffordanceColors;
+ private boolean mSearchAffordanceColorSet;
+ private boolean mShowingTitle = true;
+
+ // transition related
+ private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+ private Object mTitleUpTransition;
+ private Object mTitleDownTransition;
+ private Object mSceneWithTitle;
+ private Object mSceneWithoutTitle;
+
+ /**
+ * Sets the badge drawable displayed in the title area.
+ */
+ public void setBadgeDrawable(Drawable drawable) {
+ if (drawable != mBadgeDrawable) {
+ mBadgeDrawable = drawable;
+ if (mTitleView != null) {
+ mTitleView.setBadgeDrawable(drawable);
+ }
+ }
+ }
+
+ /**
+ * Returns the badge drawable.
+ */
+ public Drawable getBadgeDrawable() {
+ return mBadgeDrawable;
+ }
+
+ /**
+ * Sets a title for the fragment.
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ if (mTitleView != null) {
+ mTitleView.setTitle(mTitle);
+ }
+ }
+
+ /**
+ * Returns the title for the fragment.
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the grid presenter.
+ */
+ public void setGridPresenter(VerticalGridPresenter gridPresenter) {
+ if (gridPresenter == null) {
+ throw new IllegalArgumentException("Grid presenter may not be null");
+ }
+ mGridPresenter = gridPresenter;
+ mGridPresenter.setOnItemViewSelectedListener(mRowSelectedListener);
+ if (mOnItemViewClickedListener != null) {
+ mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ if (mOnItemClickedListener != null) {
+ mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+ }
+ }
+
+ /**
+ * Returns the grid presenter.
+ */
+ public VerticalGridPresenter getGridPresenter() {
+ return mGridPresenter;
+ }
+
+ /**
+ * Sets the object adapter for the fragment.
+ */
+ public void setAdapter(ObjectAdapter adapter) {
+ mAdapter = adapter;
+ updateAdapter();
+ }
+
+ /**
+ * Returns the object adapter.
+ */
+ public ObjectAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ final private OnItemViewSelectedListener mRowSelectedListener =
+ new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ int position = mGridViewHolder.getGridView().getSelectedPosition();
+ if (DEBUG) Log.v(TAG, "row selected position " + position);
+ onRowSelected(position);
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onItemSelected(item, row);
+ }
+ if (mOnItemViewSelectedListener != null) {
+ mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+ rowViewHolder, row);
+ }
+ }
+ };
+
+ /**
+ * Sets an item selection listener.
+ * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+ */
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ }
+
+ /**
+ * Sets an item selection listener.
+ */
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ mOnItemViewSelectedListener = listener;
+ }
+
+ private void onRowSelected(int position) {
+ if (position != mSelectedPosition) {
+ if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(position)) {
+ // if has no sibling in front of it, show title
+ if (!mShowingTitle) {
+ sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
+ mShowingTitle = true;
+ }
+ } else if (mShowingTitle) {
+ sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
+ mShowingTitle = false;
+ }
+ mSelectedPosition = position;
+ }
+ }
+
+ /**
+ * Sets an item clicked listener.
+ * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+ */
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ mOnItemClickedListener = listener;
+ if (mGridPresenter != null) {
+ mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+ }
+ }
+
+ /**
+ * Returns the item clicked listener.
+ * @deprecated Use {@link #getOnItemViewClickedListener()}
+ */
+ public OnItemClickedListener getOnItemClickedListener() {
+ return mOnItemClickedListener;
+ }
+
+ /**
+ * Sets an item clicked listener.
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ mOnItemViewClickedListener = listener;
+ if (mGridPresenter != null) {
+ mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ }
+
+ /**
+ * Returns the item clicked listener.
+ */
+ public OnItemViewClickedListener getOnItemViewClickedListener() {
+ return mOnItemViewClickedListener;
+ }
+
+ /**
+ * Sets a click listener for the search affordance.
+ *
+ * <p>The presence of a listener will change the visibility of the search
+ * affordance in the title area. When set to non-null, the title area will
+ * contain a call to search action.
+ *
+ * <p>The listener's onClick method will be invoked when the user clicks on
+ * the search action.
+ *
+ * @param listener The listener to invoke when the search affordance is
+ * clicked, or null to hide the search affordance.
+ */
+ public void setOnSearchClickedListener(View.OnClickListener listener) {
+ mExternalOnSearchClickedListener = listener;
+ if (mTitleView != null) {
+ mTitleView.setOnSearchClickedListener(listener);
+ }
+ }
+
+ /**
+ * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
+ */
+ public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+ mSearchAffordanceColors = colors;
+ mSearchAffordanceColorSet = true;
+ if (mTitleView != null) {
+ mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ }
+ }
+
+ /**
+ * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
+ */
+ public SearchOrbView.Colors getSearchAffordanceColors() {
+ if (mSearchAffordanceColorSet) {
+ return mSearchAffordanceColors;
+ }
+ if (mTitleView == null) {
+ throw new IllegalStateException("Fragment views not yet created");
+ }
+ return mTitleView.getSearchAffordanceColors();
+ }
+
+ /**
+ * Sets the color used to draw the search affordance.
+ * A default brighter color will be set by the framework.
+ *
+ * @param color The color to use for the search affordance.
+ */
+ public void setSearchAffordanceColor(int color) {
+ setSearchAffordanceColors(new SearchOrbView.Colors(color));
+ }
+
+ /**
+ * Returns the color used to draw the search affordance.
+ */
+ public int getSearchAffordanceColor() {
+ return getSearchAffordanceColors().color;
+ }
+
+ private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+ new BrowseFrameLayout.OnFocusSearchListener() {
+ @Override
+ public View onFocusSearch(View focused, int direction) {
+ if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+ final View searchOrbView = mTitleView.getSearchAffordanceView();
+ final boolean isRtl = ViewCompat.getLayoutDirection(focused) ==
+ View.LAYOUT_DIRECTION_RTL;
+ final int forward = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+ if (focused == searchOrbView && (
+ direction == View.FOCUS_DOWN || direction == forward)) {
+ return mGridViewHolder.view;
+
+ } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+ && direction == View.FOCUS_UP) {
+ return searchOrbView;
+
+ } else {
+ return null;
+ }
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
+ container, false);
+
+ mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+ mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+
+ mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+ mTitleView.setBadgeDrawable(mBadgeDrawable);
+ mTitleView.setTitle(mTitle);
+ if (mSearchAffordanceColorSet) {
+ mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ }
+ if (mExternalOnSearchClickedListener != null) {
+ mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+ }
+
+ mSceneWithTitle = sTransitionHelper.createScene(root, new Runnable() {
+ @Override
+ public void run() {
+ mTitleView.setVisibility(View.VISIBLE);
+ }
+ });
+ mSceneWithoutTitle = sTransitionHelper.createScene(root, new Runnable() {
+ @Override
+ public void run() {
+ mTitleView.setVisibility(View.INVISIBLE);
+ }
+ });
+ Context context = getActivity();
+ mTitleUpTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_out);
+ mTitleDownTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_in);
+
+ return root;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
+ mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
+ gridDock.addView(mGridViewHolder.view);
+
+ updateAdapter();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mGridViewHolder.getGridView().requestFocus();
+ }
+
+ @Override
+ public void onPause() {
+ mTitleView.enableAnimation(false);
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mTitleView.enableAnimation(true);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mGridViewHolder = null;
+ }
+
+ /**
+ * Sets the selected item position.
+ */
+ public void setSelectedPosition(int position) {
+ mSelectedPosition = position;
+ if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
+ mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
+ }
+ }
+
+ private void updateAdapter() {
+ if (mGridViewHolder != null) {
+ mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
+ if (mSelectedPosition != -1) {
+ mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
+ }
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
new file mode 100644
index 0000000..f7451d4
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.animation.AnimatorInflater;
+import android.content.Context;
+import android.os.Build;
+import android.support.v17.leanback.R;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * Helper class to load Leanback specific transition.
+ * @hide
+ */
+public class LeanbackTransitionHelper {
+
+ static interface LeanbackTransitionHelperVersion {
+
+ public Object loadTitleInTransition(Context context, TransitionHelper helper);
+
+ public Object loadTitleOutTransition(Context context, TransitionHelper helper);
+ }
+
+ /*
+ * Kitkat does not allow load custom transition from resource, calling
+ * LeanbackTransitionHelperKitKat to build custom transition in code.
+ */
+ static class LeanbackTransitionHelperKitKatImpl implements LeanbackTransitionHelperVersion {
+
+ @Override
+ public Object loadTitleInTransition(Context context, TransitionHelper helper) {
+ return LeanbackTransitionHelperKitKat.loadTitleInTransition(context);
+ }
+
+ @Override
+ public Object loadTitleOutTransition(Context context, TransitionHelper helper) {
+ return LeanbackTransitionHelperKitKat.loadTitleOutTransition(context);
+ }
+ }
+
+ /*
+ * Load transition from resource or just return stub for API17.
+ */
+ static class LeanbackTransitionHelperDefault implements LeanbackTransitionHelperVersion {
+
+ @Override
+ public Object loadTitleInTransition(Context context, TransitionHelper helper) {
+ return helper.loadTransition(context, R.transition.lb_title_in);
+ }
+
+ @Override
+ public Object loadTitleOutTransition(Context context, TransitionHelper helper) {
+ return helper.loadTransition(context, R.transition.lb_title_out);
+ }
+ }
+
+ static LeanbackTransitionHelperVersion sImpl;
+
+ static {
+ if (Build.VERSION.SDK_INT >= 21) {
+ sImpl = new LeanbackTransitionHelperDefault();
+ } else if (Build.VERSION.SDK_INT >= 19) {
+ sImpl = new LeanbackTransitionHelperKitKatImpl();
+ } else {
+ // Helper will create a stub object for transition in this case.
+ sImpl = new LeanbackTransitionHelperDefault();
+ }
+ }
+
+ static public Object loadTitleInTransition(Context context, TransitionHelper helper) {
+ return sImpl.loadTitleInTransition(context, helper);
+ }
+
+ static public Object loadTitleOutTransition(Context context, TransitionHelper helper) {
+ return sImpl.loadTitleOutTransition(context, helper);
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
index 2e17119..1c66d03 100644
--- a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
@@ -15,6 +15,7 @@
import android.content.Context;
import android.os.Build;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -28,10 +29,10 @@
public static final int FADE_IN = 0x1;
public static final int FADE_OUT = 0x2;
- public static final int SLIDE_LEFT = 0;
- public static final int SLIDE_TOP = 1;
- public static final int SLIDE_RIGHT = 2;
- public static final int SLIDE_BOTTOM = 3;
+ public static final int SLIDE_LEFT = Gravity.LEFT;
+ public static final int SLIDE_TOP = Gravity.TOP;
+ public static final int SLIDE_RIGHT = Gravity.RIGHT;
+ public static final int SLIDE_BOTTOM = Gravity.BOTTOM;
private final static TransitionHelper sHelper = new TransitionHelper();
TransitionHelperVersionImpl mImpl;
@@ -50,6 +51,13 @@
}
/**
+ * Returns true if system supports entrance Transition animations.
+ */
+ public static boolean systemSupportsEntranceTransitions() {
+ return Build.VERSION.SDK_INT >= 21;
+ }
+
+ /**
* Interface implemented by classes that support Transition animations.
*/
static interface TransitionHelperVersionImpl {
@@ -74,7 +82,7 @@
public Object createAutoTransition();
- public Object createSlide(SlideCallback callback);
+ public Object createSlide(int slideEdge);
public Object createScale();
@@ -120,6 +128,8 @@
public void addTarget(Object transition, View view);
public Object createDefaultInterpolator(Context context);
+
+ public Object loadTransition(Context context, int resId);
}
/**
@@ -192,7 +202,7 @@
}
@Override
- public Object createSlide(SlideCallback callback) {
+ public Object createSlide(int slideEdge) {
return new TransitionStub();
}
@@ -291,6 +301,11 @@
public Object createDefaultInterpolator(Context context) {
return null;
}
+
+ @Override
+ public Object loadTransition(Context context, int resId) {
+ return new TransitionStub();
+ }
}
/**
@@ -359,8 +374,8 @@
}
@Override
- public Object createSlide(SlideCallback callback) {
- return TransitionHelperKitkat.createSlide(callback);
+ public Object createSlide(int slideEdge) {
+ return TransitionHelperKitkat.createSlide(slideEdge);
}
@Override
@@ -463,6 +478,11 @@
public Object createDefaultInterpolator(Context context) {
return null;
}
+
+ @Override
+ public Object loadTransition(Context context, int resId) {
+ return TransitionHelperKitkat.loadTransition(context, resId);
+ }
}
private static final class TransitionHelperApi21Impl extends TransitionHelperKitkatImpl {
@@ -596,8 +616,8 @@
return mImpl.createTransitionSet(sequential);
}
- public Object createSlide(SlideCallback callback) {
- return mImpl.createSlide(callback);
+ public Object createSlide(int slideEdge) {
+ return mImpl.createSlide(slideEdge);
}
public Object createScale() {
@@ -667,4 +687,8 @@
public Object createDefaultInterpolator(Context context) {
return mImpl.createDefaultInterpolator(context);
}
+
+ public Object loadTransition(Context context, int resId) {
+ return mImpl.loadTransition(context, resId);
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
index bedca43..bd5fa62 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -13,6 +13,7 @@
*/
package android.support.v17.leanback.widget;
+import android.graphics.drawable.Drawable;
import android.support.v17.leanback.R;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -38,10 +39,12 @@
static class ActionViewHolder extends Presenter.ViewHolder {
Action mAction;
Button mButton;
+ int mLayoutDirection;
- public ActionViewHolder(View view) {
+ public ActionViewHolder(View view, int layoutDirection) {
super(view);
mButton = (Button) view.findViewById(R.id.lb_action_button);
+ mLayoutDirection = layoutDirection;
}
}
@@ -50,7 +53,7 @@
public ViewHolder onCreateViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.lb_action_1_line, parent, false);
- return new ActionViewHolder(v);
+ return new ActionViewHolder(v, parent.getLayoutDirection());
}
@Override
@@ -72,27 +75,32 @@
public ViewHolder onCreateViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.lb_action_2_lines, parent, false);
- return new ActionViewHolder(v);
+ return new ActionViewHolder(v, parent.getLayoutDirection());
}
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
Action action = (Action) item;
ActionViewHolder vh = (ActionViewHolder) viewHolder;
+ Drawable icon = action.getIcon();
vh.mAction = action;
- if (action.getIcon() != null) {
- final int leftPadding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_left);
- final int rightPadding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_right);
- vh.view.setPadding(leftPadding, 0, rightPadding, 0);
+ if (icon != null) {
+ final int startPadding = vh.view.getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start);
+ final int endPadding = vh.view.getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end);
+ vh.view.setPaddingRelative(startPadding, 0, endPadding, 0);
} else {
final int padding = vh.view.getResources()
.getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
- vh.view.setPadding(padding, 0, padding, 0);
+ vh.view.setPaddingRelative(padding, 0, padding, 0);
}
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(action.getIcon(), null, null, null);
+ if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
+ } else {
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+ }
CharSequence line1 = action.getLabel1();
CharSequence line2 = action.getLabel2();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 6c4ee28..d11e5b1 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -88,6 +88,7 @@
/**
* Inserts an item into this adapter at the specified index.
+ * If the index is >= {@link #size} an exception will be thrown.
*
* @param index The index at which the item should be inserted.
* @param item The item to insert into the adapter.
@@ -99,13 +100,16 @@
/**
* Adds the objects in the given collection to the adapter, starting at the
- * given index.
+ * given index. If the index is >= {@link #size} an exception will be thrown.
*
* @param index The index at which the items should be inserted.
* @param items A {@link Collection} of items to insert.
*/
public void addAll(int index, Collection items) {
int itemsCount = items.size();
+ if (itemsCount == 0) {
+ return;
+ }
mItems.addAll(index, items);
notifyItemRangeInserted(index, itemsCount);
}
@@ -126,6 +130,18 @@
}
/**
+ * Replaces item at position with a new item and calls notifyItemRangeChanged()
+ * at the given position. Note that this method does not compare new item to
+ * existing item.
+ * @param position The index of item to replace.
+ * @param item The new item to be placed at given position.
+ */
+ public void replace(int position, Object item) {
+ mItems.set(position, item);
+ notifyItemRangeChanged(position, 1);
+ }
+
+ /**
* Removes a range of items from the adapter. The range is specified by giving
* the starting position and the number of elements to remove.
*
@@ -135,6 +151,9 @@
*/
public int removeItems(int position, int count) {
int itemsToRemove = Math.min(count, mItems.size() - position);
+ if (itemsToRemove <= 0) {
+ return 0;
+ }
for (int i = 0; i < itemsToRemove; i++) {
mItems.remove(position);
@@ -148,6 +167,9 @@
*/
public void clear() {
int itemCount = mItems.size();
+ if (itemCount == 0) {
+ return;
+ }
mItems.clear();
notifyItemRangeRemoved(0, itemCount);
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
index 94eee28..bb81bb6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -55,6 +55,10 @@
* navigating away from the first item, the focus maintains a middle
* location.
* <p>
+ * For HorizontalGridView, low edge refers to left edge when RTL is false or
+ * right edge when RTL is true.
+ * For VerticalGridView, low edge refers to top edge.
+ * <p>
* The middle location is calculated by "windowAlignOffset" and
* "windowAlignOffsetPercent"; if neither of these two is defined, the
* default value is 1/2 of the size.
@@ -66,6 +70,10 @@
* navigating to the end of list. When navigating away from the end, the
* focus maintains a middle location.
* <p>
+ * For HorizontalGridView, high edge refers to right edge when RTL is false or
+ * left edge when RTL is true.
+ * For VerticalGridView, high edge refers to bottom edge.
+ * <p>
* The middle location is calculated by "windowAlignOffset" and
* "windowAlignOffsetPercent"; if neither of these two is defined, the
* default value is 1/2 of the size.
@@ -179,6 +187,10 @@
setChildrenDrawingOrderEnabled(true);
setWillNotDraw(true);
setOverScrollMode(View.OVER_SCROLL_NEVER);
+ // Disable change animation by default on leanback.
+ // Change animation will create a new view and cause undesired
+ // focus animation between the old view and new view.
+ getItemAnimator().setSupportsChangeAnimations(false);
}
protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
@@ -248,11 +260,13 @@
}
/**
- * Set the absolute offset in pixels for window alignment.
+ * Set the offset in pixels for window alignment.
*
- * @param offset The number of pixels to offset. Can be negative for
- * alignment from the high edge, or positive for alignment from the
- * low edge.
+ * @param offset The number of pixels to offset. If the offset is positive,
+ * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
+ * if the offset is negative, the absolute value is distance from high
+ * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
+ * Default value is 0.
*/
public void setWindowAlignmentOffset(int offset) {
mLayoutManager.setWindowAlignmentOffset(offset);
@@ -260,11 +274,13 @@
}
/**
- * Get the absolute offset in pixels for window alignment.
+ * Get the offset in pixels for window alignment.
*
- * @return The number of pixels to offset. Will be negative for alignment
- * from the high edge, or positive for alignment from the low edge.
- * Default value is 0.
+ * @return The number of pixels to offset. If the offset is positive,
+ * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
+ * if the offset is negative, the absolute value is distance from high
+ * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
+ * Default value is 0.
*/
public int getWindowAlignmentOffset() {
return mLayoutManager.getWindowAlignmentOffset();
@@ -277,6 +293,7 @@
* @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
* width from low edge. Use
* {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+ * Default value is 50.
*/
public void setWindowAlignmentOffsetPercent(float offsetPercent) {
mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
@@ -476,7 +493,7 @@
/**
* Describes how the child views are positioned. Defaults to
- * GRAVITY_TOP|GRAVITY_LEFT.
+ * GRAVITY_TOP|GRAVITY_START.
*
* @param gravity See {@link android.view.Gravity}
*/
@@ -669,16 +686,6 @@
mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
}
- /**
- * Set the factor by which children should be laid out beyond the view bounds
- * in the direction of orientation. 1.0 disables over reach.
- *
- * @param fraction fraction of over reach
- */
- public final void setPrimaryOverReach(float fraction) {
- mLayoutManager.setPrimaryOverReach(fraction);
- }
-
@Override
public boolean hasOverlappingRendering() {
return mHasOverlappingRendering;
@@ -687,4 +694,14 @@
public void setHasOverlappingRendering(boolean hasOverlapping) {
mHasOverlappingRendering = hasOverlapping;
}
+
+ /**
+ * Notify layout manager that layout directionality has been updated
+ */
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ mLayoutManager.onRtlPropertiesChanged(layoutDirection);
+ }
+
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
similarity index 96%
rename from v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
rename to v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
index 9b87305..654f39b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
@@ -11,7 +11,7 @@
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
-package android.support.v17.leanback.app;
+package android.support.v17.leanback.widget;
import android.content.Context;
import android.graphics.Rect;
@@ -22,9 +22,9 @@
/**
* Top level implementation viewgroup for browse to manage transitions between
* browse sub fragments.
- *
+ * @hide
*/
-class BrowseFrameLayout extends FrameLayout {
+public class BrowseFrameLayout extends FrameLayout {
public interface OnFocusSearchListener {
public View onFocusSearch(View focused, int direction);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
similarity index 94%
rename from v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
rename to v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
index 3f10a63..6b663ce 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
@@ -11,7 +11,7 @@
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
-package android.support.v17.leanback.app;
+package android.support.v17.leanback.widget;
import android.content.Context;
import android.util.AttributeSet;
@@ -22,8 +22,9 @@
* Customized FrameLayout excludes margin of child from calculating the child size.
* So we can change left margin of rows while keep the width of rows unchanged without
* using hardcoded DIPS.
+ * @hide
*/
-class BrowseRowsFrameLayout extends FrameLayout {
+public class BrowseRowsFrameLayout extends FrameLayout {
public BrowseRowsFrameLayout(Context context) {
this(context ,null);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
index 2434f98..3f4ee55 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
@@ -122,21 +122,22 @@
void showControls(Presenter presenter) {
ObjectAdapter adapter = getDisplayedAdapter();
+ int adapterSize = adapter == null ? 0 : adapter.size();
// Shrink the number of attached views
View focusedView = mControlBar.getFocusedChild();
- if (focusedView != null && adapter.size() > 0 &&
- mControlBar.indexOfChild(focusedView) >= adapter.size()) {
+ if (focusedView != null && adapterSize > 0 &&
+ mControlBar.indexOfChild(focusedView) >= adapterSize) {
mControlBar.getChildAt(adapter.size() - 1).requestFocus();
}
- for (int i = mControlBar.getChildCount() - 1; i >= adapter.size(); i--) {
+ for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) {
mControlBar.removeViewAt(i);
}
- for (int position = 0; position < adapter.size() && position < MAX_CONTROLS;
+ for (int position = 0; position < adapterSize && position < MAX_CONTROLS;
position++) {
bindControlToAction(position, adapter, presenter);
}
mControlBar.setChildMarginFromCenter(
- getChildMarginFromCenter(mControlBar.getContext(), adapter.size()));
+ getChildMarginFromCenter(mControlBar.getContext(), adapterSize));
}
void bindControlToAction(int position, Presenter presenter) {
@@ -241,7 +242,9 @@
BoundData data = (BoundData) item;
if (vh.mAdapter != data.adapter) {
vh.mAdapter = data.adapter;
- vh.mAdapter.registerObserver(vh.mDataObserver);
+ if (vh.mAdapter != null) {
+ vh.mAdapter.registerObserver(vh.mDataObserver);
+ }
}
vh.mPresenter = data.presenter;
vh.mData = data;
@@ -251,8 +254,10 @@
@Override
public void onUnbindViewHolder(Presenter.ViewHolder holder) {
ViewHolder vh = (ViewHolder) holder;
- vh.mAdapter.unregisterObserver(vh.mDataObserver);
- vh.mAdapter = null;
+ if (vh.mAdapter != null) {
+ vh.mAdapter.unregisterObserver(vh.mDataObserver);
+ vh.mAdapter = null;
+ }
vh.mData = null;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
index ac14ac6..f45f20e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
@@ -20,6 +20,7 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
+import android.widget.TextView;
/**
* ControlButtonPresenterSelector displays primary and secondary
@@ -57,11 +58,13 @@
static class ActionViewHolder extends Presenter.ViewHolder {
ImageView mIcon;
+ TextView mLabel;
View mFocusableView;
public ActionViewHolder(View view) {
super(view);
mIcon = (ImageView) view.findViewById(R.id.icon);
+ mLabel = (TextView) view.findViewById(R.id.label);
mFocusableView = view.findViewById(R.id.button);
}
}
@@ -85,7 +88,14 @@
Action action = (Action) item;
ActionViewHolder vh = (ActionViewHolder) viewHolder;
vh.mIcon.setImageDrawable(action.getIcon());
- CharSequence contentDescription = !TextUtils.isEmpty(action.getLabel1()) ?
+ if (vh.mLabel != null) {
+ if (action.getIcon() == null) {
+ vh.mLabel.setText(action.getLabel1());
+ } else {
+ vh.mLabel.setText(null);
+ }
+ }
+ CharSequence contentDescription = TextUtils.isEmpty(action.getLabel2()) ?
action.getLabel1() : action.getLabel2();
if (!TextUtils.equals(vh.mFocusableView.getContentDescription(), contentDescription)) {
vh.mFocusableView.setContentDescription(contentDescription);
@@ -98,6 +108,9 @@
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
ActionViewHolder vh = (ActionViewHolder) viewHolder;
vh.mIcon.setImageDrawable(null);
+ if (vh.mLabel != null) {
+ vh.mLabel.setText(null);
+ }
vh.mFocusableView.setContentDescription(null);
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
index 7bf2faf..8556e16 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -467,7 +467,7 @@
getDefaultBackgroundColor(vh.mOverviewView.getContext());
if (useMargin) {
- layoutParams.leftMargin = horizontalMargin;
+ layoutParams.setMarginStart(horizontalMargin);
layoutParams.topMargin = layoutParams.bottomMargin = verticalMargin;
RoundedRectHelper.getInstance().setRoundedRectBackground(vh.mOverviewFrame, bgColor);
vh.mRightPanel.setBackground(null);
@@ -530,4 +530,22 @@
((ColorDrawable) vh.mOverviewFrame.getForeground().mutate()).setColor(dimmedColor);
}
}
+
+ @Override
+ protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
+ super.onRowViewAttachedToWindow(vh);
+ if (mDetailsPresenter != null) {
+ mDetailsPresenter.onViewAttachedToWindow(
+ ((ViewHolder) vh).mDetailsDescriptionViewHolder);
+ }
+ }
+
+ @Override
+ protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
+ super.onRowViewDetachedFromWindow(vh);
+ if (mDetailsPresenter != null) {
+ mDetailsPresenter.onViewDetachedFromWindow(
+ ((ViewHolder) vh).mDetailsDescriptionViewHolder);
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
index 6052c81..410aa28 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
@@ -22,24 +22,93 @@
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.DetailsOverviewRowPresenter.ViewHolder;
import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Matrix;
import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.MeasureSpec;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
import java.util.List;
final class DetailsOverviewSharedElementHelper extends SharedElementCallback {
+ private static final String TAG = "DetailsOverviewSharedElementHelper";
+ private static final boolean DEBUG = false;
+
private ViewHolder mViewHolder;
private Activity mActivityToRunTransition;
+ private boolean mStartedPostpone;
private String mSharedElementName;
private int mRightPanelWidth;
private int mRightPanelHeight;
+ private ScaleType mSavedScaleType;
+ private Matrix mSavedMatrix;
+
+ private boolean hasImageViewScaleChange(View snapshotView) {
+ return snapshotView instanceof ImageView;
+ }
+
+ private void saveImageViewScale() {
+ if (mSavedScaleType == null) {
+ // only save first time after initialize/restoreImageViewScale()
+ ImageView imageView = mViewHolder.mImageView;
+ mSavedScaleType = imageView.getScaleType();
+ mSavedMatrix = mSavedScaleType == ScaleType.MATRIX ? imageView.getMatrix() : null;
+ if (DEBUG) {
+ Log.d(TAG, "saveImageViewScale: "+mSavedScaleType);
+ }
+ }
+ }
+
+ private static void updateImageViewAfterScaleTypeChange(ImageView imageView) {
+ // enforcing imageView to update its internal bounds/matrix immediately
+ imageView.measure(
+ MeasureSpec.makeMeasureSpec(imageView.getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY));
+ imageView.layout(imageView.getLeft(), imageView.getTop(),
+ imageView.getRight(), imageView.getBottom());
+ }
+
+ private void changeImageViewScale(View snapshotView) {
+ ImageView snapshotImageView = (ImageView) snapshotView;
+ ImageView imageView = mViewHolder.mImageView;
+ if (DEBUG) {
+ Log.d(TAG, "changeImageViewScale to "+snapshotImageView.getScaleType());
+ }
+ imageView.setScaleType(snapshotImageView.getScaleType());
+ if (snapshotImageView.getScaleType() == ScaleType.MATRIX) {
+ imageView.setImageMatrix(snapshotImageView.getImageMatrix());
+ }
+ updateImageViewAfterScaleTypeChange(imageView);
+ }
+
+ private void restoreImageViewScale() {
+ if (mSavedScaleType != null) {
+ if (DEBUG) {
+ Log.d(TAG, "restoreImageViewScale to "+mSavedScaleType);
+ }
+ ImageView imageView = mViewHolder.mImageView;
+ imageView.setScaleType(mSavedScaleType);
+ if (mSavedScaleType == ScaleType.MATRIX) {
+ imageView.setImageMatrix(mSavedMatrix);
+ }
+ // only restore once unless another save happens
+ mSavedScaleType = null;
+ updateImageViewAfterScaleTypeChange(imageView);
+ }
+ }
+
@Override
public void onSharedElementStart(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {
+ if (DEBUG) {
+ Log.d(TAG, "onSharedElementStart " + mActivityToRunTransition);
+ }
if (sharedElements.size() < 1) {
return;
}
@@ -47,6 +116,11 @@
if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
return;
}
+ View snapshot = sharedElementSnapshots.get(0);
+ if (hasImageViewScaleChange(snapshot)) {
+ saveImageViewScale();
+ changeImageViewScale(snapshot);
+ }
View imageView = mViewHolder.mImageView;
final int width = overviewView.getWidth();
final int height = overviewView.getHeight();
@@ -69,6 +143,9 @@
@Override
public void onSharedElementEnd(List<String> sharedElementNames,
List<View> sharedElements, List<View> sharedElementSnapshots) {
+ if (DEBUG) {
+ Log.d(TAG, "onSharedElementEnd " + mActivityToRunTransition);
+ }
if (sharedElements.size() < 1) {
return;
}
@@ -76,6 +153,7 @@
if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
return;
}
+ restoreImageViewScale();
// temporary let action row take focus so we defer button background animation
mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
mViewHolder.mActionsRow.setVisibility(View.VISIBLE);
@@ -98,25 +176,38 @@
}
mActivityToRunTransition = activity;
mSharedElementName = sharedElementName;
- if (mActivityToRunTransition != null) {
- ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this);
- ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
- if (timeoutMs > 0) {
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mActivityToRunTransition == null) {
- return;
- }
- ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
- mActivityToRunTransition = null;
+ if (DEBUG) {
+ Log.d(TAG, "postponeEnterTransition " + mActivityToRunTransition);
+ }
+ ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this);
+ ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
+ if (timeoutMs > 0) {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (mStartedPostpone) {
+ return;
}
- }, timeoutMs);
- }
+ if (DEBUG) {
+ Log.d(TAG, "timeout " + mActivityToRunTransition);
+ }
+ startPostponedEnterTransition();
+ }
+ }, timeoutMs);
}
}
void onBindToDrawable(ViewHolder vh) {
+ if (DEBUG) {
+ Log.d(TAG, "onBindToDrawable, could start transition of " + mActivityToRunTransition);
+ }
+ if (mViewHolder != null) {
+ if (DEBUG) {
+ Log.d(TAG, "rebind? clear transitionName on current viewHolder "
+ + mViewHolder.mOverviewFrame);
+ }
+ ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, null);
+ }
// After we got a image drawable, we can determine size of right panel.
// We want right panel to have fixed size so that the right panel don't change size
// when the overview is layout as a small bounds in transition.
@@ -128,37 +219,50 @@
mViewHolder.mRightPanel.removeOnLayoutChangeListener(this);
mRightPanelWidth = mViewHolder.mRightPanel.getWidth();
mRightPanelHeight = mViewHolder.mRightPanel.getHeight();
+ if (DEBUG) {
+ Log.d(TAG, "onLayoutChange records size of right panel as "
+ + mRightPanelWidth + ", "+ mRightPanelHeight);
+ }
}
});
- if (mActivityToRunTransition != null) {
- mViewHolder.mRightPanel.postOnAnimation(new Runnable() {
- @Override
- public void run() {
- if (mActivityToRunTransition == null) {
- return;
- }
- final TransitionHelper transitionHelper = TransitionHelper.getInstance();
- Object transition = transitionHelper.getSharedElementEnterTransition(
- mActivityToRunTransition.getWindow());
- if (transition != null) {
- transitionHelper.setTransitionListener(transition, new TransitionListener() {
- @Override
- public void onTransitionEnd(Object transition) {
- // after transition if the action row still focused, transfer
- // focus to its children
- if (mViewHolder.mActionsRow.isFocused()) {
- mViewHolder.mActionsRow.requestFocus();
- }
- transitionHelper.setTransitionListener(transition, null);
- }
- });
- }
- ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName);
- ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
- mActivityToRunTransition = null;
- mSharedElementName = null;
+ mViewHolder.mRightPanel.postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "setTransitionName "+mViewHolder.mOverviewFrame);
}
- });
+ ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName);
+ final TransitionHelper transitionHelper = TransitionHelper.getInstance();
+ Object transition = transitionHelper.getSharedElementEnterTransition(
+ mActivityToRunTransition.getWindow());
+ if (transition != null) {
+ transitionHelper.setTransitionListener(transition, new TransitionListener() {
+ @Override
+ public void onTransitionEnd(Object transition) {
+ if (DEBUG) {
+ Log.d(TAG, "onTransitionEnd " + mActivityToRunTransition);
+ }
+ // after transition if the action row still focused, transfer
+ // focus to its children
+ if (mViewHolder.mActionsRow.isFocused()) {
+ mViewHolder.mActionsRow.requestFocus();
+ }
+ transitionHelper.setTransitionListener(transition, null);
+ }
+ });
+ }
+ startPostponedEnterTransition();
+ }
+ });
+ }
+
+ private void startPostponedEnterTransition() {
+ if (!mStartedPostpone) {
+ if (DEBUG) {
+ Log.d(TAG, "startPostponedEnterTransition " + mActivityToRunTransition);
+ }
+ ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
+ mStartedPostpone = true;
}
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index b6c4fa0..e32e30f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -61,7 +61,7 @@
// For placement
private int mLeftInset;
private int mTopInset;
- private int mRighInset;
+ private int mRightInset;
private int mBottomInset;
// For alignment
@@ -109,7 +109,7 @@
}
int getOpticalRight(View view) {
- return view.getRight() - mRighInset;
+ return view.getRight() - mRightInset;
}
int getOpticalBottom(View view) {
@@ -117,7 +117,7 @@
}
int getOpticalWidth(View view) {
- return view.getWidth() - mLeftInset - mRighInset;
+ return view.getWidth() - mLeftInset - mRightInset;
}
int getOpticalHeight(View view) {
@@ -129,7 +129,7 @@
}
int getOpticalRightInset() {
- return mRighInset;
+ return mRightInset;
}
int getOpticalTopInset() {
@@ -151,7 +151,7 @@
void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
mLeftInset = leftInset;
mTopInset = topInset;
- mRighInset = rightInset;
+ mRightInset = rightInset;
mBottomInset = bottomInset;
}
@@ -174,6 +174,30 @@
private final BaseGridView mBaseGridView;
/**
+ * Note on conventions in the presence of RTL layout directions:
+ * Many properties and method names reference entities related to the
+ * beginnings and ends of things. In the presence of RTL flows,
+ * it may not be clear whether this is intended to reference a
+ * quantity that changes direction in RTL cases, or a quantity that
+ * does not. Here are the conventions in use:
+ *
+ * start/end: coordinate quantities - do reverse
+ * (optical) left/right: coordinate quantities - do not reverse
+ * low/high: coordinate quantities - do not reverse
+ * min/max: coordinate quantities - do not reverse
+ * scroll offset - coordinate quantities - do not reverse
+ * first/last: positional indices - do not reverse
+ * front/end: positional indices - do not reverse
+ * prepend/append: related to positional indices - do not reverse
+ *
+ * Note that although quantities do not reverse in RTL flows, their
+ * relationship does. In LTR flows, the first positional index is
+ * leftmost; in RTL flows, it is rightmost. Thus, anywhere that
+ * positional quantities are mapped onto coordinate quantities,
+ * the flow must be checked and the logic reversed.
+ */
+
+ /**
* The orientation of a "row".
*/
private int mOrientation = HORIZONTAL;
@@ -269,7 +293,7 @@
/**
* How to position child in secondary direction.
*/
- private int mGravity = Gravity.LEFT | Gravity.TOP;
+ private int mGravity = Gravity.START | Gravity.TOP;
/**
* The number of rows in the grid.
*/
@@ -343,14 +367,19 @@
*/
private boolean mScrollEnabled = true;
- /**
- * Percent of overreach.
- */
- private float mPrimaryOverReach = 1f;
-
private int[] mTempDeltas = new int[2];
/**
+ * Set to true for RTL layout in horizontal orientation
+ */
+ private boolean mReverseFlowPrimary = false;
+
+ /**
+ * Set to true for RTL layout in vertical orientation
+ */
+ private boolean mReverseFlowSecondary = false;
+
+ /**
* Temporaries used for measuring.
*/
private int[] mMeasuredDimension = new int[2];
@@ -373,6 +402,17 @@
mForceFullLayout = true;
}
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ if (mOrientation == HORIZONTAL) {
+ mReverseFlowPrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
+ mReverseFlowSecondary = false;
+ } else {
+ mReverseFlowSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
+ mReverseFlowPrimary = false;
+ }
+ mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
+ }
+
public int getFocusScrollStrategy() {
return mFocusScrollStrategy;
}
@@ -504,14 +544,6 @@
mChildSelectedListener = listener;
}
- public void setPrimaryOverReach(float fraction) {
- if (fraction != mPrimaryOverReach) {
- if (DEBUG) Log.v(getTag(), "setPrimaryOverReach " + fraction);
- mPrimaryOverReach = fraction;
- requestLayout();
- }
- }
-
private int getPositionByView(View view) {
if (view == null) {
return NO_POSITION;
@@ -532,16 +564,34 @@
if (mChildSelectedListener == null) {
return;
}
- if (mFocusPosition != NO_POSITION) {
- View view = findViewByPosition(mFocusPosition);
- if (view != null) {
- RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
- mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
- vh == null? NO_ID: vh.getItemId());
- return;
+
+ View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
+ if (view != null) {
+ RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
+ mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
+ vh == null? NO_ID: vh.getItemId());
+ } else {
+ mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
+ }
+
+ // Children may request layout when a child selection event occurs (such as a change of
+ // padding on the current and previously selected rows).
+ // If in layout, a child requesting layout may have been laid out before the selection
+ // callback.
+ // If it was not, the child will be laid out after the selection callback.
+ // If so, the layout request will be honoured though the view system will emit a double-
+ // layout warning.
+ // If not in layout, we may be scrolling in which case the child layout request will be
+ // eaten by recyclerview. Post a requestLayout.
+ if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (getChildAt(i).isLayoutRequested()) {
+ forceRequestLayout();
+ break;
+ }
}
}
- mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
}
@Override
@@ -659,10 +709,14 @@
final int newItemCount = mState.getItemCount();
- if (focusPosition == NO_POSITION && newItemCount > 0) {
+ // Force the re-init path in the following conditional
+ if (newItemCount == 0) {
+ focusPosition = NO_POSITION;
+ } else if (focusPosition == NO_POSITION && newItemCount > 0) {
// if focus position is never set before, initialize it to 0
focusPosition = 0;
}
+
// If adapter has changed then caches are invalid; otherwise,
// we try to maintain each row's position if number of rows keeps the same
// and existing mGrid contains the focusPosition.
@@ -741,6 +795,7 @@
// Same adapter, we can reuse any attached views
detachAndScrapAttachedViews(mRecycler);
+ updateScrollController();
} else {
// otherwise recreate data structure
@@ -750,6 +805,7 @@
mRows[i] = new StaggeredGrid.Row();
}
mGrid = new StaggeredGridDefault();
+ mGrid.setReversedFlow(mOrientation == HORIZONTAL && mReverseFlowPrimary);
if (newItemCount == 0) {
focusPosition = NO_POSITION;
} else if (focusPosition >= newItemCount) {
@@ -759,9 +815,7 @@
// Adapter may have changed so remove all attached views permanently
removeAndRecycleAllViews(mRecycler);
- mScrollOffsetPrimary = 0;
- mScrollOffsetSecondary = 0;
- mWindowAlignment.reset();
+ initScrollController();
}
mGrid.setProvider(mGridProvider);
@@ -769,7 +823,6 @@
mGrid.setRows(mRows);
mFirstVisiblePos = mLastVisiblePos = NO_POSITION;
- initScrollController();
updateScrollSecondAxis();
return focusPosition;
@@ -787,14 +840,23 @@
private int getRowStartSecondary(int rowIndex) {
int start = 0;
- for (int i = 0; i < rowIndex; i++) {
- start += getRowSizeSecondary(i) + mMarginSecondary;
+ // Iterate from left to right, which is a different index traversal
+ // in RTL flow
+ if (mReverseFlowSecondary) {
+ for (int i = mNumRows-1; i > rowIndex; i--) {
+ start += getRowSizeSecondary(i) + mMarginSecondary;
+ }
+ } else {
+ for (int i = 0; i < rowIndex; i++) {
+ start += getRowSizeSecondary(i) + mMarginSecondary;
+ }
}
return start;
}
private int getSizeSecondary() {
- return getRowStartSecondary(mNumRows - 1) + getRowSizeSecondary(mNumRows - 1);
+ int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
+ return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
}
private void measureScrapChild(int position, int widthSpec, int heightSpec,
@@ -845,9 +907,18 @@
}
}
- if (measure && rowSize < 0 && mState.getItemCount() > 0) {
+ final int itemCount = mState.getItemCount();
+ if (measure && rowSize < 0 && itemCount > 0) {
if (scrapChildWidth < 0 && scrapChildHeight < 0) {
- measureScrapChild(mFocusPosition == NO_POSITION ? 0 : mFocusPosition,
+ int position;
+ if (mFocusPosition == NO_POSITION) {
+ position = 0;
+ } else if (mFocusPosition >= itemCount) {
+ position = itemCount - 1;
+ } else {
+ position = mFocusPosition;
+ }
+ measureScrapChild(position,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
@@ -1091,24 +1162,23 @@
int length = mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
int start, end;
final boolean rowIsEmpty = mRows[rowIndex].high == mRows[rowIndex].low;
- if (append) {
+ boolean addHigh = (!mReverseFlowPrimary) ? append : !append;
+ int lowVisiblePos = (!mReverseFlowPrimary) ? mFirstVisiblePos : mLastVisiblePos;
+ int highVisiblePos = (!mReverseFlowPrimary) ? mLastVisiblePos : mFirstVisiblePos;
+ if (addHigh) {
if (!rowIsEmpty) {
// if there are existing item in the row, add margin between
start = mRows[rowIndex].high + mMarginPrimary;
} else {
- if (mLastVisiblePos >= 0) {
- int lastRow = mGrid.getLocation(mLastVisiblePos).row;
- // if the last visible item is not last row, align to beginning,
- // otherwise start a new column after.
- if (lastRow < mNumRows - 1) {
- start = mRows[lastRow].low;
- } else {
- start = mRows[lastRow].high + mMarginPrimary;
+ if (lowVisiblePos >= 0) {
+ int rowOfLowPos = mGrid.getLocation(lowVisiblePos).row;
+ // If row is after row of lowest position,
+ // start a new column after the first row.
+ if (rowIndex < rowOfLowPos) {
+ mRows[rowIndex].low = mRows[rowOfLowPos].high + mMarginPrimary;
}
- } else {
- start = 0;
}
- mRows[rowIndex].low = start;
+ start = mRows[rowIndex].low;
}
end = start + length;
mRows[rowIndex].high = end;
@@ -1116,25 +1186,17 @@
if (!rowIsEmpty) {
// if there are existing item in the row, add margin between
end = mRows[rowIndex].low - mMarginPrimary;
- start = end - length;
} else {
- if (mFirstVisiblePos >= 0) {
- int firstRow = mGrid.getLocation(mFirstVisiblePos).row;
- // if the first visible item is not first row, align to beginning,
- // otherwise start a new column before.
- if (firstRow > 0) {
- start = mRows[firstRow].low;
- end = start + length;
- } else {
- end = mRows[firstRow].low - mMarginPrimary;
- start = end - length;
+ if (highVisiblePos >= 0) {
+ int rowOfHighPos = mGrid.getLocation(highVisiblePos).row;
+ if (mOrientation == HORIZONTAL ?
+ rowIndex < rowOfHighPos : rowIndex > rowOfHighPos) {
+ mRows[rowIndex].high = mRows[rowOfHighPos].low - mMarginPrimary;
}
- } else {
- start = 0;
- end = length;
}
- mRows[rowIndex].high = end;
+ end = mRows[rowIndex].high;
}
+ start = end - length;
mRows[rowIndex].low = start;
}
if (mFirstVisiblePos < 0) {
@@ -1155,10 +1217,18 @@
Log.d(getTag(), "addView " + index + " " + v);
}
if (index == mFirstVisiblePos) {
- updateScrollMin();
+ if (!mReverseFlowPrimary) {
+ updateScrollMin();
+ } else {
+ updateScrollMax();
+ }
}
if (index == mLastVisiblePos) {
- updateScrollMax();
+ if (!mReverseFlowPrimary) {
+ updateScrollMax();
+ } else {
+ updateScrollMin();
+ }
}
}
};
@@ -1170,7 +1240,9 @@
sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
}
final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
- final int horizontalGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary) ?
+ Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK, View.LAYOUT_DIRECTION_RTL) :
+ mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if (mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP
|| mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT) {
// do nothing
@@ -1217,33 +1289,52 @@
}
private boolean needsAppendVisibleItem() {
- if (mLastVisiblePos < mFocusPosition) {
- return true;
- }
- int right = mScrollOffsetPrimary + mSizePrimary;
- for (int i = 0; i < mNumRows; i++) {
- if (mRows[i].low == mRows[i].high) {
- if (mRows[i].high < right) {
+ if (mReverseFlowPrimary) {
+ for (int i = 0; i < mNumRows; i++) {
+ if (mRows[i].low == mRows[i].high) {
+ if (mRows[i].low > mScrollOffsetPrimary) {
+ return true;
+ }
+ } else if (mRows[i].low - mMarginPrimary > mScrollOffsetPrimary) {
return true;
}
- } else if (mRows[i].high < right - mMarginPrimary) {
- return true;
+ }
+ } else {
+ int right = mScrollOffsetPrimary + mSizePrimary;
+ for (int i = 0; i < mNumRows; i++) {
+ if (mRows[i].low == mRows[i].high) {
+ if (mRows[i].high < right) {
+ return true;
+ }
+ } else if (mRows[i].high < right - mMarginPrimary) {
+ return true;
+ }
}
}
return false;
}
private boolean needsPrependVisibleItem() {
- if (mFirstVisiblePos > mFocusPosition) {
- return true;
- }
- for (int i = 0; i < mNumRows; i++) {
- if (mRows[i].low == mRows[i].high) {
- if (mRows[i].low > mScrollOffsetPrimary) {
+ if (mReverseFlowPrimary) {
+ int right = mScrollOffsetPrimary + mSizePrimary;
+ for (int i = 0; i < mNumRows; i++) {
+ if (mRows[i].low == mRows[i].high) {
+ if (mRows[i].high < right) {
+ return true;
+ }
+ } else if (mRows[i].high < right - mMarginPrimary) {
return true;
}
- } else if (mRows[i].low - mMarginPrimary > mScrollOffsetPrimary) {
- return true;
+ }
+ } else {
+ for (int i = 0; i < mNumRows; i++) {
+ if (mRows[i].low == mRows[i].high) {
+ if (mRows[i].low > mScrollOffsetPrimary) {
+ return true;
+ }
+ } else if (mRows[i].low - mMarginPrimary > mScrollOffsetPrimary) {
+ return true;
+ }
}
}
return false;
@@ -1264,7 +1355,11 @@
} else if ((mLastVisiblePos == NO_POSITION && mState.getItemCount() > 0) ||
(mLastVisiblePos != NO_POSITION &&
mLastVisiblePos < mState.getItemCount() - 1)) {
- mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
+ if (mReverseFlowPrimary) {
+ mGrid.appendItems(mScrollOffsetPrimary);
+ } else {
+ mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
+ }
return false;
} else {
return true;
@@ -1293,7 +1388,11 @@
return false;
}
} else {
- mGrid.prependItems(mScrollOffsetPrimary);
+ if (mReverseFlowPrimary) {
+ mGrid.prependItems(mScrollOffsetPrimary + mSizePrimary);
+ } else {
+ mGrid.prependItems(mScrollOffsetPrimary);
+ }
return false;
}
} else {
@@ -1328,7 +1427,9 @@
boolean update = false;
while(mLastVisiblePos > mFirstVisiblePos && mLastVisiblePos > mFocusPosition) {
View view = findViewByPosition(mLastVisiblePos);
- if (getViewMin(view) > mSizePrimary) {
+ boolean offEnd = (!mReverseFlowPrimary) ? getViewMin(view) > mSizePrimary :
+ getViewMax(view) < 0;
+ if (offEnd) {
removeChildAt(mLastVisiblePos);
mLastVisiblePos--;
update = true;
@@ -1348,7 +1449,9 @@
boolean update = false;
while(mLastVisiblePos > mFirstVisiblePos && mFirstVisiblePos < mFocusPosition) {
View view = findViewByPosition(mFirstVisiblePos);
- if (getViewMax(view) < 0) {
+ boolean offFront = (!mReverseFlowPrimary) ? getViewMax(view) < 0:
+ getViewMin(view) > mSizePrimary;
+ if (offFront) {
removeChildAt(mFirstVisiblePos);
mFirstVisiblePos++;
update = true;
@@ -1385,7 +1488,7 @@
// Fast layout when there is no structure change, adapter change, etc.
protected void fastRelayout(boolean scrollToFocus) {
- initScrollController();
+ updateScrollController();
List<Integer>[] rows = mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
@@ -1493,12 +1596,14 @@
mFocusPositionOffset = 0;
}
saveContext(recycler, state);
+
// Track the old focus view so we can adjust our system scroll position
// so that any scroll animations happening now will remain valid.
// We must use same delta in Pre Layout (if prelayout exists) and second layout.
// So we cache the deltas in PreLayout and use it in second layout.
int delta = 0, deltaSecondary = 0;
- if (mFocusPosition != NO_POSITION && scrollToFocus) {
+ if (mFocusPosition != NO_POSITION && scrollToFocus
+ && mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
// FIXME: we should get the remaining scroll animation offset from RecyclerView
View focusView = findViewByPosition(mFocusPosition);
if (focusView != null) {
@@ -1524,6 +1629,9 @@
if (DEBUG) Log.v(getTag(), "savedFocusPos " + savedFocusPos +
" mFocusPosition " + mFocusPosition);
}
+ if (mFocusPosition == NO_POSITION) {
+ mBaseGridView.clearFocus();
+ }
mWindowAlignment.mainAxis().invalidateScrollMin();
mWindowAlignment.mainAxis().invalidateScrollMax();
@@ -1601,6 +1709,7 @@
if (fastRelayout && mFocusPosition != savedFocusPos) {
dispatchChildSelected();
}
+
mInLayout = false;
leaveContext();
if (DEBUG) Log.v(getTag(), "layoutChildren end");
@@ -1668,16 +1777,20 @@
// scroll in main direction may add/prune views
private int scrollDirectionPrimary(int da) {
+ boolean isMaxUnknown = false, isMinUnknown = false;
+ int minScroll = 0, maxScroll = 0;
if (da > 0) {
- if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
- int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
+ isMaxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
+ if (!isMaxUnknown) {
+ maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
if (mScrollOffsetPrimary + da > maxScroll) {
da = maxScroll - mScrollOffsetPrimary;
}
}
} else if (da < 0) {
- if (!mWindowAlignment.mainAxis().isMinUnknown()) {
- int minScroll = mWindowAlignment.mainAxis().getMinScroll();
+ isMinUnknown = mWindowAlignment.mainAxis().isMinUnknown();
+ if (!isMinUnknown) {
+ minScroll = mWindowAlignment.mainAxis().getMinScroll();
if (mScrollOffsetPrimary + da < minScroll) {
da = minScroll - mScrollOffsetPrimary;
}
@@ -1696,17 +1809,33 @@
boolean updated;
if (da > 0) {
- appendVisibleItems();
+ if (mReverseFlowPrimary) {
+ prependVisibleItems();
+ } else {
+ appendVisibleItems();
+ }
} else if (da < 0) {
- prependVisibleItems();
+ if (mReverseFlowPrimary) {
+ appendVisibleItems();
+ } else {
+ prependVisibleItems();
+ }
}
updated = getChildCount() > childCount;
childCount = getChildCount();
if (da > 0) {
- removeInvisibleViewsAtFront();
+ if (mReverseFlowPrimary) {
+ removeInvisibleViewsAtEnd();
+ } else {
+ removeInvisibleViewsAtFront();
+ }
} else if (da < 0) {
- removeInvisibleViewsAtEnd();
+ if (mReverseFlowPrimary) {
+ removeInvisibleViewsAtFront();
+ } else {
+ removeInvisibleViewsAtEnd();
+ }
}
updated |= getChildCount() < childCount;
@@ -1730,12 +1859,14 @@
}
private void updateScrollMax() {
- if (mLastVisiblePos < 0) {
+ int highVisiblePos = (!mReverseFlowPrimary) ? mLastVisiblePos : mFirstVisiblePos;
+ int highMaxPos = (!mReverseFlowPrimary) ? mState.getItemCount() - 1 : 0;
+ if (highVisiblePos < 0) {
return;
}
- final boolean lastAvailable = mLastVisiblePos == mState.getItemCount() - 1;
+ final boolean highAvailable = highVisiblePos == highMaxPos;
final boolean maxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
- if (!lastAvailable && maxUnknown) {
+ if (!highAvailable && maxUnknown) {
return;
}
int maxEdge = Integer.MIN_VALUE;
@@ -1747,17 +1878,18 @@
}
}
int maxScroll = Integer.MAX_VALUE;
- for (int i = mLastVisiblePos; i >= mFirstVisiblePos; i--) {
- StaggeredGrid.Location location = mGrid.getLocation(i);
+ for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
+ int pos = mReverseFlowPrimary ? i : mLastVisiblePos-i+mFirstVisiblePos;
+ StaggeredGrid.Location location = mGrid.getLocation(pos);
if (location != null && location.row == rowIndex) {
int savedMaxEdge = mWindowAlignment.mainAxis().getMaxEdge();
mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
- maxScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
+ maxScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
mWindowAlignment.mainAxis().setMaxEdge(savedMaxEdge);
break;
}
}
- if (lastAvailable) {
+ if (highAvailable) {
mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
mWindowAlignment.mainAxis().setMaxScroll(maxScroll);
if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge +
@@ -1774,12 +1906,14 @@
}
private void updateScrollMin() {
- if (mFirstVisiblePos < 0) {
+ int lowVisiblePos = (!mReverseFlowPrimary) ? mFirstVisiblePos : mLastVisiblePos;
+ int lowMinPos = (!mReverseFlowPrimary) ? 0 : mState.getItemCount() - 1;
+ if (lowVisiblePos < 0) {
return;
}
- final boolean firstAvailable = mFirstVisiblePos == 0;
+ final boolean lowAvailable = lowVisiblePos == lowMinPos;
final boolean minUnknown = mWindowAlignment.mainAxis().isMinUnknown();
- if (!firstAvailable && minUnknown) {
+ if (!lowAvailable && minUnknown) {
return;
}
int minEdge = Integer.MAX_VALUE;
@@ -1792,16 +1926,17 @@
}
int minScroll = Integer.MIN_VALUE;
for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
- StaggeredGrid.Location location = mGrid.getLocation(i);
+ int pos = mReverseFlowPrimary ? mLastVisiblePos-i+mFirstVisiblePos : i;
+ StaggeredGrid.Location location = mGrid.getLocation(pos);
if (location != null && location.row == rowIndex) {
int savedMinEdge = mWindowAlignment.mainAxis().getMinEdge();
mWindowAlignment.mainAxis().setMinEdge(minEdge);
- minScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
+ minScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
mWindowAlignment.mainAxis().setMinEdge(savedMinEdge);
break;
}
}
- if (firstAvailable) {
+ if (lowAvailable) {
mWindowAlignment.mainAxis().setMinEdge(minEdge);
mWindowAlignment.mainAxis().setMinScroll(minScroll);
if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge +
@@ -1823,6 +1958,23 @@
}
private void initScrollController() {
+ mWindowAlignment.reset();
+ mWindowAlignment.horizontal.setSize(getWidth());
+ mWindowAlignment.vertical.setSize(getHeight());
+ mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
+ mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
+ mSizePrimary = mWindowAlignment.mainAxis().getSize();
+ mScrollOffsetPrimary = -mWindowAlignment.mainAxis().getPaddingLow();
+ mScrollOffsetSecondary = -mWindowAlignment.secondAxis().getPaddingLow();
+
+ if (DEBUG) {
+ Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
+ + " mWindowAlignment " + mWindowAlignment
+ + " mScrollOffsetPrimary " + mScrollOffsetPrimary);
+ }
+ }
+
+ private void updateScrollController() {
// mScrollOffsetPrimary and mScrollOffsetSecondary includes the padding.
// e.g. when topPadding is 16 for horizontal grid view, the initial
// mScrollOffsetSecondary is -16. fastLayout() put views based on offsets(not padding),
@@ -1839,20 +1991,16 @@
mScrollOffsetPrimary -= paddingPrimaryDiff;
mScrollOffsetSecondary -= paddingSecondaryDiff;
- if (mOrientation == HORIZONTAL) {
- mWindowAlignment.horizontal.setSize((int)(getWidth() * mPrimaryOverReach + 0.5f));
- mWindowAlignment.vertical.setSize(getHeight());
- } else {
- mWindowAlignment.horizontal.setSize(getWidth());
- mWindowAlignment.vertical.setSize((int) (getHeight() * mPrimaryOverReach + 0.5f));
- }
+ mWindowAlignment.horizontal.setSize(getWidth());
+ mWindowAlignment.vertical.setSize(getHeight());
mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
mSizePrimary = mWindowAlignment.mainAxis().getSize();
if (DEBUG) {
- Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
- + " mWindowAlignment " + mWindowAlignment);
+ Log.v(getTag(), "updateScrollController mSizePrimary " + mSizePrimary
+ + " mWindowAlignment " + mWindowAlignment
+ + " mScrollOffsetPrimary " + mScrollOffsetPrimary);
}
}
@@ -1869,9 +2017,12 @@
}
public void setSelection(RecyclerView parent, int position, boolean smooth) {
- if (mFocusPosition == position) {
- return;
+ if (mFocusPosition != position && position != NO_POSITION) {
+ scrollToSelection(parent, position, smooth);
}
+ }
+
+ private void scrollToSelection(RecyclerView parent, int position, boolean smooth) {
View view = findViewByPosition(position);
if (view != null) {
mInSelection = true;
@@ -1897,7 +2048,10 @@
return null;
}
final int firstChildPos = getPosition(getChildAt(0));
- final int direction = targetPosition < firstChildPos ? -1 : 1;
+ // TODO We should be able to deduce direction from bounds of current and target focus,
+ // rather than making assumptions about positions and directionality
+ final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos : targetPosition < firstChildPos;
+ final int direction = isStart ? -1 : 1;
if (mOrientation == HORIZONTAL) {
return new PointF(direction, 0);
} else {
@@ -1958,7 +2112,7 @@
@Override
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
- if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
+ if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
+ positionStart + " itemCount " + itemCount);
if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE
&& getChildAt(mFocusPosition) != null) {
@@ -1978,7 +2132,7 @@
@Override
public void onItemsMoved(RecyclerView recyclerView, int fromPosition, int toPosition,
int itemCount) {
- if (DEBUG) Log.v(getTag(), "onItemsAdded fromPosition "
+ if (DEBUG) Log.v(getTag(), "onItemsMoved fromPosition "
+ fromPosition + " toPosition " + toPosition);
if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE
&& getChildAt(mFocusPosition) != null) {
@@ -1999,6 +2153,8 @@
@Override
public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+ if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
+ + positionStart + " itemCount " + itemCount);
for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
mChildrenStates.remove(i);
}
@@ -2009,6 +2165,11 @@
if (mFocusSearchDisabled) {
return true;
}
+ if (getPositionByView(child) == NO_POSITION) {
+ // This shouldn't happen, but in case it does be sure not to attempt a
+ // scroll to a view whose item has been removed.
+ return true;
+ }
if (!mInLayout && !mInSelection) {
scrollToView(child, true);
}
@@ -2041,28 +2202,33 @@
}
private int getPrimarySystemScrollPosition(View view) {
- int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
- int pos = getPositionByView(view);
- StaggeredGrid.Location location = mGrid.getLocation(pos);
- final int row = location.row;
- boolean isFirst = mFirstVisiblePos == 0;
+ final int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
+ final int viewMin = getViewMin(view);
+ final int viewMax = getViewMax(view);
// TODO: change to use State object in onRequestChildFocus()
- boolean isLast = mLastVisiblePos == (mState == null ?
- getItemCount() : mState.getItemCount()) - 1;
- if (isFirst || isLast) {
- for (int i = getChildCount() - 1; i >= 0; i--) {
- int position = getPositionByIndex(i);
- StaggeredGrid.Location loc = mGrid.getLocation(position);
- if (loc != null && loc.row == row) {
- if (position < pos) {
- isFirst = false;
- } else if (position > pos) {
- isLast = false;
- }
- }
+ boolean isMin, isMax;
+ if (!mReverseFlowPrimary) {
+ isMin = mFirstVisiblePos == 0;
+ isMax = mLastVisiblePos == (mState == null ?
+ getItemCount() : mState.getItemCount()) - 1;
+ } else {
+ isMax = mFirstVisiblePos == 0;
+ isMin = mLastVisiblePos == (mState == null ?
+ getItemCount() : mState.getItemCount()) - 1;
+ }
+ for (int i = getChildCount() - 1; (isMin || isMax) && i >= 0; i--) {
+ View v = getChildAt(i);
+ if (v == view || v == null) {
+ continue;
+ }
+ if (isMin && getViewMin(v) < viewMin) {
+ isMin = false;
+ }
+ if (isMax && getViewMax(v) > viewMax) {
+ isMax = false;
}
}
- return mWindowAlignment.mainAxis().getSystemScrollPos(viewCenterPrimary, isFirst, isLast);
+ return mWindowAlignment.mainAxis().getSystemScrollPos(viewCenterPrimary, isMin, isMax);
}
private int getSecondarySystemScrollPosition(View view) {
@@ -2070,10 +2236,15 @@
int pos = getPositionByView(view);
StaggeredGrid.Location location = mGrid.getLocation(pos);
final int row = location.row;
- boolean isFirst = row == 0;
- boolean isLast = row == mGrid.getNumRows() - 1;
- return mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary,
- isFirst, isLast);
+ final boolean isMin, isMax;
+ if (!mReverseFlowSecondary) {
+ isMin = row == 0;
+ isMax = row == mGrid.getNumRows() - 1;
+ } else {
+ isMax = row == 0;
+ isMin = row == mGrid.getNumRows() - 1;
+ }
+ return mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary, isMin, isMax);
}
/**
@@ -2199,7 +2370,8 @@
int scrollSecondary = getSecondarySystemScrollPosition(view);
if (DEBUG) {
Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
- +" " + mWindowAlignment);
+ + " " + mWindowAlignment);
+ Log.v(getTag(), "getAlignedPosition " + mScrollOffsetPrimary + " " + mScrollOffsetSecondary);
}
scrollPrimary -= mScrollOffsetPrimary;
scrollSecondary -= mScrollOffsetSecondary;
@@ -2249,12 +2421,9 @@
public void setScrollEnabled(boolean scrollEnabled) {
if (mScrollEnabled != scrollEnabled) {
mScrollEnabled = scrollEnabled;
- if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
- View focusView = findViewByPosition(mFocusPosition == NO_POSITION ? 0 :
- mFocusPosition);
- if (focusView != null) {
- scrollToView(focusView, true);
- }
+ if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
+ && mFocusPosition != NO_POSITION) {
+ scrollToSelection(mBaseGridView, mFocusPosition, true);
}
}
}
@@ -2404,26 +2573,27 @@
View view = null;
int movement = getMovement(direction);
+ final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
if (mNumRows == 1) {
// for simple row, use LinearSmoothScroller to smooth animation.
// It will stay at a fixed cap speed in continuous scroll.
if (movement == NEXT_ITEM) {
int newPos = mFocusPosition + mNumRows;
- if (newPos < getItemCount()) {
+ if (newPos < getItemCount() && mScrollEnabled) {
setSelectionSmooth(mBaseGridView, newPos);
view = focused;
} else {
- if (!mFocusOutEnd) {
+ if (isScroll || !mFocusOutEnd) {
view = focused;
}
}
} else if (movement == PREV_ITEM){
int newPos = mFocusPosition - mNumRows;
- if (newPos >= 0) {
+ if (newPos >= 0 && mScrollEnabled) {
setSelectionSmooth(mBaseGridView, newPos);
view = focused;
} else {
- if (!mFocusOutFront) {
+ if (isScroll || !mFocusOutFront) {
view = focused;
}
}
@@ -2445,9 +2615,9 @@
if (view == null) {
// returning the same view to prevent focus lost when scrolling past the end of the list
if (movement == PREV_ITEM) {
- view = mFocusOutFront ? null : focused;
+ view = mFocusOutFront && !isScroll ? null : focused;
} else if (movement == NEXT_ITEM){
- view = mFocusOutEnd ? null : focused;
+ view = mFocusOutEnd && !isScroll ? null : focused;
}
}
leaveContext();
@@ -2525,10 +2695,10 @@
if (mOrientation == HORIZONTAL) {
switch(direction) {
case View.FOCUS_LEFT:
- movement = PREV_ITEM;
+ movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
break;
case View.FOCUS_RIGHT:
- movement = NEXT_ITEM;
+ movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
break;
case View.FOCUS_UP:
movement = PREV_ROW;
@@ -2540,10 +2710,10 @@
} else if (mOrientation == VERTICAL) {
switch(direction) {
case View.FOCUS_LEFT:
- movement = PREV_ROW;
+ movement = (!mReverseFlowPrimary) ? PREV_ROW : NEXT_ROW;
break;
case View.FOCUS_RIGHT:
- movement = NEXT_ROW;
+ movement = (!mReverseFlowPrimary) ? NEXT_ROW : PREV_ROW;
break;
case View.FOCUS_UP:
movement = PREV_ITEM;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
index a280f4f..d94913c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
@@ -22,23 +22,21 @@
public class HeaderItem {
private final long mId;
- private final String mImageUri;
private final String mName;
/**
* Create a header item. All fields are optional.
*/
- public HeaderItem(long id, String name, String imageUri) {
+ public HeaderItem(long id, String name) {
mId = id;
mName = name;
- mImageUri = imageUri;
}
/**
- * Create a header item. All fields are optional.
+ * Create a header item.
*/
- public HeaderItem(String name, String imageUri) {
- this(NO_ID, name, imageUri);
+ public HeaderItem(String name) {
+ this(NO_ID, name);
}
/**
@@ -54,12 +52,4 @@
public final String getName() {
return mName;
}
-
- /**
- * Returns the icon for this header item.
- */
- public final String getImageUri() {
- return mImageUri;
- }
-
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
index 0700995..425883a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
@@ -14,6 +14,7 @@
package android.support.v17.leanback.widget;
import android.graphics.Rect;
+import android.support.v4.view.ViewCompat;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.MeasureSpec;
@@ -42,12 +43,18 @@
protected void onViewSelected(View view) {
int rightLimit = getParentViewGroup().getWidth() -
getParentViewGroup().getPaddingRight();
- // measure the hover card width, if it's too large, align hover card
- // right edge with row view's right edge
+ int leftLimit = getParentViewGroup().getPaddingLeft();
+ // measure the hover card width; if it's too large, align hover card
+ // end edge with row view's end edge, otherwise align start edges.
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
- if (mCardLeft + view.getMeasuredWidth() > rightLimit) {
+ boolean isRtl = ViewCompat.getLayoutDirection(view) == View.LAYOUT_DIRECTION_RTL;
+ if (!isRtl && mCardLeft + view.getMeasuredWidth() > rightLimit) {
params.leftMargin = rightLimit - view.getMeasuredWidth();
+ } else if (isRtl && mCardLeft < leftLimit) {
+ params.leftMargin = leftLimit;
+ } else if (isRtl) {
+ params.leftMargin = mCardRight - view.getMeasuredWidth();
} else {
params.leftMargin = mCardLeft;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
index c4bb99a..d1545dd 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
@@ -37,6 +37,7 @@
private TextView mContentView;
private ImageView mBadgeImage;
private ImageView mBadgeFadeMask;
+ private boolean mAttachedToWindow;
public ImageCardView(Context context) {
this(context, null);
@@ -111,7 +112,7 @@
} else {
mImageView.setVisibility(View.VISIBLE);
if (fade) {
- fadeIn(mImageView);
+ fadeIn();
} else {
mImageView.animate().cancel();
mImageView.setAlpha(1f);
@@ -216,10 +217,12 @@
return mBadgeImage.getDrawable();
}
- private void fadeIn(View v) {
- v.setAlpha(0f);
- v.animate().alpha(1f).setDuration(v.getContext().getResources().getInteger(
- android.R.integer.config_shortAnimTime)).start();
+ private void fadeIn() {
+ mImageView.setAlpha(0f);
+ if (mAttachedToWindow) {
+ mImageView.animate().alpha(1f).setDuration(mImageView.getResources().getInteger(
+ android.R.integer.config_shortAnimTime));
+ }
}
@Override
@@ -241,7 +244,17 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAttachedToWindow = true;
+ if (mImageView.getAlpha() == 0) {
+ fadeIn();
+ }
+ }
+
+ @Override
protected void onDetachedFromWindow() {
+ mAttachedToWindow = false;
mImageView.animate().cancel();
mImageView.setAlpha(1f);
super.onDetachedFromWindow();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
index a8deee4..fc0237b 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -17,11 +17,14 @@
import android.content.res.TypedArray;
import android.support.v17.leanback.R;
import android.support.v17.leanback.graphics.ColorOverlayDimmer;
+import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import java.util.HashMap;
+
/**
* ListRowPresenter renders {@link ListRow} using a
* {@link HorizontalGridView} hosted in a {@link ListRowView}.
@@ -47,6 +50,8 @@
private static final String TAG = "ListRowPresenter";
private static final boolean DEBUG = false;
+ private static final int DEFAULT_RECYCLED_POOL_SIZE = 24;
+
public static class ViewHolder extends RowPresenter.ViewHolder {
final ListRowPresenter mListRowPresenter;
final HorizontalGridView mGridView;
@@ -87,6 +92,7 @@
private boolean mShadowEnabled = true;
private int mBrowseRowsFadingEdgeLength = -1;
private boolean mRoundedCornersEnabled = true;
+ private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>();
private static int sSelectedRowTopPadding;
private static int sExpandedSelectedRowTopPadding;
@@ -184,10 +190,6 @@
}
if (needsDefaultListSelectEffect()) {
ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView);
- ((ViewGroup) rowViewHolder.view).setClipChildren(false);
- if (rowViewHolder.mContainerViewHolder != null) {
- ((ViewGroup) rowViewHolder.mContainerViewHolder.view).setClipChildren(false);
- }
}
FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter,
mZoomFactor, false);
@@ -242,7 +244,8 @@
@Override
public void onAddPresenter(Presenter presenter, int type) {
- rowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(type, 24);
+ rowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(
+ type, getRecycledPoolSize(presenter));
}
});
}
@@ -252,6 +255,21 @@
}
/**
+ * Sets the recycled pool size for the given presenter.
+ */
+ public void setRecycledPoolSize(Presenter presenter, int size) {
+ mRecycledPoolSize.put(presenter, size);
+ }
+
+ /**
+ * Returns the recycled pool size for the given presenter.
+ */
+ public int getRecycledPoolSize(Presenter presenter) {
+ return mRecycledPoolSize.containsKey(presenter) ? mRecycledPoolSize.get(presenter) :
+ DEFAULT_RECYCLED_POOL_SIZE;
+ }
+
+ /**
* Set {@link PresenterSelector} used for showing a select object in a hover card.
*/
public final void setHoverCardPresenterSelector(PresenterSelector selector) {
@@ -537,4 +555,11 @@
vh.mGridView.setScrollEnabled(!freeze);
}
+ @Override
+ public void setEntranceTransitionState(RowPresenter.ViewHolder holder,
+ boolean afterEntrance) {
+ super.setEntranceTransitionState(holder, afterEntrance);
+ ((ViewHolder) holder).mGridView.setChildrenVisibility(
+ afterEntrance? View.VISIBLE : View.INVISIBLE);
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
index 03648c6..ddac678 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
@@ -49,6 +49,7 @@
private int mIndex;
private Drawable[] mDrawables;
private String[] mLabels;
+ private String[] mLabels2;
/**
* Constructor
@@ -67,37 +68,64 @@
setIndex(0);
}
+ /**
+ * Sets the array of strings used as labels. The size of the array defines the range
+ * of valid indices for this action. The labels are used to define the accessibility
+ * content description unless secondary labels are provided.
+ */
public void setLabels(String[] labels) {
mLabels = labels;
setIndex(0);
}
/**
- * Returns the number of drawables.
+ * Sets the array of strings used as secondary labels. These labels are used
+ * in place of the primary labels for accessibility content description only.
*/
- public int getNumberOfDrawables() {
- return mDrawables.length;
+ public void setSecondaryLabels(String[] labels) {
+ mLabels2 = labels;
+ setIndex(0);
+ }
+
+ /**
+ * Returns the number of actions.
+ */
+ public int getActionCount() {
+ if (mDrawables != null) {
+ return mDrawables.length;
+ }
+ if (mLabels != null) {
+ return mLabels.length;
+ }
+ return 0;
}
/**
* Returns the drawable at the given index.
*/
public Drawable getDrawable(int index) {
- return mDrawables[index];
+ return mDrawables == null ? null : mDrawables[index];
}
/**
* Returns the label at the given index.
*/
public String getLabel(int index) {
- return mLabels[index];
+ return mLabels == null ? null : mLabels[index];
+ }
+
+ /**
+ * Returns the secondary label at the given index.
+ */
+ public String getSecondaryLabel(int index) {
+ return mLabels2 == null ? null : mLabels2[index];
}
/**
* Increments the index, wrapping to zero once the end is reached.
*/
public void nextIndex() {
- setIndex(mIndex < mDrawables.length - 1 ? mIndex + 1 : 0);
+ setIndex(mIndex < getActionCount() - 1 ? mIndex + 1 : 0);
}
/**
@@ -105,10 +133,15 @@
*/
public void setIndex(int index) {
mIndex = index;
- setIcon(mDrawables[mIndex]);
+ if (mDrawables != null) {
+ setIcon(mDrawables[mIndex]);
+ }
if (mLabels != null) {
setLabel1(mLabels[mIndex]);
}
+ if (mLabels2 != null) {
+ setLabel2(mLabels2[mIndex]);
+ }
}
/**
@@ -156,32 +189,92 @@
/**
* An action displaying an icon for fast forward.
*/
- public static class FastForwardAction extends Action {
+ public static class FastForwardAction extends MultiAction {
/**
* Constructor
* @param context Context used for loading resources.
*/
public FastForwardAction(Context context) {
+ this(context, 1);
+ }
+
+ /**
+ * Constructor
+ * @param context Context used for loading resources.
+ * @param numSpeeds Number of supported fast forward speeds.
+ */
+ public FastForwardAction(Context context, int numSpeeds) {
super(R.id.lb_control_fast_forward);
- setIcon(getStyledDrawable(context,
- R.styleable.lbPlaybackControlsActionIcons_fast_forward));
- setLabel1(context.getString(R.string.lb_playback_controls_fast_forward));
+
+ if (numSpeeds < 1) {
+ throw new IllegalArgumentException("numSpeeds must be > 0");
+ }
+ Drawable[] drawables = new Drawable[numSpeeds];
+ drawables[0] = getStyledDrawable(context,
+ R.styleable.lbPlaybackControlsActionIcons_fast_forward);
+ setDrawables(drawables);
+
+ String[] labels = new String[getActionCount()];
+ labels[0] = context.getString(R.string.lb_playback_controls_fast_forward);
+
+ String[] labels2 = new String[getActionCount()];
+ labels2[0] = labels[0];
+
+ for (int i = 1; i < numSpeeds; i++) {
+ int multiplier = i + 1;
+ labels[i] = context.getResources().getString(
+ R.string.lb_control_display_fast_forward_multiplier, multiplier);
+ labels2[i] = context.getResources().getString(
+ R.string.lb_playback_controls_fast_forward_multiplier, multiplier);
+ }
+ setLabels(labels);
+ setSecondaryLabels(labels2);
}
}
/**
* An action displaying an icon for rewind.
*/
- public static class RewindAction extends Action {
+ public static class RewindAction extends MultiAction {
/**
* Constructor
* @param context Context used for loading resources.
*/
public RewindAction(Context context) {
+ this(context, 1);
+ }
+
+ /**
+ * Constructor
+ * @param context Context used for loading resources.
+ * @param numSpeeds Number of supported fast forward speeds.
+ */
+ public RewindAction(Context context, int numSpeeds) {
super(R.id.lb_control_fast_rewind);
- setIcon(getStyledDrawable(context,
- R.styleable.lbPlaybackControlsActionIcons_rewind));
- setLabel1(context.getString(R.string.lb_playback_controls_rewind));
+
+ if (numSpeeds < 1) {
+ throw new IllegalArgumentException("numSpeeds must be > 0");
+ }
+ Drawable[] drawables = new Drawable[numSpeeds];
+ drawables[0] = getStyledDrawable(context,
+ R.styleable.lbPlaybackControlsActionIcons_rewind);
+ setDrawables(drawables);
+
+ String[] labels = new String[getActionCount()];
+ labels[0] = context.getString(R.string.lb_playback_controls_rewind);
+
+ String[] labels2 = new String[getActionCount()];
+ labels2[0] = labels[0];
+
+ for (int i = 1; i < numSpeeds; i++) {
+ int multiplier = i + 1;
+ labels[i] = labels[i] = context.getResources().getString(
+ R.string.lb_control_display_rewind_multiplier, multiplier);
+ labels2[i] = context.getResources().getString(
+ R.string.lb_playback_controls_rewind_multiplier, multiplier);
+ }
+ setLabels(labels);
+ setSecondaryLabels(labels2);
}
}
@@ -267,7 +360,7 @@
super(R.id.lb_control_thumbs_up, context,
R.styleable.lbPlaybackControlsActionIcons_thumb_up,
R.styleable.lbPlaybackControlsActionIcons_thumb_up_outline);
- String[] labels = new String[getNumberOfDrawables()];
+ String[] labels = new String[getActionCount()];
labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_up);
labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_up_outline);
setLabels(labels);
@@ -282,7 +375,7 @@
super(R.id.lb_control_thumbs_down, context,
R.styleable.lbPlaybackControlsActionIcons_thumb_down,
R.styleable.lbPlaybackControlsActionIcons_thumb_down_outline);
- String[] labels = new String[getNumberOfDrawables()];
+ String[] labels = new String[getActionCount()];
labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_down);
labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_down_outline);
setLabels(labels);
@@ -600,6 +693,8 @@
/**
* Sets the current time in milliseconds for the playback controls row.
+ * If this row is bound to a view, the view will automatically
+ * be updated to reflect the new value.
*/
public void setCurrentTime(int ms) {
if (mCurrentTimeMs != ms) {
@@ -617,6 +712,8 @@
/**
* Sets the buffered progress for the playback controls row.
+ * If this row is bound to a view, the view will automatically
+ * be updated to reflect the new value.
*/
public void setBufferedProgress(int ms) {
if (mBufferedProgressMs != ms) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
index be55a40..9c62a1b 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
@@ -116,6 +116,9 @@
ObjectAdapter adapter = primary ?
((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter() :
((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter();
+ if (adapter == null) {
+ return null;
+ }
if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) {
ControlButtonPresenterSelector selector =
(ControlButtonPresenterSelector) adapter.getPresenterSelector();
@@ -361,6 +364,7 @@
MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
if (row.getImageDrawable() == null || row.getItem() == null) {
+ vh.mImageView.setImageDrawable(null);
vh.setBackground(vh.mControlsDock);
lp.setMarginStart(0);
lp.setMarginEnd(0);
@@ -413,4 +417,22 @@
((ViewHolder) vh).dispatchItemSelection();
}
}
+
+ @Override
+ protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
+ super.onRowViewAttachedToWindow(vh);
+ if (mDescriptionPresenter != null) {
+ mDescriptionPresenter.onViewAttachedToWindow(
+ ((ViewHolder) vh).mDescriptionViewHolder);
+ }
+ }
+
+ @Override
+ protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
+ super.onRowViewDetachedFromWindow(vh);
+ if (mDescriptionPresenter != null) {
+ mDescriptionPresenter.onViewDetachedFromWindow(
+ ((ViewHolder) vh).mDescriptionViewHolder);
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
index 9a2b0bf..7e1ecb0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
@@ -101,7 +101,7 @@
*
* <p>Becoming detached from the window is not necessarily a permanent condition;
* the consumer of an presenter's views may choose to cache views offscreen while they
- * are not visible, attaching an detaching them as appropriate.</p>
+ * are not visible, attaching and detaching them as appropriate.</p>
*
* Any view property animations should be cancelled here or the view may fail
* to be recycled.
@@ -117,7 +117,7 @@
* Utility method for removing all running animations on a view.
*/
protected static void cancelAnimationsRecursive(View view) {
- if (view.hasTransientState()) {
+ if (view != null && view.hasTransientState()) {
view.animate().cancel();
if (view instanceof ViewGroup) {
final int count = ((ViewGroup) view).getChildCount();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java b/v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
new file mode 100644
index 0000000..45aba9f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+import android.support.v17.leanback.R;
+
+/**
+ * <p>A {@link android.widget.TextView} that adjusts text size automatically in response
+ * to certain trigger conditions, such as text that wraps over multiple lines.</p>
+ * @hide
+ */
+class ResizingTextView extends TextView {
+
+ /**
+ * Trigger text resize when text flows into the last line of a multi-line text view.
+ */
+ public static final int TRIGGER_MAX_LINES = 0x01;
+
+ private int mTriggerConditions; // Union of trigger conditions
+ private int mResizedTextSize;
+ // Note: Maintaining line spacing turned out not to be useful, and will be removed in
+ // the next round of design for this class (b/18736630). For now it simply defaults to false.
+ private boolean mMaintainLineSpacing;
+ private int mResizedPaddingAdjustmentTop;
+ private int mResizedPaddingAdjustmentBottom;
+
+ private boolean mIsResized = false;
+ // Remember default properties in case we need to restore them
+ private boolean mDefaultsInitialized = false;
+ private int mDefaultTextSize;
+ private float mDefaultLineSpacingExtra;
+ private int mDefaultPaddingTop;
+ private int mDefaultPaddingBottom;
+
+ public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(ctx, attrs, defStyleAttr);
+ TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView,
+ defStyleAttr, defStyleRes);
+
+ try {
+ mTriggerConditions = a.getInt(
+ R.styleable.lbResizingTextView_resizeTrigger, TRIGGER_MAX_LINES);
+ mResizedTextSize = a.getDimensionPixelSize(
+ R.styleable.lbResizingTextView_resizedTextSize, -1);
+ mMaintainLineSpacing = a.getBoolean(
+ R.styleable.lbResizingTextView_maintainLineSpacing, false);
+ mResizedPaddingAdjustmentTop = a.getDimensionPixelOffset(
+ R.styleable.lbResizingTextView_resizedPaddingAdjustmentTop, 0);
+ mResizedPaddingAdjustmentBottom = a.getDimensionPixelOffset(
+ R.styleable.lbResizingTextView_resizedPaddingAdjustmentBottom, 0);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr) {
+ this(ctx, attrs, defStyleAttr, 0);
+ }
+
+ public ResizingTextView(Context ctx, AttributeSet attrs) {
+ // TODO We should define our own style that inherits from TextViewStyle, to set defaults
+ // for new styleables, We then pass the appropriate R.attr up the constructor chain here.
+ this(ctx, attrs, android.R.attr.textViewStyle);
+ }
+
+ public ResizingTextView(Context ctx) {
+ this(ctx, null);
+ }
+
+ /**
+ * @return the trigger conditions used to determine whether resize occurs
+ */
+ public int getTriggerConditions() {
+ return mTriggerConditions;
+ }
+
+ /**
+ * Set the trigger conditions used to determine whether resize occurs. Pass
+ * a union of trigger condition constants, such as {@link ResizingTextView#TRIGGER_MAX_LINES}.
+ *
+ * @param conditions A union of trigger condition constants
+ */
+ public void setTriggerConditions(int conditions) {
+ if (mTriggerConditions != conditions) {
+ mTriggerConditions = conditions;
+ // Always request a layout when trigger conditions change
+ requestLayout();
+ }
+ }
+
+ /**
+ * @return the resized text size
+ */
+ public int getResizedTextSize() {
+ return mResizedTextSize;
+ }
+
+ /**
+ * Set the text size for resized text.
+ *
+ * @param size The text size for resized text
+ */
+ public void setResizedTextSize(int size) {
+ if (mResizedTextSize != size) {
+ mResizedTextSize = size;
+ resizeParamsChanged();
+ }
+ }
+
+ /**
+ * @return whether or not to maintain line spacing when resizing text.
+ * The default is true.
+ */
+ public boolean getMaintainLineSpacing() {
+ return mMaintainLineSpacing;
+ }
+
+ /**
+ * Set whether or not to maintain line spacing when resizing text.
+ * The default is true.
+ *
+ * @param maintain Whether or not to maintain line spacing
+ */
+ public void setMaintainLineSpacing(boolean maintain) {
+ if (mMaintainLineSpacing != maintain) {
+ mMaintainLineSpacing = maintain;
+ resizeParamsChanged();
+ }
+ }
+
+ /**
+ * @return desired adjustment to top padding for resized text
+ */
+ public int getResizedPaddingAdjustmentTop() {
+ return mResizedPaddingAdjustmentTop;
+ }
+
+ /**
+ * Set the desired adjustment to top padding for resized text.
+ *
+ * @param adjustment The adjustment to top padding, in pixels
+ */
+ public void setResizedPaddingAdjustmentTop(int adjustment) {
+ if (mResizedPaddingAdjustmentTop != adjustment) {
+ mResizedPaddingAdjustmentTop = adjustment;
+ resizeParamsChanged();
+ }
+ }
+
+ /**
+ * @return desired adjustment to bottom padding for resized text
+ */
+ public int getResizedPaddingAdjustmentBottom() {
+ return mResizedPaddingAdjustmentBottom;
+ }
+
+ /**
+ * Set the desired adjustment to bottom padding for resized text.
+ *
+ * @param adjustment The adjustment to bottom padding, in pixels
+ */
+ public void setResizedPaddingAdjustmentBottom(int adjustment) {
+ if (mResizedPaddingAdjustmentBottom != adjustment) {
+ mResizedPaddingAdjustmentBottom = adjustment;
+ resizeParamsChanged();
+ }
+ }
+
+ private void resizeParamsChanged() {
+ // If we're not resized, then changing resize parameters doesn't
+ // affect layout, so don't bother requesting.
+ if (mIsResized) {
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!mDefaultsInitialized) {
+ mDefaultTextSize = (int) getTextSize();
+ mDefaultLineSpacingExtra = getLineSpacingExtra();
+ mDefaultPaddingTop = getPaddingTop();
+ mDefaultPaddingBottom = getPaddingBottom();
+ mDefaultsInitialized = true;
+ }
+
+ // Always try first to measure with defaults. Otherwise, we may think we can get away
+ // with larger text sizes later when we actually can't.
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
+ setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
+ setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ boolean resizeText = false;
+
+ final Layout layout = getLayout();
+ if (layout != null) {
+ if ((mTriggerConditions & TRIGGER_MAX_LINES) > 0) {
+ final int lineCount = layout.getLineCount();
+ final int maxLines = getMaxLines();
+ if (maxLines > 1) {
+ resizeText = lineCount == maxLines;
+ }
+ }
+ }
+
+ final int currentSizePx = (int) getTextSize();
+ boolean remeasure = false;
+ if (resizeText) {
+ if (mResizedTextSize != -1 && currentSizePx != mResizedTextSize) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mResizedTextSize);
+ remeasure = true;
+ }
+ // Check for other desired adjustments in addition to the text size
+ final float targetLineSpacingExtra = mDefaultLineSpacingExtra +
+ mDefaultTextSize - mResizedTextSize;
+ if (mMaintainLineSpacing && getLineSpacingExtra() != targetLineSpacingExtra) {
+ setLineSpacing(targetLineSpacingExtra, getLineSpacingMultiplier());
+ remeasure = true;
+ }
+ final int paddingTop = mDefaultPaddingTop + mResizedPaddingAdjustmentTop;
+ final int paddingBottom = mDefaultPaddingBottom + mResizedPaddingAdjustmentBottom;
+ if (getPaddingTop() != paddingTop || getPaddingBottom() != paddingBottom) {
+ setPaddingTopAndBottom(paddingTop, paddingBottom);
+ remeasure = true;
+ }
+ } else {
+ // Use default size, line spacing, and padding
+ if (mResizedTextSize != -1 && currentSizePx != mDefaultTextSize) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
+ remeasure = true;
+ }
+ if (mMaintainLineSpacing && getLineSpacingExtra() != mDefaultLineSpacingExtra) {
+ setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
+ remeasure = true;
+ }
+ if (getPaddingTop() != mDefaultPaddingTop ||
+ getPaddingBottom() != mDefaultPaddingBottom) {
+ setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
+ remeasure = true;
+ }
+ }
+ mIsResized = resizeText;
+ if (remeasure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private void setPaddingTopAndBottom(int paddingTop, int paddingBottom) {
+ if (isPaddingRelative()) {
+ setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), paddingBottom);
+ } else {
+ setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), paddingBottom);
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index 1c7ed3d..aa774db 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -200,6 +200,11 @@
*/
protected void initializeRowViewHolder(ViewHolder vh) {
vh.mInitialzed = true;
+ // set clip children to false for slide transition
+ ((ViewGroup) vh.view).setClipChildren(false);
+ if (vh.mContainerViewHolder != null) {
+ ((ViewGroup) vh.mContainerViewHolder.view).setClipChildren(false);
+ }
}
/**
@@ -490,8 +495,21 @@
/**
* Freeze/Unfreeze the row, typically used when transition starts/ends.
+ * This method is called by fragment, app should not call it directly.
*/
public void freeze(ViewHolder holder, boolean freeze) {
}
+ /**
+ * Change visibility of views, entrance transition will be run against the views that
+ * change visibilities. Subclass may override and begin with calling
+ * super.setEntranceTransitionState(). This method is called by fragment,
+ * app should not call it directly.
+ */
+ public void setEntranceTransitionState(ViewHolder holder, boolean afterTransition) {
+ if (holder.mHeaderViewHolder != null) {
+ holder.mHeaderViewHolder.view.setVisibility(afterTransition ?
+ View.VISIBLE : View.INVISIBLE);
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
new file mode 100644
index 0000000..362744e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Subclass of FrameLayout that support scale layout area size for children.
+ * @hide
+ */
+public class ScaleFrameLayout extends FrameLayout {
+
+ private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
+
+ private float mLayoutScaleX = 1f;
+ private float mLayoutScaleY = 1f;
+
+ public ScaleFrameLayout(Context context) {
+ this(context ,null);
+ }
+
+ public ScaleFrameLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ScaleFrameLayout(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setLayoutScaleX(float scaleX) {
+ if (scaleX != mLayoutScaleX) {
+ mLayoutScaleX = scaleX;
+ requestLayout();
+ }
+ }
+
+ public void setLayoutScaleY(float scaleY) {
+ if (scaleY != mLayoutScaleY) {
+ mLayoutScaleY = scaleY;
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int count = getChildCount();
+
+ final int parentLeft, parentRight;
+ final int layoutDirection = getLayoutDirection();
+ final float pivotX = (layoutDirection == View.LAYOUT_DIRECTION_RTL) ?
+ getWidth() - getPivotX() :
+ getPivotX();
+ if (mLayoutScaleX != 1f) {
+ parentLeft = getPaddingLeft() + (int)(pivotX - pivotX / mLayoutScaleX + 0.5f);
+ parentRight = (int)(pivotX + (right - left - pivotX) / mLayoutScaleX + 0.5f)
+ - getPaddingRight();
+ } else {
+ parentLeft = getPaddingLeft();
+ parentRight = right - left - getPaddingRight();
+ }
+
+ final int parentTop, parentBottom;
+ final float pivotY = getPivotY();
+ if (mLayoutScaleY != 1f) {
+ parentTop = getPaddingTop() + (int)(pivotY - pivotY / mLayoutScaleY + 0.5f);
+ parentBottom = (int)(pivotY + (bottom - top - pivotY) / mLayoutScaleY + 0.5f)
+ - getPaddingBottom();
+ } else {
+ parentTop = getPaddingTop();
+ parentBottom = bottom - top - getPaddingBottom();
+ }
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int width = child.getMeasuredWidth();
+ final int height = child.getMeasuredHeight();
+
+ int childLeft;
+ int childTop;
+
+ int gravity = lp.gravity;
+ if (gravity == -1) {
+ gravity = DEFAULT_CHILD_GRAVITY;
+ }
+
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
+ final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
+ lp.leftMargin - lp.rightMargin;
+ break;
+ case Gravity.RIGHT:
+ childLeft = parentRight - width - lp.rightMargin;
+ break;
+ case Gravity.LEFT:
+ default:
+ childLeft = parentLeft + lp.leftMargin;
+ }
+
+ switch (verticalGravity) {
+ case Gravity.TOP:
+ childTop = parentTop + lp.topMargin;
+ break;
+ case Gravity.CENTER_VERTICAL:
+ childTop = parentTop + (parentBottom - parentTop - height) / 2 +
+ lp.topMargin - lp.bottomMargin;
+ break;
+ case Gravity.BOTTOM:
+ childTop = parentBottom - height - lp.bottomMargin;
+ break;
+ default:
+ childTop = parentTop + lp.topMargin;
+ }
+
+ child.layout(childLeft, childTop, childLeft + width, childTop + height);
+ // synchronize child pivot to be same as ScaleFrameLayout's pivot
+ child.setPivotX(pivotX - childLeft);
+ child.setPivotY(pivotY - childTop);
+ }
+ }
+ }
+
+ private static int getScaledMeasureSpec(int measureSpec, float scale) {
+ return scale == 1f ? measureSpec : MeasureSpec.makeMeasureSpec(
+ (int) (MeasureSpec.getSize(measureSpec) / scale + 0.5f),
+ MeasureSpec.getMode(measureSpec));
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mLayoutScaleX != 1f || mLayoutScaleY != 1f) {
+ final int scaledWidthMeasureSpec =
+ getScaledMeasureSpec(widthMeasureSpec, mLayoutScaleX);
+ final int scaledHeightMeasureSpec =
+ getScaledMeasureSpec(heightMeasureSpec, mLayoutScaleY);
+ super.onMeasure(scaledWidthMeasureSpec, scaledHeightMeasureSpec);
+ setMeasuredDimension((int)(getMeasuredWidth() * mLayoutScaleX + 0.5f),
+ (int)(getMeasuredHeight() * mLayoutScaleY + 0.5f));
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ /**
+ * setForeground() is not supported, throws UnsupportedOperationException() when called.
+ */
+ @Override
+ public void setForeground(Drawable d) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
index 0d22284..c46ed5e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -107,6 +107,7 @@
private SpeechOrbView mSpeechOrbView;
private ImageView mBadgeView;
private String mSearchQuery;
+ private String mHint;
private String mTitle;
private Drawable mBadgeDrawable;
private final Handler mHandler = new Handler();
@@ -191,7 +192,7 @@
if (hasFocus) {
showNativeKeyboard();
}
- updateUi();
+ updateUi(hasFocus);
}
});
final Runnable mOnTextChangedRunnable = new Runnable() {
@@ -236,8 +237,9 @@
public boolean onEditorAction(TextView textView, int action, KeyEvent keyEvent) {
if (DEBUG) Log.v(TAG, "onEditorAction: " + action + " event: " + keyEvent);
boolean handled = true;
- if (EditorInfo.IME_ACTION_SEARCH == action && null != mSearchBarListener) {
- if (DEBUG) Log.v(TAG, "Action Pressed");
+ if ((EditorInfo.IME_ACTION_SEARCH == action ||
+ EditorInfo.IME_NULL == action) && null != mSearchBarListener) {
+ if (DEBUG) Log.v(TAG, "Action or enter pressed");
hideNativeKeyboard();
mHandler.postDelayed(new Runnable() {
@Override
@@ -298,11 +300,11 @@
} else {
stopRecognition();
}
- updateUi();
+ updateUi(hasFocus);
}
});
- updateUi();
+ updateUi(hasFocus());
updateHint();
}
@@ -371,7 +373,7 @@
* Returns the current search bar hint text.
*/
public CharSequence getHint() {
- return (mSearchTextEditor == null) ? null : mSearchTextEditor.getHint();
+ return mHint;
}
/**
@@ -468,8 +470,6 @@
* This will update the hint for the search bar properly depending on state and provided title
*/
private void updateHint() {
- if (null == mSearchTextEditor) return;
-
String title = getResources().getString(R.string.lb_search_bar_hint);
if (!TextUtils.isEmpty(mTitle)) {
if (isVoiceMode()) {
@@ -480,7 +480,10 @@
} else if (isVoiceMode()) {
title = getResources().getString(R.string.lb_search_bar_hint_speech);
}
- mSearchTextEditor.setHint(title);
+ mHint = title;
+ if (mSearchTextEditor != null) {
+ mSearchTextEditor.setHint(mHint);
+ }
}
private void toggleRecognition() {
@@ -499,6 +502,12 @@
mListening, mRecognizing));
if (!mRecognizing) return;
+
+ // Edit text content was cleared when starting recogition; ensure the content is restored
+ // in error cases
+ mSearchTextEditor.setText(mSearchQuery);
+ mSearchTextEditor.setHint(mHint);
+
mRecognizing = false;
if (mSpeechRecognitionCallback != null || null == mSpeechRecognizer) return;
@@ -528,6 +537,7 @@
}
if (mSpeechRecognitionCallback != null) {
mSearchTextEditor.setText("");
+ mSearchTextEditor.setHint("");
mSpeechRecognitionCallback.recognizeSpeech();
return;
}
@@ -673,14 +683,16 @@
mSpeechRecognizer.startListening(recognizerIntent);
}
- private void updateUi() {
- if (DEBUG) Log.v(TAG, String.format("Update UI %s %s",
- isVoiceMode() ? "Voice" : "Text",
- hasFocus() ? "Focused" : "Unfocused"));
- if (isVoiceMode()) {
+ private void updateUi(boolean hasFocus) {
+ if (hasFocus) {
mBarBackground.setAlpha(mBackgroundSpeechAlpha);
- mSearchTextEditor.setTextColor(mTextColorSpeechMode);
- mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode);
+ if (isVoiceMode()) {
+ mSearchTextEditor.setTextColor(mTextHintColorSpeechMode);
+ mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode);
+ } else {
+ mSearchTextEditor.setTextColor(mTextColorSpeechMode);
+ mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode);
+ }
} else {
mBarBackground.setAlpha(mBackgroundAlpha);
mSearchTextEditor.setTextColor(mTextColor);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java
new file mode 100644
index 0000000..1167746
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java
@@ -0,0 +1,126 @@
+package android.support.v17.leanback.widget;
+
+import android.util.SparseArray;
+
+/**
+ * An ObjectAdapter implemented with a {@link android.util.SparseArray}.
+ * This class maintains an array of objects where each object is associated
+ * with an integer key which determines its order relative to other objects.
+ */
+public class SparseArrayObjectAdapter extends ObjectAdapter {
+ private SparseArray<Object> mItems = new SparseArray<Object>();
+
+ /**
+ * Construct an adapter with the given {@link PresenterSelector}.
+ */
+ public SparseArrayObjectAdapter(PresenterSelector presenterSelector) {
+ super(presenterSelector);
+ }
+
+ /**
+ * Construct an adapter with the given {@link Presenter}.
+ */
+ public SparseArrayObjectAdapter(Presenter presenter) {
+ super(presenter);
+ }
+
+ /**
+ * Construct an adapter.
+ */
+ public SparseArrayObjectAdapter() {
+ super();
+ }
+
+ @Override
+ public int size() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object get(int position) {
+ return mItems.valueAt(position);
+ }
+
+ /**
+ * Returns the index for the given item in the adapter.
+ *
+ * @param item The item to find in the array.
+ * @return Index of the item, or a negative value if not found.
+ */
+ public int indexOf(Object item) {
+ return mItems.indexOfValue(item);
+ }
+
+ /**
+ * Returns the index for the given key in the adapter.
+ *
+ * @param key The key to find in the array.
+ * @return Index of the item, or a negative value if not found.
+ */
+ public int indexOf(int key) {
+ return mItems.indexOfKey(key);
+ }
+
+ /**
+ * Notify that the content of a range of items changed. Note that this is
+ * not same as items being added or removed.
+ *
+ * @param positionStart The position of first item that has changed.
+ * @param itemCount The count of how many items have changed.
+ */
+ public void notifyArrayItemRangeChanged(int positionStart, int itemCount) {
+ notifyItemRangeChanged(positionStart, itemCount);
+ }
+
+ /**
+ * Sets the item for the given key.
+ *
+ * @param key The key associated with the item.
+ * @param item The item associated with the key.
+ */
+ public void set(int key, Object item) {
+ int index = mItems.indexOfKey(key);
+ if (index >= 0) {
+ if (mItems.valueAt(index) != item) {
+ mItems.setValueAt(index, item);
+ notifyItemRangeChanged(index, 1);
+ }
+ } else {
+ mItems.append(key, item);
+ index = mItems.indexOfKey(key);
+ notifyItemRangeInserted(index, 1);
+ }
+ }
+
+ /**
+ * Clears the given key and associated item from the adapter.
+ *
+ * @param key The key to be cleared.
+ */
+ public void clear(int key) {
+ int index = mItems.indexOfKey(key);
+ if (index >= 0) {
+ mItems.removeAt(index);
+ notifyItemRangeRemoved(index, 1);
+ }
+ }
+
+ /**
+ * Removes all items from this adapter, leaving it empty.
+ */
+ public void clear() {
+ final int itemCount = mItems.size();
+ if (itemCount == 0) {
+ return;
+ }
+ mItems.clear();
+ notifyItemRangeRemoved(0, itemCount);
+ }
+
+ /**
+ * Returns the object for the given key, or null if no mapping for that key exists.
+ */
+ public Object lookup(int key) {
+ return mItems.get(key);
+ }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
index 5404418..4b5abe8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -97,6 +97,7 @@
protected Row[] mRows;
protected CircularArray<Location> mLocations = new CircularArray<Location>(64);
private ArrayList<Integer>[] mTmpItemPositionsInRows;
+ protected boolean mReversedFlow;
/**
* A constant representing a default starting index, indicating that the
@@ -111,6 +112,10 @@
protected int mFirstIndex = -1;
+ public void setReversedFlow(boolean reversedFlow) {
+ mReversedFlow = reversedFlow;
+ }
+
/**
* Sets the {@link Provider} for this staggered grid.
*
@@ -246,7 +251,7 @@
/**
* Append items until the high edge reaches upTo.
*/
- public abstract void appendItems(int upTo);
+ public abstract void appendItems(int toLimit);
protected final int getMaxLowRowIndex() {
int maxLowRowIndex = 0;
@@ -296,7 +301,7 @@
/**
* Prepend items until the low edge reaches downTo.
*/
- public abstract void prependItems(int downTo);
+ public abstract void prependItems(int toLimit);
/**
* Strip items, keep a contiguous subset of items; the subset should include
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
index 9f2a06c..a8b855a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
@@ -22,7 +22,7 @@
final class StaggeredGridDefault extends StaggeredGrid {
@Override
- public void appendItems(int upTo) {
+ public void appendItems(int toLimit) {
int count = mProvider.getCount();
int itemIndex;
int rowIndex;
@@ -36,9 +36,13 @@
top_loop:
while (true) {
- // find highest row (.high is biggest)
- int maxHighRowIndex = mLocations.size() > 0 ? getMaxHighRowIndex() : -1;
- int maxHigh = maxHighRowIndex != -1 ? mRows[maxHighRowIndex].high : Integer.MIN_VALUE;
+ // find endmost row edge (.high is biggest, or .low is smallest in reversed flow)
+ int edgeRowIndex = mReversedFlow ?
+ (mLocations.size() > 0 ? getMinLowRowIndex() : -1) :
+ (mLocations.size() > 0 ? getMaxHighRowIndex() : -1);
+ int edge = mReversedFlow ?
+ (edgeRowIndex != -1 ? mRows[edgeRowIndex].low : Integer.MAX_VALUE) :
+ (edgeRowIndex != -1 ? mRows[edgeRowIndex].high : Integer.MIN_VALUE);
// fill from current row till last row so that each row will grow longer than
// the previous highest row.
for (; rowIndex < mNumRows; rowIndex++) {
@@ -49,11 +53,15 @@
appendItemToRow(itemIndex++, rowIndex);
// fill more item to the row to make sure this row is longer than
// the previous highest row.
- if (maxHighRowIndex == -1) {
- maxHighRowIndex = getMaxHighRowIndex();
- maxHigh = mRows[maxHighRowIndex].high;
- } else if (rowIndex != maxHighRowIndex) {
- while (mRows[rowIndex].high < maxHigh) {
+ if (edgeRowIndex == -1) {
+ edgeRowIndex = mReversedFlow ? getMinLowRowIndex() : getMaxHighRowIndex();
+ edge = mReversedFlow ?
+ mRows[edgeRowIndex].low :
+ mRows[edgeRowIndex].high;
+ } else if (rowIndex != edgeRowIndex) {
+ while (mReversedFlow ?
+ mRows[rowIndex].low > edge :
+ mRows[rowIndex].high < edge) {
if (itemIndex == count) {
break top_loop;
}
@@ -61,7 +69,9 @@
}
}
}
- if (mRows[getMinHighRowIndex()].high >= upTo) {
+ if (mReversedFlow ?
+ mRows[getMaxLowRowIndex()].low <= toLimit :
+ mRows[getMinHighRowIndex()].high >= toLimit) {
break;
}
// start fill from row 0 again
@@ -70,7 +80,7 @@
}
@Override
- public void prependItems(int downTo) {
+ public void prependItems(int toLimit) {
if (mProvider.getCount() <= 0) return;
int itemIndex;
int rowIndex;
@@ -89,18 +99,27 @@
top_loop:
while (true) {
- int minLowRowIndex = mLocations.size() > 0 ? getMinLowRowIndex() : -1;
- int minLow = minLowRowIndex != -1 ? mRows[minLowRowIndex].low : Integer.MAX_VALUE;
+ // find startmost row edge (.low is smallest, or .high is biggest in reversed flow)
+ int edgeRowIndex = mReversedFlow ?
+ (mLocations.size() > 0 ? getMaxHighRowIndex() : -1) :
+ (mLocations.size() > 0 ? getMinLowRowIndex() : -1);
+ int edge = mReversedFlow ?
+ (edgeRowIndex != -1 ? mRows[edgeRowIndex].high : Integer.MIN_VALUE) :
+ (edgeRowIndex != -1 ? mRows[edgeRowIndex].low : Integer.MAX_VALUE);
for (; rowIndex >=0 ; rowIndex--) {
if (itemIndex < 0) {
break top_loop;
}
prependItemToRow(itemIndex--, rowIndex);
- if (minLowRowIndex == -1) {
- minLowRowIndex = getMinLowRowIndex();
- minLow = mRows[minLowRowIndex].low;
- } else if (rowIndex != minLowRowIndex) {
- while (mRows[rowIndex].low > minLow) {
+ if (edgeRowIndex == -1) {
+ edgeRowIndex = mReversedFlow ? getMaxHighRowIndex() : getMinLowRowIndex();
+ edge = mReversedFlow ?
+ mRows[edgeRowIndex].high :
+ mRows[edgeRowIndex].low;
+ } else if (rowIndex != edgeRowIndex) {
+ while (mReversedFlow ?
+ mRows[rowIndex].high < edge :
+ mRows[rowIndex].low > edge) {
if (itemIndex < 0) {
break top_loop;
}
@@ -108,7 +127,9 @@
}
}
}
- if (mRows[getMaxLowRowIndex()].low <= downTo) {
+ if (mReversedFlow ?
+ mRows[getMinHighRowIndex()].high >= toLimit :
+ mRows[getMaxLowRowIndex()].low <= toLimit) {
break;
}
rowIndex = mNumRows - 1;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
index 1c61fa0..9d73470 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
@@ -118,4 +118,11 @@
public SearchOrbView.Colors getSearchAffordanceColors() {
return mSearchOrbView.getOrbColors();
}
+
+ /**
+ * Enables or disables any view animations.
+ */
+ public void enableAnimation(boolean enable) {
+ mSearchOrbView.enableOrbColorAnimation(enable && mSearchOrbView.hasFocus());
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
index 79a2c1a..ad714e0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -21,6 +21,8 @@
import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import android.view.View;
+
/**
* Maintains Window Alignment information of two axis.
*/
@@ -65,6 +67,8 @@
private int mPaddingHigh;
+ private boolean mReversedFlow;
+
private String mName; // for debugging
public Axis(String name) {
@@ -194,19 +198,30 @@
return mSize - mPaddingLow - mPaddingHigh;
}
- final public int getSystemScrollPos(boolean isFirst, boolean isLast) {
- return getSystemScrollPos((int) mScrollCenter, isFirst, isLast);
+ final public int getSystemScrollPos(boolean isAtMin, boolean isAtMax) {
+ return getSystemScrollPos((int) mScrollCenter, isAtMin, isAtMax);
}
- final public int getSystemScrollPos(int scrollCenter, boolean isFirst, boolean isLast) {
+ final public int getSystemScrollPos(int scrollCenter, boolean isAtMin, boolean isAtMax) {
int middlePosition;
- if (mWindowAlignmentOffset >= 0) {
- middlePosition = mWindowAlignmentOffset - mPaddingLow;
+ if (!mReversedFlow) {
+ if (mWindowAlignmentOffset >= 0) {
+ middlePosition = mWindowAlignmentOffset - mPaddingLow;
+ } else {
+ middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
+ }
+ if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+ middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+ }
} else {
- middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
- }
- if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
- middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+ if (mWindowAlignmentOffset >= 0) {
+ middlePosition = mSize - mWindowAlignmentOffset - mPaddingLow;
+ } else {
+ middlePosition = - mWindowAlignmentOffset - mPaddingLow;
+ }
+ if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+ middlePosition -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+ }
}
int clientSize = getClientSize();
int afterMiddlePosition = clientSize - middlePosition;
@@ -216,30 +231,37 @@
(mWindowAlignment & WINDOW_ALIGN_BOTH_EDGE) == WINDOW_ALIGN_BOTH_EDGE) {
if (mMaxEdge - mMinEdge <= clientSize) {
// total children size is less than view port and we want to align
- // both edge: align first child to left edge of view port
- return mMinEdge - mPaddingLow;
+ // both edge: align first child to start edge of view port
+ return mReversedFlow ? mMaxEdge - mPaddingLow - clientSize
+ : mMinEdge - mPaddingLow;
}
}
if (!isMinUnknown) {
- if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 &&
- (isFirst || scrollCenter - mMinEdge <= middlePosition)) {
- // scroll center is within half of view port size: align the left edge
- // of first child to the left edge of view port
+ if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
+ : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
+ && (isAtMin || scrollCenter - mMinEdge <= middlePosition)) {
+ // scroll center is within half of view port size: align the start edge
+ // of first child to the start edge of view port
return mMinEdge - mPaddingLow;
}
}
if (!isMaxUnknown) {
- if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 &&
- (isLast || mMaxEdge - scrollCenter <= afterMiddlePosition)) {
- // scroll center is very close to the right edge of view port : align the
- // right edge of last children (plus expanded size) to view port's right
- return mMaxEdge -mPaddingLow - (clientSize);
+ if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
+ : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
+ && (isAtMax || mMaxEdge - scrollCenter <= afterMiddlePosition)) {
+ // scroll center is very close to the end edge of view port : align the
+ // end edge of last children (plus expanded size) to view port's end
+ return mMaxEdge - mPaddingLow - clientSize;
}
}
// else put scroll center in middle of view port
return scrollCenter - middlePosition - mPaddingLow;
}
+ final public void setReversedFlow(boolean reversedFlow) {
+ mReversedFlow = reversedFlow;
+ }
+
@Override
public String toString() {
return "center: " + mScrollCenter + " min:" + mMinEdge +
@@ -289,7 +311,7 @@
public String toString() {
return new StringBuffer().append("horizontal=")
.append(horizontal.toString())
- .append("vertical=")
+ .append("; vertical=")
.append(vertical.toString())
.toString();
}
diff --git a/v4/Android.mk b/v4/Android.mk
index dbebab4..855b4f2 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -19,6 +19,7 @@
LOCAL_MODULE := android-support-v4-donut
LOCAL_SDK_VERSION := 4
LOCAL_SRC_FILES := $(call all-java-files-under, donut)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations
include $(BUILD_STATIC_JAVA_LIBRARY)
# -----------------------------------------------------------------------
@@ -153,7 +154,7 @@
# -----------------------------------------------------------------------
-# A helper sub-library that makes direct use of the upcoming API.
+# A helper sub-library that makes direct use of V20 APIs.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v4-api20
LOCAL_SDK_VERSION := 20
@@ -163,25 +164,36 @@
# -----------------------------------------------------------------------
-# A helper sub-library that makes direct use of the upcoming API.
-# TODO: Apply a real name and SDK version when available
+# A helper sub-library that makes direct use of Lollipop APIs.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v4-api21
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, api21)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api20
include $(BUILD_STATIC_JAVA_LIBRARY)
# -----------------------------------------------------------------------
+# A helper sub-library that makes direct use of V22 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api22
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api22)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api21
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
# Here is the final static library that apps can link against.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v4
LOCAL_SDK_VERSION := 4
+LOCAL_AIDL_INCLUDES := frameworks/support/v4/java
+
LOCAL_SRC_FILES := $(call all-java-files-under, java) \
$(call all-Iaidl-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api21
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-annotations
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api22
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
index e6025e1..ef41045 100644
--- a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -29,6 +29,7 @@
import android.view.ViewTreeObserver;
import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
class FragmentTransitionCompat21 {
@@ -44,7 +45,7 @@
}
public static Object captureExitingViews(Object exitTransition, View root,
- ArrayList<View> viewList, Map<String, View> namedViews) {
+ ArrayList<View> viewList, Map<String, View> namedViews, View nonExistentView) {
if (exitTransition != null) {
captureTransitioningViews(viewList, root);
if (namedViews != null) {
@@ -53,6 +54,7 @@
if (viewList.isEmpty()) {
exitTransition = null;
} else {
+ viewList.add(nonExistentView);
addTargets((Transition) exitTransition, viewList);
}
}
@@ -130,6 +132,8 @@
if (enterTransition != null) {
captureTransitioningViews(enteringViews, fragmentView);
enteringViews.removeAll(renamedViews.values());
+ enteringViews.add(nonExistentView);
+ enterTransition.removeTarget(nonExistentView);
addTargets(enterTransition, enteringViews);
}
}
@@ -304,22 +308,71 @@
}
}
+ /**
+ * This method removes the views from transitions that target ONLY those views.
+ * The views list should match those added in addTargets and should contain
+ * one view that is not in the view hierarchy (state.nonExistentView).
+ */
public static void removeTargets(Object transitionObject, ArrayList<View> views) {
Transition transition = (Transition) transitionObject;
- int numViews = views.size();
- for (int i = 0; i < numViews; i++) {
- transition.removeTarget(views.get(i));
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ removeTargets(child, views);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (targets != null && targets.size() == views.size() &&
+ targets.containsAll(views)) {
+ // We have an exact match. We must have added these earlier in addTargets
+ for (int i = views.size() - 1; i >= 0; i--) {
+ transition.removeTarget(views.get(i));
+ }
+ }
}
}
+ /**
+ * This method adds views as targets to the transition, but only if the transition
+ * doesn't already have a target. It is best for views to contain one View object
+ * that does not exist in the view hierarchy (state.nonExistentView) so that
+ * when they are removed later, a list match will suffice to remove the targets.
+ * Otherwise, if you happened to have targeted the exact views for the transition,
+ * the removeTargets call will remove them unexpectedly.
+ */
public static void addTargets(Object transitionObject, ArrayList<View> views) {
Transition transition = (Transition) transitionObject;
- int numViews = views.size();
- for (int i = 0; i < numViews; i++) {
- transition.addTarget(views.get(i));
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ addTargets(child, views);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (isNullOrEmpty(targets)) {
+ // We can just add the target views
+ int numViews = views.size();
+ for (int i = 0; i < numViews; i++) {
+ transition.addTarget(views.get(i));
+ }
+ }
}
}
+ private static boolean hasSimpleTarget(Transition transition) {
+ return !isNullOrEmpty(transition.getTargetIds()) ||
+ !isNullOrEmpty(transition.getTargetNames()) ||
+ !isNullOrEmpty(transition.getTargetTypes());
+ }
+
+ private static boolean isNullOrEmpty(List list) {
+ return list == null || list.isEmpty();
+ }
+
public interface ViewRetriever {
View getView();
}
diff --git a/v4/api21/android/support/v4/app/NotificationCompatApi21.java b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
index 94f6776..a16b2a2 100644
--- a/v4/api21/android/support/v4/app/NotificationCompatApi21.java
+++ b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
+import android.os.Parcelable;
import android.widget.RemoteViews;
import java.util.ArrayList;
@@ -42,6 +43,15 @@
public static final String CATEGORY_RECOMMENDATION = Notification.CATEGORY_RECOMMENDATION;
public static final String CATEGORY_STATUS = Notification.CATEGORY_STATUS;
+ private static final String KEY_AUTHOR = "author";
+ private static final String KEY_TEXT = "text";
+ private static final String KEY_MESSAGES = "messages";
+ private static final String KEY_REMOTE_INPUT = "remote_input";
+ private static final String KEY_ON_REPLY = "on_reply";
+ private static final String KEY_ON_READ = "on_read";
+ private static final String KEY_PARTICIPANTS = "participants";
+ private static final String KEY_TIMESTAMP = "timestamp";
+
public static class Builder implements NotificationBuilderWithBuilderAccessor,
NotificationBuilderWithActions {
private Notification.Builder b;
@@ -113,4 +123,100 @@
public static String getCategory(Notification notif) {
return notif.category;
}
+
+ static Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc) {
+ if (uc == null) {
+ return null;
+ }
+ Bundle b = new Bundle();
+ String author = null;
+ if (uc.getParticipants() != null && uc.getParticipants().length > 1) {
+ author = uc.getParticipants()[0];
+ }
+ Parcelable[] messages = new Parcelable[uc.getMessages().length];
+ for (int i = 0; i < messages.length; i++) {
+ Bundle m = new Bundle();
+ m.putString(KEY_TEXT, uc.getMessages()[i]);
+ m.putString(KEY_AUTHOR, author);
+ messages[i] = m;
+ }
+ b.putParcelableArray(KEY_MESSAGES, messages);
+ RemoteInputCompatBase.RemoteInput remoteInput = uc.getRemoteInput();
+ if (remoteInput != null) {
+ b.putParcelable(KEY_REMOTE_INPUT, fromCompatRemoteInput(remoteInput));
+ }
+ b.putParcelable(KEY_ON_REPLY, uc.getReplyPendingIntent());
+ b.putParcelable(KEY_ON_READ, uc.getReadPendingIntent());
+ b.putStringArray(KEY_PARTICIPANTS, uc.getParticipants());
+ b.putLong(KEY_TIMESTAMP, uc.getLatestTimestamp());
+ return b;
+ }
+
+ static NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+ Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+ RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+ if (b == null) {
+ return null;
+ }
+ Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
+ String[] messages = null;
+ if (parcelableMessages != null) {
+ String[] tmp = new String[parcelableMessages.length];
+ boolean success = true;
+ for (int i = 0; i < tmp.length; i++) {
+ if (!(parcelableMessages[i] instanceof Bundle)) {
+ success = false;
+ break;
+ }
+ tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
+ if (tmp[i] == null) {
+ success = false;
+ break;
+ }
+ }
+ if (success) {
+ messages = tmp;
+ } else {
+ return null;
+ }
+ }
+
+ PendingIntent onRead = b.getParcelable(KEY_ON_READ);
+ PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
+
+ android.app.RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
+
+ String[] participants = b.getStringArray(KEY_PARTICIPANTS);
+ if (participants == null || participants.length != 1) {
+ return null;
+ }
+
+
+ return factory.build(
+ messages,
+ remoteInput != null ? toCompatRemoteInput(remoteInput, remoteInputFactory) : null,
+ onReply,
+ onRead,
+ participants, b.getLong(KEY_TIMESTAMP));
+ }
+
+ private static android.app.RemoteInput fromCompatRemoteInput(
+ RemoteInputCompatBase.RemoteInput src) {
+ return new android.app.RemoteInput.Builder(src.getResultKey())
+ .setLabel(src.getLabel())
+ .setChoices(src.getChoices())
+ .setAllowFreeFormInput(src.getAllowFreeFormInput())
+ .addExtras(src.getExtras())
+ .build();
+ }
+
+ private static RemoteInputCompatBase.RemoteInput toCompatRemoteInput(
+ android.app.RemoteInput remoteInput,
+ RemoteInputCompatBase.RemoteInput.Factory factory) {
+ return factory.build(remoteInput.getResultKey(),
+ remoteInput.getLabel(),
+ remoteInput.getChoices(),
+ remoteInput.getAllowFreeFormInput(),
+ remoteInput.getExtras());
+ }
}
diff --git a/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
index 645b633..2272c02 100644
--- a/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
+++ b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
@@ -17,11 +17,18 @@
package android.support.v4.content.res;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
class ResourcesCompatApi21 {
- public static Drawable getDrawable(Resources res, int id, Theme theme) {
+ public static Drawable getDrawable(Resources res, int id, Theme theme)
+ throws NotFoundException {
return res.getDrawable(id, theme);
}
+
+ public static Drawable getDrawableForDensity(Resources res, int id, int density, Theme theme)
+ throws NotFoundException {
+ return res.getDrawableForDensity(id, density, theme);
+ }
}
diff --git a/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java b/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
new file mode 100644
index 0000000..991515a
--- /dev/null
+++ b/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media;
+
+import android.graphics.Bitmap;
+import android.media.MediaDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+
+public class MediaDescriptionCompatApi21 {
+
+ public static String getMediaId(Object descriptionObj) {
+ return ((MediaDescription) descriptionObj).getMediaId();
+ }
+
+ public static CharSequence getTitle(Object descriptionObj) {
+ return ((MediaDescription) descriptionObj).getTitle();
+ }
+
+ public static CharSequence getSubtitle(Object descriptionObj) {
+ return ((MediaDescription) descriptionObj).getSubtitle();
+ }
+
+ public static CharSequence getDescription(Object descriptionObj) {
+ return ((MediaDescription) descriptionObj).getDescription();
+ }
+
+ public static Bitmap getIconBitmap(Object descriptionObj) {
+ return ((MediaDescription) descriptionObj).getIconBitmap();
+ }
+
+ public static Uri getIconUri(Object descriptionObj) {
+ return ((MediaDescription) descriptionObj).getIconUri();
+ }
+
+ public static Bundle getExtras(Object descriptionObj) {
+ return ((MediaDescription) descriptionObj).getExtras();
+ }
+
+ public static void writeToParcel(Object descriptionObj, Parcel dest, int flags) {
+ ((MediaDescription) descriptionObj).writeToParcel(dest, flags);
+ }
+
+ public static Object fromParcel(Parcel in) {
+ return MediaDescription.CREATOR.createFromParcel(in);
+ }
+
+ public static class Builder {
+ public static Object newInstance() {
+ return new MediaDescription.Builder();
+ }
+
+
+ public static void setMediaId(Object builderObj, String mediaId) {
+ ((MediaDescription.Builder)builderObj).setMediaId(mediaId);
+ }
+
+ public static void setTitle(Object builderObj, CharSequence title) {
+ ((MediaDescription.Builder)builderObj).setTitle(title);
+ }
+
+ public static void setSubtitle(Object builderObj, CharSequence subtitle) {
+ ((MediaDescription.Builder)builderObj).setSubtitle(subtitle);
+ }
+
+ public static void setDescription(Object builderObj, CharSequence description) {
+ ((MediaDescription.Builder)builderObj).setDescription(description);
+ }
+
+ public static void setIconBitmap(Object builderObj, Bitmap iconBitmap) {
+ ((MediaDescription.Builder)builderObj).setIconBitmap(iconBitmap);
+ }
+
+ public static void setIconUri(Object builderObj, Uri iconUri) {
+ ((MediaDescription.Builder)builderObj).setIconUri(iconUri);
+ }
+
+ public static void setExtras(Object builderObj, Bundle extras) {
+ ((MediaDescription.Builder)builderObj).setExtras(extras);
+ }
+
+ public static Object build(Object builderObj) {
+ return ((MediaDescription.Builder) builderObj).build();
+ }
+ }
+}
diff --git a/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
index cf7dde5..6acf425 100644
--- a/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
+++ b/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
@@ -16,6 +16,7 @@
package android.support.v4.media.session;
+import android.app.PendingIntent;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -29,6 +30,9 @@
import android.os.ResultReceiver;
import android.view.KeyEvent;
+import java.util.ArrayList;
+import java.util.List;
+
class MediaControllerCompatApi21 {
public static Object fromToken(Context context, Object sessionToken) {
return new MediaController(context, (MediaSession.Token) sessionToken);
@@ -60,21 +64,58 @@
return ((MediaController)controllerObj).getMetadata();
}
+ public static List<Object> getQueue(Object controllerObj) {
+ List<MediaSession.QueueItem> queue = ((MediaController) controllerObj).getQueue();
+ if (queue == null) {
+ return null;
+ }
+ List<Object> queueObjs = new ArrayList<Object>(queue);
+ return queueObjs;
+ }
+
+ public static CharSequence getQueueTitle(Object controllerObj) {
+ return ((MediaController) controllerObj).getQueueTitle();
+ }
+
+ public static Bundle getExtras(Object controllerObj) {
+ return ((MediaController) controllerObj).getExtras();
+ }
+
public static int getRatingType(Object controllerObj) {
- return ((MediaController)controllerObj).getRatingType();
+ return ((MediaController) controllerObj).getRatingType();
+ }
+
+ public static long getFlags(Object controllerObj) {
+ return ((MediaController) controllerObj).getFlags();
}
public static Object getPlaybackInfo(Object controllerObj) {
- return ((MediaController)controllerObj).getPlaybackInfo();
+ return ((MediaController) controllerObj).getPlaybackInfo();
+ }
+
+ public static PendingIntent getSessionActivity(Object controllerObj) {
+ return ((MediaController) controllerObj).getSessionActivity();
}
public static boolean dispatchMediaButtonEvent(Object controllerObj, KeyEvent event) {
- return ((MediaController)controllerObj).dispatchMediaButtonEvent(event);
+ return ((MediaController) controllerObj).dispatchMediaButtonEvent(event);
+ }
+
+ public static void setVolumeTo(Object controllerObj, int value, int flags) {
+ ((MediaController) controllerObj).setVolumeTo(value, flags);
+ }
+
+ public static void adjustVolume(Object controllerObj, int direction, int flags) {
+ ((MediaController) controllerObj).adjustVolume(direction, flags);
}
public static void sendCommand(Object controllerObj,
String command, Bundle params, ResultReceiver cb) {
- ((MediaController)controllerObj).sendCommand(command, params, cb);
+ ((MediaController) controllerObj).sendCommand(command, params, cb);
+ }
+
+ public static String getPackageName(Object controllerObj) {
+ return ((MediaController) controllerObj).getPackageName();
}
public static class TransportControls {
@@ -113,6 +154,22 @@
public static void setRating(Object controlsObj, Object ratingObj) {
((MediaController.TransportControls)controlsObj).setRating((Rating)ratingObj);
}
+
+ public static void playFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
+ ((MediaController.TransportControls) controlsObj).playFromMediaId(mediaId, extras);
+ }
+
+ public static void playFromSearch(Object controlsObj, String query, Bundle extras) {
+ ((MediaController.TransportControls) controlsObj).playFromSearch(query, extras);
+ }
+
+ public static void skipToQueueItem(Object controlsObj, long id) {
+ ((MediaController.TransportControls) controlsObj).skipToQueueItem(id);
+ }
+
+ public static void sendCustomAction(Object controlsObj, String action, Bundle args) {
+ ((MediaController.TransportControls) controlsObj).sendCustomAction(action, args);
+ }
}
public static class PlaybackInfo {
diff --git a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
index 4977cba..9b07ea0 100644
--- a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
+++ b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
@@ -16,9 +16,11 @@
package android.support.v4.media.session;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
+import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
@@ -29,6 +31,9 @@
import android.os.Parcelable;
import android.os.ResultReceiver;
+import java.util.ArrayList;
+import java.util.List;
+
class MediaSessionCompatApi21 {
public static Object createSession(Context context, String tag) {
return new MediaSession(context, tag);
@@ -41,6 +46,13 @@
throw new IllegalArgumentException("mediaSession is not a valid MediaSession object");
}
+ public static Object verifyToken(Object token) {
+ if (token instanceof MediaSession.Token) {
+ return token;
+ }
+ throw new IllegalArgumentException("token is not a valid MediaSession.Token object");
+ }
+
public static Object createCallback(Callback callback) {
return new CallbackProxy<Callback>(callback);
}
@@ -92,10 +104,41 @@
((MediaSession)sessionObj).setMetadata((MediaMetadata)metadataObj);
}
+ public static void setSessionActivity(Object sessionObj, PendingIntent pi) {
+ ((MediaSession) sessionObj).setSessionActivity(pi);
+ }
+
+ public static void setMediaButtonReceiver(Object sessionObj, PendingIntent pi) {
+ ((MediaSession) sessionObj).setMediaButtonReceiver(pi);
+ }
+
+ public static void setQueue(Object sessionObj, List<Object> queueObjs) {
+ if (queueObjs == null) {
+ ((MediaSession) sessionObj).setQueue(null);
+ return;
+ }
+ ArrayList<MediaSession.QueueItem> queue = new ArrayList<MediaSession.QueueItem>();
+ for (Object itemObj : queueObjs) {
+ queue.add((MediaSession.QueueItem) itemObj);
+ }
+ ((MediaSession) sessionObj).setQueue(queue);
+ }
+
+ public static void setQueueTitle(Object sessionObj, CharSequence title) {
+ ((MediaSession) sessionObj).setQueueTitle(title);
+ }
+
+ public static void setExtras(Object sessionObj, Bundle extras) {
+ ((MediaSession) sessionObj).setExtras(extras);
+ }
+
public static interface Callback {
public void onCommand(String command, Bundle extras, ResultReceiver cb);
public boolean onMediaButtonEvent(Intent mediaButtonIntent);
public void onPlay();
+ public void onPlayFromMediaId(String mediaId, Bundle extras);
+ public void onPlayFromSearch(String search, Bundle extras);
+ public void onSkipToQueueItem(long id);
public void onPause();
public void onSkipToNext();
public void onSkipToPrevious();
@@ -104,6 +147,7 @@
public void onStop();
public void onSeekTo(long pos);
public void onSetRating(Object ratingObj);
+ public void onCustomAction(String action, Bundle extras);
}
static class CallbackProxy<T extends Callback> extends MediaSession.Callback {
@@ -168,4 +212,19 @@
mCallback.onSetRating(rating);
}
}
+
+ static class QueueItem {
+
+ public static Object createItem(Object mediaDescription, long id) {
+ return new MediaSession.QueueItem((MediaDescription) mediaDescription, id);
+ }
+
+ public static Object getDescription(Object queueItem) {
+ return ((MediaSession.QueueItem) queueItem).getDescription();
+ }
+
+ public static long getQueueId(Object queueItem) {
+ return ((MediaSession.QueueItem) queueItem).getQueueId();
+ }
+ }
}
diff --git a/v4/api21/android/support/v4/view/ViewCompatApi21.java b/v4/api21/android/support/v4/view/ViewCompatApi21.java
index ad343b7..6d00e0a 100644
--- a/v4/api21/android/support/v4/view/ViewCompatApi21.java
+++ b/v4/api21/android/support/v4/view/ViewCompatApi21.java
@@ -63,4 +63,8 @@
}
});
}
+
+ public static boolean isImportantForAccessibility(View view) {
+ return view.isImportantForAccessibility();
+ }
}
diff --git a/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
index df4d299..0ae3a5c 100644
--- a/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
+++ b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
@@ -30,10 +30,8 @@
return (List<Object>) result;
}
- static void addAction(Object info, int id, CharSequence label) {
- AccessibilityNodeInfo.AccessibilityAction aa =
- new AccessibilityNodeInfo.AccessibilityAction(id, label);
- ((AccessibilityNodeInfo) info).addAction(aa);
+ static void addAction(Object info, Object action) {
+ ((AccessibilityNodeInfo) info).addAction((AccessibilityAction) action);
}
public static Object obtainCollectionInfo(int rowCount, int columnCount,
@@ -54,13 +52,15 @@
}
}
- static class AccessibilityAction {
- static int getId(Object action) {
- return ((AccessibilityNodeInfo.AccessibilityAction) action).getId();
- }
+ static Object newAccessibilityAction(int actionId, CharSequence label) {
+ return new AccessibilityAction(actionId, label);
+ }
- static CharSequence getLabel(Object action) {
- return ((AccessibilityNodeInfo.AccessibilityAction) action).getLabel();
- }
+ static int getAccessibilityActionId(Object action) {
+ return ((AccessibilityNodeInfo.AccessibilityAction) action).getId();
+ }
+
+ static CharSequence getAccessibilityActionLabel(Object action) {
+ return ((AccessibilityNodeInfo.AccessibilityAction) action).getLabel();
}
}
diff --git a/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java b/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java
index 12e9555..07cc3fa 100644
--- a/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java
+++ b/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java
@@ -17,6 +17,9 @@
package android.support.v4.widget;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -26,6 +29,11 @@
* Provides functionality for DrawerLayout unique to API 21
*/
class DrawerLayoutCompatApi21 {
+
+ private static final int[] THEME_ATTRS = {
+ android.R.attr.colorPrimaryDark
+ };
+
public static void configureApplyInsets(View drawerLayout) {
if (drawerLayout instanceof DrawerLayoutImpl) {
drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener());
@@ -66,6 +74,15 @@
return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0;
}
+ public static Drawable getDefaultStatusBarBackground(Context context) {
+ final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS);
+ try {
+ return a.getDrawable(0);
+ } finally {
+ a.recycle();
+ }
+ }
+
static class InsetsListener implements View.OnApplyWindowInsetsListener {
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
diff --git a/v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java b/v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
new file mode 100644
index 0000000..b847778
--- /dev/null
+++ b/v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.media.session.MediaSession;
+
+class MediaSessionCompatApi22 {
+
+ public static void setRatingType(Object sessionObj, int type) {
+ ((MediaSession) sessionObj).setRatingType(type);
+ }
+}
diff --git a/v4/api22/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi22.java b/v4/api22/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi22.java
new file mode 100644
index 0000000..786318d
--- /dev/null
+++ b/v4/api22/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi22.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.View;
+
+/**
+ * Api22-specific AccessibilityNodeInfo API implementation.
+ */
+class AccessibilityNodeInfoCompatApi22 {
+
+ public static Object getTraversalBefore(Object info) {
+ return ((AccessibilityNodeInfo) info).getTraversalBefore();
+ }
+
+ public static void setTraversalBefore(Object info, View view) {
+ ((AccessibilityNodeInfo) info).setTraversalBefore(view);
+ }
+
+ public static void setTraversalBefore(Object info, View root, int virtualDescendantId) {
+ ((AccessibilityNodeInfo) info).setTraversalBefore(root, virtualDescendantId);
+ }
+
+ public static Object getTraversalAfter(Object info) {
+ return ((AccessibilityNodeInfo) info).getTraversalAfter();
+ }
+
+ public static void setTraversalAfter(Object info, View view) {
+ ((AccessibilityNodeInfo) info).setTraversalAfter(view);
+ }
+
+ public static void setTraversalAfter(Object info, View root, int virtualDescendantId) {
+ ((AccessibilityNodeInfo) info).setTraversalAfter(root, virtualDescendantId);
+ }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index fed5c24..1cf63e8 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -27,8 +27,9 @@
def jbMr1SS = createApiSourceset('jellybeanmr1', 'jellybean-mr1', '17', jbSS)
def jbMr2SS = createApiSourceset('jellybeanmr2', 'jellybean-mr2', '18', jbMr1SS)
def kitkatSS = createApiSourceset('kitkat', 'kitkat', '19', jbMr2SS)
-def api20SS = createApiSourceset('api20', 'api20', 'current', kitkatSS)
-def api21SS = createApiSourceset('api21', 'api21', 'current', api20SS)
+def api20SS = createApiSourceset('api20', 'api20', '20', kitkatSS)
+def api21SS = createApiSourceset('api21', 'api21', '21', api20SS)
+def api22SS = createApiSourceset('api22', 'api22', 'current', api21SS)
def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
@@ -55,6 +56,7 @@
dependencies {
compile project(':support-annotations')
+ donutCompile project(':support-annotations')
// add the internal implementation as a dependency.
// this is not enough to make the regular compileJava task
diff --git a/v4/donut/android/support/v4/app/NotificationCompatBase.java b/v4/donut/android/support/v4/app/NotificationCompatBase.java
index d8e99af..fdc1da0 100644
--- a/v4/donut/android/support/v4/app/NotificationCompatBase.java
+++ b/v4/donut/android/support/v4/app/NotificationCompatBase.java
@@ -34,4 +34,21 @@
public Action[] newArray(int length);
}
}
+
+ public static abstract class UnreadConversation {
+ abstract String[] getParticipants();
+ abstract String getParticipant();
+ abstract String[] getMessages();
+ abstract RemoteInputCompatBase.RemoteInput getRemoteInput();
+ abstract PendingIntent getReplyPendingIntent();
+ abstract PendingIntent getReadPendingIntent();
+ abstract long getLatestTimestamp();
+
+ public interface Factory {
+ UnreadConversation build(String[] messages,
+ RemoteInputCompatBase.RemoteInput remoteInput,
+ PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+ String[] participants, long latestTimestamp);
+ }
+ }
}
diff --git a/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java b/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java
new file mode 100644
index 0000000..f49eb2b
--- /dev/null
+++ b/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+
+public class MediaSessionCompatApi8 {
+ public static void registerMediaButtonEventReceiver(Context context, ComponentName mbr) {
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ am.registerMediaButtonEventReceiver(mbr);
+ }
+
+ public static void unregisterMediaButtonEventReceiver(Context context, ComponentName mbr) {
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ am.unregisterMediaButtonEventReceiver(mbr);
+ }
+}
\ No newline at end of file
diff --git a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
index 1c35bd3..fbcc31e 100644
--- a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
+++ b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
@@ -148,4 +148,12 @@
public static void jumpDrawablesToCurrentState(View view) {
view.jumpDrawablesToCurrentState();
}
+
+ public static void setSaveFromParentEnabled(View view, boolean enabled) {
+ view.setSaveFromParentEnabled(enabled);
+ }
+
+ public static void setActivated(View view, boolean activated) {
+ view.setActivated(activated);
+ }
}
diff --git a/v4/ics-mr1/android/support/v4/content/res/ResourcesCompatIcsMr1.java b/v4/ics-mr1/android/support/v4/content/res/ResourcesCompatIcsMr1.java
new file mode 100644
index 0000000..8e14256
--- /dev/null
+++ b/v4/ics-mr1/android/support/v4/content/res/ResourcesCompatIcsMr1.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+
+class ResourcesCompatIcsMr1 {
+ public static Drawable getDrawableForDensity(Resources res, int id, int density)
+ throws NotFoundException {
+ return res.getDrawableForDensity(id, density);
+ }
+}
diff --git a/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
new file mode 100644
index 0000000..ff4660e
--- /dev/null
+++ b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaMetadataRetriever;
+import android.media.RemoteControlClient;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+public class MediaSessionCompatApi14 {
+ /***** RemoteControlClient States, we only need none as the others were public *******/
+ final static int RCC_PLAYSTATE_NONE = 0;
+
+ /***** MediaSession States *******/
+ final static int STATE_NONE = 0;
+ final static int STATE_STOPPED = 1;
+ final static int STATE_PAUSED = 2;
+ final static int STATE_PLAYING = 3;
+ final static int STATE_FAST_FORWARDING = 4;
+ final static int STATE_REWINDING = 5;
+ final static int STATE_BUFFERING = 6;
+ final static int STATE_ERROR = 7;
+ final static int STATE_CONNECTING = 8;
+ final static int STATE_SKIPPING_TO_PREVIOUS = 9;
+ final static int STATE_SKIPPING_TO_NEXT = 10;
+
+ /***** MediaMetadata keys ********/
+ private static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ private static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ private static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ private static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ private static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ private static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ private static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ private static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ private static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ private static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ private static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ private static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ private static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ private static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ private static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+ public static Object createRemoteControlClient(PendingIntent mbIntent) {
+ return new RemoteControlClient(mbIntent);
+ }
+
+ public static void setState(Object rccObj, int state) {
+ ((RemoteControlClient) rccObj).setPlaybackState(getRccStateFromState(state));
+ }
+
+ public static void setMetadata(Object rccObj, Bundle metadata) {
+ RemoteControlClient.MetadataEditor editor = ((RemoteControlClient) rccObj).editMetadata(
+ true);
+ buildOldMetadata(metadata, editor);
+ editor.apply();
+ }
+
+ public static void registerRemoteControlClient(Context context, Object rccObj) {
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ am.registerRemoteControlClient((RemoteControlClient) rccObj);
+ }
+
+ public static void unregisterRemoteControlClient(Context context, Object rccObj) {
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ am.unregisterRemoteControlClient((RemoteControlClient) rccObj);
+ }
+
+ static int getRccStateFromState(int state) {
+ switch (state) {
+ case STATE_CONNECTING:
+ case STATE_BUFFERING:
+ return RemoteControlClient.PLAYSTATE_BUFFERING;
+ case STATE_ERROR:
+ return RemoteControlClient.PLAYSTATE_ERROR;
+ case STATE_FAST_FORWARDING:
+ return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
+ case STATE_NONE:
+ return RCC_PLAYSTATE_NONE;
+ case STATE_PAUSED:
+ return RemoteControlClient.PLAYSTATE_PAUSED;
+ case STATE_PLAYING:
+ return RemoteControlClient.PLAYSTATE_PLAYING;
+ case STATE_REWINDING:
+ return RemoteControlClient.PLAYSTATE_REWINDING;
+ case STATE_SKIPPING_TO_PREVIOUS:
+ return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
+ case STATE_SKIPPING_TO_NEXT:
+ return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
+ case STATE_STOPPED:
+ return RemoteControlClient.PLAYSTATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ static void buildOldMetadata(Bundle metadata, RemoteControlClient.MetadataEditor editor) {
+ if (metadata.containsKey(METADATA_KEY_ALBUM)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM,
+ metadata.getString(METADATA_KEY_ALBUM));
+ }
+ if (metadata.containsKey(METADATA_KEY_ALBUM_ARTIST)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
+ metadata.getString(METADATA_KEY_ALBUM_ARTIST));
+ }
+ if (metadata.containsKey(METADATA_KEY_ARTIST)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST,
+ metadata.getString(METADATA_KEY_ARTIST));
+ }
+ if (metadata.containsKey(METADATA_KEY_AUTHOR)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_AUTHOR,
+ metadata.getString(METADATA_KEY_AUTHOR));
+ }
+ if (metadata.containsKey(METADATA_KEY_COMPILATION)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_COMPILATION,
+ metadata.getString(METADATA_KEY_COMPILATION));
+ }
+ if (metadata.containsKey(METADATA_KEY_COMPOSER)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_COMPOSER,
+ metadata.getString(METADATA_KEY_COMPOSER));
+ }
+ if (metadata.containsKey(METADATA_KEY_DATE)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_DATE,
+ metadata.getString(METADATA_KEY_DATE));
+ }
+ if (metadata.containsKey(METADATA_KEY_DISC_NUMBER)) {
+ editor.putLong(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
+ metadata.getLong(METADATA_KEY_DISC_NUMBER));
+ }
+ if (metadata.containsKey(METADATA_KEY_DURATION)) {
+ editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
+ metadata.getLong(METADATA_KEY_DURATION));
+ }
+ if (metadata.containsKey(METADATA_KEY_GENRE)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_GENRE,
+ metadata.getString(METADATA_KEY_GENRE));
+ }
+ if (metadata.containsKey(METADATA_KEY_NUM_TRACKS)) {
+ editor.putLong(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS,
+ metadata.getLong(METADATA_KEY_NUM_TRACKS));
+ }
+ if (metadata.containsKey(METADATA_KEY_TITLE)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE,
+ metadata.getString(METADATA_KEY_TITLE));
+ }
+ if (metadata.containsKey(METADATA_KEY_TRACK_NUMBER)) {
+ editor.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
+ metadata.getLong(METADATA_KEY_TRACK_NUMBER));
+ }
+ if (metadata.containsKey(METADATA_KEY_WRITER)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_WRITER,
+ metadata.getString(METADATA_KEY_WRITER));
+ }
+ if (metadata.containsKey(METADATA_KEY_YEAR)) {
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_YEAR,
+ metadata.getString(METADATA_KEY_YEAR));
+ }
+ }
+
+ public static interface Callback {
+ public void onCommand(String command, Bundle extras, ResultReceiver cb);
+
+ public boolean onMediaButtonEvent(Intent mediaButtonIntent);
+
+ public void onPlay();
+
+ public void onPause();
+
+ public void onSkipToNext();
+
+ public void onSkipToPrevious();
+
+ public void onFastForward();
+
+ public void onRewind();
+
+ public void onStop();
+
+ public void onSeekTo(long pos);
+
+ public void onSetRating(Object ratingObj);
+ }
+}
\ No newline at end of file
diff --git a/v4/ics/android/support/v4/view/ViewCompatICS.java b/v4/ics/android/support/v4/view/ViewCompatICS.java
index 82aeaf3..742c47c 100644
--- a/v4/ics/android/support/v4/view/ViewCompatICS.java
+++ b/v4/ics/android/support/v4/view/ViewCompatICS.java
@@ -16,6 +16,7 @@
package android.support.v4.view;
+import android.support.annotation.Nullable;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.accessibility.AccessibilityEvent;
@@ -34,7 +35,7 @@
return v.canScrollVertically(direction);
}
- public static void setAccessibilityDelegate(View v, Object delegate) {
+ public static void setAccessibilityDelegate(View v, @Nullable Object delegate) {
v.setAccessibilityDelegate((AccessibilityDelegate) delegate);
}
@@ -49,4 +50,8 @@
public static void onInitializeAccessibilityNodeInfo(View v, Object info) {
v.onInitializeAccessibilityNodeInfo((AccessibilityNodeInfo) info);
}
+
+ public static void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+ view.setFitsSystemWindows(fitSystemWindows);
+ }
}
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index ab23a6f..da9354e 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -1076,10 +1076,10 @@
}
private static Object captureExitingViews(Object exitTransition, Fragment outFragment,
- ArrayList<View> exitingViews, ArrayMap<String, View> namedViews) {
+ ArrayList<View> exitingViews, ArrayMap<String, View> namedViews, View nonExistentView) {
if (exitTransition != null) {
exitTransition = FragmentTransitionCompat21.captureExitingViews(exitTransition,
- outFragment.getView(), exitingViews, namedViews);
+ outFragment.getView(), exitingViews, namedViews, nonExistentView);
}
return exitTransition;
}
@@ -1147,11 +1147,8 @@
ArrayList<View> sharedElementTargets = new ArrayList<View>();
if (sharedElementTransition != null) {
namedViews = remapSharedElements(state, outFragment, isBack);
- if (namedViews.isEmpty()) {
- sharedElementTargets.add(state.nonExistentView);
- } else {
- sharedElementTargets.addAll(namedViews.values());
- }
+ sharedElementTargets.add(state.nonExistentView);
+ sharedElementTargets.addAll(namedViews.values());
// Notify the start of the transition.
SharedElementCallback callback = isBack ?
@@ -1166,7 +1163,7 @@
ArrayList<View> exitingViews = new ArrayList<View>();
exitTransition = captureExitingViews(exitTransition, outFragment, exitingViews,
- namedViews);
+ namedViews, state.nonExistentView);
// Set the epicenter of the exit transition
if (mSharedElementTargetNames != null && namedViews != null) {
@@ -1243,11 +1240,8 @@
ArrayMap<String, View> namedViews = mapSharedElementsIn(
state, isBack, inFragment);
- if (namedViews.isEmpty()) {
- sharedElementTargets.add(state.nonExistentView);
- } else {
- sharedElementTargets.addAll(namedViews.values());
- }
+ sharedElementTargets.add(state.nonExistentView);
+ sharedElementTargets.addAll(namedViews.values());
FragmentTransitionCompat21.addTargets(sharedElementTransition,
sharedElementTargets);
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index 7116ee3..ee596f9 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -28,6 +29,7 @@
import android.support.annotation.StringRes;
import android.support.v4.util.DebugUtils;
import android.support.v4.util.LogWriter;
+import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -921,7 +923,11 @@
f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
- f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+ if (Build.VERSION.SDK_INT >= 11) {
+ ViewCompat.setSaveFromParentEnabled(f.mView, false);
+ } else {
+ f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+ }
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
} else {
@@ -948,7 +954,11 @@
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
- f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+ if (Build.VERSION.SDK_INT >= 11) {
+ ViewCompat.setSaveFromParentEnabled(f.mView, false);
+ } else {
+ f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+ }
if (container != null) {
Animation anim = loadAnimation(f, transit, true,
transitionStyle);
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index 4b355cc..e007bcd 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -453,6 +453,10 @@
public String getGroup(Notification n);
public boolean isGroupSummary(Notification n);
public String getSortKey(Notification n);
+ Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc);
+ NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+ Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+ RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory);
}
static class NotificationCompatImplBase implements NotificationCompatImpl {
@@ -518,6 +522,18 @@
public String getSortKey(Notification n) {
return null;
}
+
+ @Override
+ public Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc) {
+ return null;
+ }
+
+ @Override
+ public NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+ Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+ RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+ return null;
+ }
}
static class NotificationCompatImplGingerbread extends NotificationCompatImplBase {
@@ -743,6 +759,19 @@
public String getCategory(Notification notif) {
return NotificationCompatApi21.getCategory(notif);
}
+
+ @Override
+ public Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc) {
+ return NotificationCompatApi21.getBundleForUnreadConversation(uc);
+ }
+
+ @Override
+ public NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+ Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+ RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+ return NotificationCompatApi21.getUnreadConversationFromBundle(
+ b, factory, remoteInputFactory);
+ }
}
private static void addActionsToBuilder(NotificationBuilderWithActions builder,
@@ -1789,6 +1818,7 @@
/**
* Get additional metadata carried around with this Action.
*/
+ @Override
public Bundle getExtras() {
return mExtras;
}
@@ -1797,6 +1827,7 @@
* Get the list of inputs to be collected from the user when this action is sent.
* May return null if no remote inputs were added.
*/
+ @Override
public RemoteInput[] getRemoteInputs() {
return mRemoteInputs;
}
@@ -2225,6 +2256,19 @@
*/
public static final int SIZE_FULL_SCREEN = 5;
+ /**
+ * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
+ * short amount of time when this notification is displayed on the screen. This
+ * is the default value.
+ */
+ public static final int SCREEN_TIMEOUT_SHORT = 0;
+
+ /**
+ * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
+ * for a longer amount of time when this notification is displayed on the screen.
+ */
+ public static final int SCREEN_TIMEOUT_LONG = -1;
+
/** Notification extra which contains wearable extensions */
private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
@@ -2240,12 +2284,14 @@
private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
private static final String KEY_GRAVITY = "gravity";
+ private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
// Flags bitwise-ored to mFlags
private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
+ private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
@@ -2264,6 +2310,7 @@
private int mCustomSizePreset = SIZE_DEFAULT;
private int mCustomContentHeight;
private int mGravity = DEFAULT_GRAVITY;
+ private int mHintScreenTimeout;
/**
* Create a {@link NotificationCompat.WearableExtender} with default
@@ -2302,6 +2349,7 @@
SIZE_DEFAULT);
mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
+ mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
}
}
@@ -2351,6 +2399,9 @@
if (mGravity != DEFAULT_GRAVITY) {
wearableBundle.putInt(KEY_GRAVITY, mGravity);
}
+ if (mHintScreenTimeout != 0) {
+ wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
+ }
builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
return builder;
@@ -2370,6 +2421,7 @@
that.mCustomSizePreset = this.mCustomSizePreset;
that.mCustomContentHeight = this.mCustomContentHeight;
that.mGravity = this.mGravity;
+ that.mHintScreenTimeout = this.mHintScreenTimeout;
return that;
}
@@ -2765,6 +2817,52 @@
return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
}
+ /**
+ * Set a hint that this notification's background should not be clipped if possible,
+ * and should instead be resized to fully display on the screen, retaining the aspect
+ * ratio of the image. This can be useful for images like barcodes or qr codes.
+ * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintAvoidBackgroundClipping(
+ boolean hintAvoidBackgroundClipping) {
+ setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
+ return this;
+ }
+
+ /**
+ * Get a hint that this notification's background should not be clipped if possible,
+ * and should instead be resized to fully display on the screen, retaining the aspect
+ * ratio of the image. This can be useful for images like barcodes or qr codes.
+ * @return {@code true} if it's ok if the background is clipped on the screen, false
+ * otherwise. The default value is {@code false} if this was never set.
+ */
+ public boolean getHintAvoidBackgroundClipping() {
+ return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
+ }
+
+ /**
+ * Set a hint that the screen should remain on for at least this duration when
+ * this notification is displayed on the screen.
+ * @param timeout The requested screen timeout in milliseconds. Can also be either
+ * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintScreenTimeout(int timeout) {
+ mHintScreenTimeout = timeout;
+ return this;
+ }
+
+ /**
+ * Get the duration, in milliseconds, that the screen should remain on for
+ * when this notification is displayed.
+ * @return the duration in milliseconds if > 0, or either one of the sentinel values
+ * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
+ */
+ public int getHintScreenTimeout() {
+ return mHintScreenTimeout;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
@@ -2775,6 +2873,360 @@
}
/**
+ * <p>Helper class to add Android Auto extensions to notifications. To create a notification
+ * with car extensions:
+ *
+ * <ol>
+ * <li>Create an {@link NotificationCompat.Builder}, setting any desired
+ * properties.
+ * <li>Create a {@link CarExtender}.
+ * <li>Set car-specific properties using the {@code add} and {@code set} methods of
+ * {@link CarExtender}.
+ * <li>Call {@link android.support.v4.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)}
+ * to apply the extensions to a notification.
+ * <li>Post the notification to the notification system with the
+ * {@code NotificationManagerCompat.notify(...)} methods and not the
+ * {@code NotificationManager.notify(...)} methods.
+ * </ol>
+ *
+ * <pre class="prettyprint">
+ * Notification notification = new NotificationCompat.Builder(context)
+ * ...
+ * .extend(new CarExtender()
+ * .set*(...))
+ * .build();
+ * </pre>
+ *
+ * <p>Car extensions can be accessed on an existing notification by using the
+ * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
+ * to access values.
+ */
+ public static final class CarExtender implements Extender {
+ private static final String TAG = "CarExtender";
+
+ private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
+ private static final String EXTRA_LARGE_ICON = "large_icon";
+ private static final String EXTRA_CONVERSATION = "car_conversation";
+ private static final String EXTRA_COLOR = "app_color";
+
+ private Bitmap mLargeIcon;
+ private UnreadConversation mUnreadConversation;
+ private int mColor = NotificationCompat.COLOR_DEFAULT;
+
+ /**
+ * Create a {@link CarExtender} with default options.
+ */
+ public CarExtender() {
+ }
+
+ /**
+ * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
+ *
+ * @param notif The notification from which to copy options.
+ */
+ public CarExtender(Notification notif) {
+ if (Build.VERSION.SDK_INT < 21) {
+ return;
+ }
+
+ Bundle carBundle = getExtras(notif)==null ?
+ null : getExtras(notif).getBundle(EXTRA_CAR_EXTENDER);
+ if (carBundle != null) {
+ mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
+ mColor = carBundle.getInt(EXTRA_COLOR, NotificationCompat.COLOR_DEFAULT);
+
+ Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
+ mUnreadConversation = (UnreadConversation) IMPL.getUnreadConversationFromBundle(
+ b, UnreadConversation.FACTORY, RemoteInput.FACTORY);
+ }
+ }
+
+ /**
+ * Apply car extensions to a notification that is being built. This is typically called by
+ * the {@link android.support.v4.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)}
+ * method of {@link NotificationCompat.Builder}.
+ */
+ @Override
+ public NotificationCompat.Builder extend(NotificationCompat.Builder builder) {
+ if (Build.VERSION.SDK_INT < 21) {
+ return builder;
+ }
+
+ Bundle carExtensions = new Bundle();
+
+ if (mLargeIcon != null) {
+ carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
+ }
+ if (mColor != NotificationCompat.COLOR_DEFAULT) {
+ carExtensions.putInt(EXTRA_COLOR, mColor);
+ }
+
+ if (mUnreadConversation != null) {
+ Bundle b = IMPL.getBundleForUnreadConversation(mUnreadConversation);
+ carExtensions.putBundle(EXTRA_CONVERSATION, b);
+ }
+
+ builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
+ return builder;
+ }
+
+ /**
+ * Sets the accent color to use when Android Auto presents the notification.
+ *
+ * Android Auto uses the color set with {@link android.support.v4.app.NotificationCompat.Builder#setColor(int)}
+ * to accent the displayed notification. However, not all colors are acceptable in an
+ * automotive setting. This method can be used to override the color provided in the
+ * notification in such a situation.
+ */
+ public CarExtender setColor(int color) {
+ mColor = color;
+ return this;
+ }
+
+ /**
+ * Gets the accent color.
+ *
+ * @see setColor
+ */
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Sets the large icon of the car notification.
+ *
+ * If no large icon is set in the extender, Android Auto will display the icon
+ * specified by {@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon(android.graphics.Bitmap)}
+ *
+ * @param largeIcon The large icon to use in the car notification.
+ * @return This object for method chaining.
+ */
+ public CarExtender setLargeIcon(Bitmap largeIcon) {
+ mLargeIcon = largeIcon;
+ return this;
+ }
+
+ /**
+ * Gets the large icon used in this car notification, or null if no icon has been set.
+ *
+ * @return The large icon for the car notification.
+ * @see CarExtender#setLargeIcon
+ */
+ public Bitmap getLargeIcon() {
+ return mLargeIcon;
+ }
+
+ /**
+ * Sets the unread conversation in a message notification.
+ *
+ * @param unreadConversation The unread part of the conversation this notification conveys.
+ * @return This object for method chaining.
+ */
+ public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
+ mUnreadConversation = unreadConversation;
+ return this;
+ }
+
+ /**
+ * Returns the unread conversation conveyed by this notification.
+ * @see #setUnreadConversation(UnreadConversation)
+ */
+ public UnreadConversation getUnreadConversation() {
+ return mUnreadConversation;
+ }
+
+ /**
+ * A class which holds the unread messages from a conversation.
+ */
+ public static class UnreadConversation extends NotificationCompatBase.UnreadConversation {
+ private final String[] mMessages;
+ private final RemoteInput mRemoteInput;
+ private final PendingIntent mReplyPendingIntent;
+ private final PendingIntent mReadPendingIntent;
+ private final String[] mParticipants;
+ private final long mLatestTimestamp;
+
+ UnreadConversation(String[] messages, RemoteInput remoteInput,
+ PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+ String[] participants, long latestTimestamp) {
+ mMessages = messages;
+ mRemoteInput = remoteInput;
+ mReadPendingIntent = readPendingIntent;
+ mReplyPendingIntent = replyPendingIntent;
+ mParticipants = participants;
+ mLatestTimestamp = latestTimestamp;
+ }
+
+ /**
+ * Gets the list of messages conveyed by this notification.
+ */
+ @Override
+ public String[] getMessages() {
+ return mMessages;
+ }
+
+ /**
+ * Gets the remote input that will be used to convey the response to a message list, or
+ * null if no such remote input exists.
+ */
+ @Override
+ public RemoteInput getRemoteInput() {
+ return mRemoteInput;
+ }
+
+ /**
+ * Gets the pending intent that will be triggered when the user replies to this
+ * notification.
+ */
+ @Override
+ public PendingIntent getReplyPendingIntent() {
+ return mReplyPendingIntent;
+ }
+
+ /**
+ * Gets the pending intent that Android Auto will send after it reads aloud all messages
+ * in this object's message list.
+ */
+ @Override
+ public PendingIntent getReadPendingIntent() {
+ return mReadPendingIntent;
+ }
+
+ /**
+ * Gets the participants in the conversation.
+ */
+ @Override
+ public String[] getParticipants() {
+ return mParticipants;
+ }
+
+ /**
+ * Gets the firs participant in the conversation.
+ */
+ @Override
+ public String getParticipant() {
+ return mParticipants.length > 0 ? mParticipants[0] : null;
+ }
+
+ /**
+ * Gets the timestamp of the conversation.
+ */
+ @Override
+ public long getLatestTimestamp() {
+ return mLatestTimestamp;
+ }
+
+ /** @hide */
+ static final Factory FACTORY = new Factory() {
+ @Override
+ public UnreadConversation build(
+ String[] messages, RemoteInputCompatBase.RemoteInput remoteInput,
+ PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+ String[] participants, long latestTimestamp) {
+ return new UnreadConversation(
+ messages, (RemoteInput) remoteInput, replyPendingIntent,
+ readPendingIntent,
+ participants, latestTimestamp);
+ }
+ };
+
+ /**
+ * Builder class for {@link CarExtender.UnreadConversation} objects.
+ */
+ public static class Builder {
+ private final List<String> mMessages = new ArrayList<String>();
+ private final String mParticipant;
+ private RemoteInput mRemoteInput;
+ private PendingIntent mReadPendingIntent;
+ private PendingIntent mReplyPendingIntent;
+ private long mLatestTimestamp;
+
+ /**
+ * Constructs a new builder for {@link CarExtender.UnreadConversation}.
+ *
+ * @param name The name of the other participant in the conversation.
+ */
+ public Builder(String name) {
+ mParticipant = name;
+ }
+
+ /**
+ * Appends a new unread message to the list of messages for this conversation.
+ *
+ * The messages should be added from oldest to newest.
+ *
+ * @param message The text of the new unread message.
+ * @return This object for method chaining.
+ */
+ public Builder addMessage(String message) {
+ mMessages.add(message);
+ return this;
+ }
+
+ /**
+ * Sets the pending intent and remote input which will convey the reply to this
+ * notification.
+ *
+ * @param pendingIntent The pending intent which will be triggered on a reply.
+ * @param remoteInput The remote input parcelable which will carry the reply.
+ * @return This object for method chaining.
+ *
+ * @see CarExtender.UnreadConversation#getRemoteInput
+ * @see CarExtender.UnreadConversation#getReplyPendingIntent
+ */
+ public Builder setReplyAction(
+ PendingIntent pendingIntent, RemoteInput remoteInput) {
+ mRemoteInput = remoteInput;
+ mReplyPendingIntent = pendingIntent;
+
+ return this;
+ }
+
+ /**
+ * Sets the pending intent that will be sent once the messages in this notification
+ * are read.
+ *
+ * @param pendingIntent The pending intent to use.
+ * @return This object for method chaining.
+ */
+ public Builder setReadPendingIntent(PendingIntent pendingIntent) {
+ mReadPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the timestamp of the most recent message in an unread conversation.
+ *
+ * If a messaging notification has been posted by your application and has not
+ * yet been cancelled, posting a later notification with the same id and tag
+ * but without a newer timestamp may result in Android Auto not displaying a
+ * heads up notification for the later notification.
+ *
+ * @param timestamp The timestamp of the most recent message in the conversation.
+ * @return This object for method chaining.
+ */
+ public Builder setLatestTimestamp(long timestamp) {
+ mLatestTimestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * Builds a new unread conversation object.
+ *
+ * @return The new unread conversation object.
+ */
+ public UnreadConversation build() {
+ String[] messages = mMessages.toArray(new String[mMessages.size()]);
+ String[] participants = { mParticipant };
+ return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
+ mReadPendingIntent, participants, mLatestTimestamp);
+ }
+ }
+ }
+ }
+
+
+ /**
* Get an array of Notification objects from a parcelable array bundle field.
* Update the bundle to have a typed array so fetches in the future don't need
* to do an array copy.
diff --git a/v4/java/android/support/v4/app/SharedElementCallback.java b/v4/java/android/support/v4/app/SharedElementCallback.java
index 545edd0..7c0de86 100644
--- a/v4/java/android/support/v4/app/SharedElementCallback.java
+++ b/v4/java/android/support/v4/app/SharedElementCallback.java
@@ -20,11 +20,15 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Parcelable;
import android.view.View;
import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
import java.util.List;
import java.util.Map;
@@ -38,6 +42,10 @@
*/
public abstract class SharedElementCallback {
private Matrix mTempMatrix;
+ private static int MAX_IMAGE_SIZE = (1024 * 1024);
+ private static final String BUNDLE_SNAPSHOT_BITMAP = "sharedElement:snapshot:bitmap";
+ private static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "sharedElement:snapshot:imageScaleType";
+ private static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "sharedElement:snapshot:imageMatrix";
/**
* Called immediately after the start state is set for the shared element.
@@ -138,15 +146,40 @@
*/
public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
RectF screenBounds) {
+ if (sharedElement instanceof ImageView) {
+ ImageView imageView = ((ImageView) sharedElement);
+ Drawable d = imageView.getDrawable();
+ Drawable bg = imageView.getBackground();
+ if (d != null && bg == null) {
+ Bitmap bitmap = createDrawableBitmap(d);
+ if (bitmap != null) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
+ bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
+ imageView.getScaleType().toString());
+ if (imageView.getScaleType() == ScaleType.MATRIX) {
+ Matrix matrix = imageView.getImageMatrix();
+ float[] values = new float[9];
+ matrix.getValues(values);
+ bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
+ }
+ return bundle;
+ }
+ }
+ }
int bitmapWidth = Math.round(screenBounds.width());
int bitmapHeight = Math.round(screenBounds.height());
Bitmap bitmap = null;
if (bitmapWidth > 0 && bitmapHeight > 0) {
+ float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
+ bitmapWidth *= scale;
+ bitmapHeight *= scale;
if (mTempMatrix == null) {
mTempMatrix = new Matrix();
}
mTempMatrix.set(viewToGlobalMatrix);
mTempMatrix.postTranslate(-screenBounds.left, -screenBounds.top);
+ mTempMatrix.postScale(scale, scale);
bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.concat(mTempMatrix);
@@ -156,6 +189,35 @@
}
/**
+ * Get a copy of bitmap of given drawable.
+ */
+ private static Bitmap createDrawableBitmap(Drawable drawable) {
+ int width = drawable.getIntrinsicWidth();
+ int height = drawable.getIntrinsicHeight();
+ if (width <= 0 || height <= 0) {
+ return null;
+ }
+ float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (width * height));
+ if (drawable instanceof BitmapDrawable && scale == 1f) {
+ // return same bitmap if scale down not needed
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+ int bitmapWidth = (int) (width * scale);
+ int bitmapHeight = (int) (height * scale);
+ Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ Rect existingBounds = drawable.getBounds();
+ int left = existingBounds.left;
+ int top = existingBounds.top;
+ int right = existingBounds.right;
+ int bottom = existingBounds.bottom;
+ drawable.setBounds(0, 0, bitmapWidth, bitmapHeight);
+ drawable.draw(canvas);
+ drawable.setBounds(left, top, right, bottom);
+ return bitmap;
+ }
+
+ /**
* Reconstitutes a snapshot View from a Parcelable returned in
* {@link #onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix,
* android.graphics.RectF)} to be used in {@link #onSharedElementStart(java.util.List,
@@ -174,7 +236,24 @@
*/
public View onCreateSnapshotView(Context context, Parcelable snapshot) {
ImageView view = null;
- if (snapshot instanceof Bitmap) {
+ if (snapshot instanceof Bundle) {
+ Bundle bundle = (Bundle) snapshot;
+ Bitmap bitmap = (Bitmap) bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);
+ if (bitmap == null) {
+ return null;
+ }
+ ImageView imageView = new ImageView(context);
+ view = imageView;
+ imageView.setImageBitmap(bitmap);
+ imageView.setScaleType(
+ ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
+ if (imageView.getScaleType() == ScaleType.MATRIX) {
+ float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
+ Matrix matrix = new Matrix();
+ matrix.setValues(values);
+ imageView.setImageMatrix(matrix);
+ }
+ } else if (snapshot instanceof Bitmap) {
Bitmap bitmap = (Bitmap) snapshot;
view = new ImageView(context);
view.setImageBitmap(bitmap);
diff --git a/v4/java/android/support/v4/content/res/ResourcesCompat.java b/v4/java/android/support/v4/content/res/ResourcesCompat.java
index 2dbd334..252977b 100644
--- a/v4/java/android/support/v4/content/res/ResourcesCompat.java
+++ b/v4/java/android/support/v4/content/res/ResourcesCompat.java
@@ -39,12 +39,14 @@
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
- * @param theme The theme used to style the drawable attributes, may be {@code null}.
+ * @param theme The theme used to style the drawable attributes, may be
+ * {@code null}.
* @return Drawable An object that can be used to draw this resource.
* @throws NotFoundException Throws NotFoundException if the given ID does
* not exist.
*/
- public Drawable getDrawable(Resources res, int id, Theme theme)
+ @SuppressWarnings("deprecation")
+ public static Drawable getDrawable(Resources res, int id, Theme theme)
throws NotFoundException {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
@@ -53,4 +55,39 @@
return res.getDrawable(id);
}
}
+
+
+ /**
+ * Return a drawable object associated with a particular resource ID for
+ * the given screen density in DPI and styled for the specified theme.
+ * <p>
+ * Prior to API level 15, the theme and density will not be applied and
+ * this method simply calls through to {@link Resources#getDrawable(int)}.
+ * <p>
+ * Prior to API level 21, the theme will not be applied and this method
+ * calls through to Resources.getDrawableForDensity(int, int).
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @param density The desired screen density indicated by the resource as
+ * found in {@link android.util.DisplayMetrics}.
+ * @param theme The theme used to style the drawable attributes, may be
+ * {@code null}.
+ * @return Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ */
+ @SuppressWarnings("deprecation")
+ public static Drawable getDrawableForDensity(Resources res, int id, int density, Theme theme)
+ throws NotFoundException {
+ final int version = Build.VERSION.SDK_INT;
+ if (version >= 21) {
+ return ResourcesCompatApi21.getDrawableForDensity(res, id, density, theme);
+ } else if (version >= 15) {
+ return ResourcesCompatIcsMr1.getDrawableForDensity(res, id, density);
+ } else {
+ return res.getDrawable(id);
+ }
+ }
}
diff --git a/v4/java/android/support/v4/media/MediaDescriptionCompat.java b/v4/java/android/support/v4/media/MediaDescriptionCompat.java
new file mode 100644
index 0000000..39e3a24
--- /dev/null
+++ b/v4/java/android/support/v4/media/MediaDescriptionCompat.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * A simple set of metadata for a media item suitable for display. This can be
+ * created using the Builder or retrieved from existing metadata using
+ * {@link MediaMetadataCompat#getDescription()}.
+ */
+public final class MediaDescriptionCompat implements Parcelable {
+ /**
+ * A unique persistent id for the content or null.
+ */
+ private final String mMediaId;
+ /**
+ * A primary title suitable for display or null.
+ */
+ private final CharSequence mTitle;
+ /**
+ * A subtitle suitable for display or null.
+ */
+ private final CharSequence mSubtitle;
+ /**
+ * A description suitable for display or null.
+ */
+ private final CharSequence mDescription;
+ /**
+ * A bitmap icon suitable for display or null.
+ */
+ private final Bitmap mIcon;
+ /**
+ * A Uri for an icon suitable for display or null.
+ */
+ private final Uri mIconUri;
+ /**
+ * Extras for opaque use by apps/system.
+ */
+ private final Bundle mExtras;
+
+ /**
+ * A cached copy of the equivalent framework object.
+ */
+ private Object mDescriptionObj;
+
+ private MediaDescriptionCompat(String mediaId, CharSequence title, CharSequence subtitle,
+ CharSequence description, Bitmap icon, Uri iconUri, Bundle extras) {
+ mMediaId = mediaId;
+ mTitle = title;
+ mSubtitle = subtitle;
+ mDescription = description;
+ mIcon = icon;
+ mIconUri = iconUri;
+ mExtras = extras;
+ }
+
+ private MediaDescriptionCompat(Parcel in) {
+ mMediaId = in.readString();
+ mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mIcon = in.readParcelable(null);
+ mIconUri = in.readParcelable(null);
+ mExtras = in.readBundle();
+ }
+
+ /**
+ * Returns the media id or null. See
+ * {@link MediaMetadataCompat#METADATA_KEY_MEDIA_ID}.
+ */
+ public String getMediaId() {
+ return mMediaId;
+ }
+
+ /**
+ * Returns a title suitable for display or null.
+ *
+ * @return A title or null.
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns a subtitle suitable for display or null.
+ *
+ * @return A subtitle or null.
+ */
+ public CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Returns a description suitable for display or null.
+ *
+ * @return A description or null.
+ */
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns a bitmap icon suitable for display or null.
+ *
+ * @return An icon or null.
+ */
+ public Bitmap getIconBitmap() {
+ return mIcon;
+ }
+
+ /**
+ * Returns a Uri for an icon suitable for display or null.
+ *
+ * @return An icon uri or null.
+ */
+ public Uri getIconUri() {
+ return mIconUri;
+ }
+
+ /**
+ * Returns any extras that were added to the description.
+ *
+ * @return A bundle of extras or null.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (Build.VERSION.SDK_INT < 21) {
+ dest.writeString(mMediaId);
+ TextUtils.writeToParcel(mTitle, dest, flags);
+ TextUtils.writeToParcel(mSubtitle, dest, flags);
+ TextUtils.writeToParcel(mDescription, dest, flags);
+ dest.writeParcelable(mIcon, flags);
+ dest.writeParcelable(mIconUri, flags);
+ dest.writeBundle(mExtras);
+ } else {
+ MediaDescriptionCompatApi21.writeToParcel(getMediaDescription(), dest, flags);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mTitle + ", " + mSubtitle + ", " + mDescription;
+ }
+
+ /**
+ * Gets the underlying framework {@link android.media.MediaDescription}
+ * object.
+ * <p>
+ * This method is only supported on
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
+ * </p>
+ *
+ * @return An equivalent {@link android.media.MediaDescription} object, or
+ * null if none.
+ */
+ public Object getMediaDescription() {
+ if (mDescriptionObj != null || Build.VERSION.SDK_INT < 21) {
+ return mDescriptionObj;
+ }
+ Object bob = MediaDescriptionCompatApi21.Builder.newInstance();
+ MediaDescriptionCompatApi21.Builder.setMediaId(bob, mMediaId);
+ MediaDescriptionCompatApi21.Builder.setTitle(bob, mTitle);
+ MediaDescriptionCompatApi21.Builder.setSubtitle(bob, mSubtitle);
+ MediaDescriptionCompatApi21.Builder.setDescription(bob, mDescription);
+ MediaDescriptionCompatApi21.Builder.setIconBitmap(bob, mIcon);
+ MediaDescriptionCompatApi21.Builder.setIconUri(bob, mIconUri);
+ MediaDescriptionCompatApi21.Builder.setExtras(bob, mExtras);
+ mDescriptionObj = MediaDescriptionCompatApi21.Builder.build(bob);
+
+ return mDescriptionObj;
+ }
+
+ /**
+ * Creates an instance from a framework
+ * {@link android.media.MediaDescription} object.
+ * <p>
+ * This method is only supported on API 21+.
+ * </p>
+ *
+ * @param descriptionObj A {@link android.media.MediaDescription} object, or
+ * null if none.
+ * @return An equivalent {@link MediaMetadataCompat} object, or null if
+ * none.
+ */
+ public static MediaDescriptionCompat fromMediaDescription(Object descriptionObj) {
+ if (descriptionObj == null || Build.VERSION.SDK_INT < 21) {
+ return null;
+ }
+
+ Builder bob = new Builder();
+ bob.setMediaId(MediaDescriptionCompatApi21.getMediaId(descriptionObj));
+ bob.setTitle(MediaDescriptionCompatApi21.getTitle(descriptionObj));
+ bob.setSubtitle(MediaDescriptionCompatApi21.getSubtitle(descriptionObj));
+ bob.setDescription(MediaDescriptionCompatApi21.getDescription(descriptionObj));
+ bob.setIconBitmap(MediaDescriptionCompatApi21.getIconBitmap(descriptionObj));
+ bob.setIconUri(MediaDescriptionCompatApi21.getIconUri(descriptionObj));
+ bob.setExtras(MediaDescriptionCompatApi21.getExtras(descriptionObj));
+ MediaDescriptionCompat descriptionCompat = bob.build();
+ descriptionCompat.mDescriptionObj = descriptionObj;
+
+ return descriptionCompat;
+ }
+
+ public static final Parcelable.Creator<MediaDescriptionCompat> CREATOR =
+ new Parcelable.Creator<MediaDescriptionCompat>() {
+ @Override
+ public MediaDescriptionCompat createFromParcel(Parcel in) {
+ if (Build.VERSION.SDK_INT < 21) {
+ return new MediaDescriptionCompat(in);
+ } else {
+ return fromMediaDescription(MediaDescriptionCompatApi21.fromParcel(in));
+ }
+ }
+
+ @Override
+ public MediaDescriptionCompat[] newArray(int size) {
+ return new MediaDescriptionCompat[size];
+ }
+ };
+
+ /**
+ * Builder for {@link MediaDescriptionCompat} objects.
+ */
+ public static final class Builder {
+ private String mMediaId;
+ private CharSequence mTitle;
+ private CharSequence mSubtitle;
+ private CharSequence mDescription;
+ private Bitmap mIcon;
+ private Uri mIconUri;
+ private Bundle mExtras;
+
+ /**
+ * Creates an initially empty builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets the media id.
+ *
+ * @param mediaId The unique id for the item or null.
+ * @return this
+ */
+ public Builder setMediaId(String mediaId) {
+ mMediaId = mediaId;
+ return this;
+ }
+
+ /**
+ * Sets the title.
+ *
+ * @param title A title suitable for display to the user or null.
+ * @return this
+ */
+ public Builder setTitle(CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets the subtitle.
+ *
+ * @param subtitle A subtitle suitable for display to the user or null.
+ * @return this
+ */
+ public Builder setSubtitle(CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Sets the description.
+ *
+ * @param description A description suitable for display to the user or
+ * null.
+ * @return this
+ */
+ public Builder setDescription(CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Sets the icon.
+ *
+ * @param icon A {@link Bitmap} icon suitable for display to the user or
+ * null.
+ * @return this
+ */
+ public Builder setIconBitmap(Bitmap icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the icon uri.
+ *
+ * @param iconUri A {@link Uri} for an icon suitable for display to the
+ * user or null.
+ * @return this
+ */
+ public Builder setIconUri(Uri iconUri) {
+ mIconUri = iconUri;
+ return this;
+ }
+
+ /**
+ * Sets a bundle of extras.
+ *
+ * @param extras The extras to include with this description or null.
+ * @return this
+ */
+ public Builder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Creates a {@link MediaDescriptionCompat} instance with the specified
+ * fields.
+ *
+ * @return A MediaDescriptionCompat instance.
+ */
+ public MediaDescriptionCompat build() {
+ return new MediaDescriptionCompat(mMediaId, mTitle, mSubtitle, mDescription, mIcon,
+ mIconUri, mExtras);
+ }
+ }
+}
diff --git a/v4/java/android/support/v4/media/MediaMetadataCompat.aidl b/v4/java/android/support/v4/media/MediaMetadataCompat.aidl
new file mode 100644
index 0000000..6d36b97
--- /dev/null
+++ b/v4/java/android/support/v4/media/MediaMetadataCompat.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media;
+
+parcelable MediaMetadataCompat;
diff --git a/v4/java/android/support/v4/media/MediaMetadataCompat.java b/v4/java/android/support/v4/media/MediaMetadataCompat.java
index d289cad..3807480 100644
--- a/v4/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/v4/java/android/support/v4/media/MediaMetadataCompat.java
@@ -16,11 +16,13 @@
package android.support.v4.media;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.util.ArrayMap;
+import android.text.TextUtils;
import android.util.Log;
import java.util.Set;
@@ -184,6 +186,13 @@
public static final String METADATA_KEY_DISPLAY_ICON_URI
= "android.media.metadata.DISPLAY_ICON_URI";
+ /**
+ * A String key for identifying the content. This value is specific to the
+ * service providing the content. If used, this should be a persistent
+ * unique key for the underlying content.
+ */
+ public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+
private static final int METADATA_TYPE_LONG = 0;
private static final int METADATA_TYPE_TEXT = 1;
private static final int METADATA_TYPE_BITMAP = 2;
@@ -218,10 +227,34 @@
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
}
+ private static final String[] PREFERRED_DESCRIPTION_ORDER = {
+ METADATA_KEY_TITLE,
+ METADATA_KEY_ARTIST,
+ METADATA_KEY_ALBUM,
+ METADATA_KEY_ALBUM_ARTIST,
+ METADATA_KEY_WRITER,
+ METADATA_KEY_AUTHOR,
+ METADATA_KEY_COMPOSER
+ };
+
+ private static final String[] PREFERRED_BITMAP_ORDER = {
+ METADATA_KEY_DISPLAY_ICON,
+ METADATA_KEY_ART,
+ METADATA_KEY_ALBUM_ART
+ };
+
+ private static final String[] PREFERRED_URI_ORDER = {
+ METADATA_KEY_DISPLAY_ICON_URI,
+ METADATA_KEY_ART_URI,
+ METADATA_KEY_ALBUM_ART_URI
+ };
+
private final Bundle mBundle;
private Object mMetadataObj;
+ private MediaDescriptionCompat mDescription;
private MediaMetadataCompat(Bundle bundle) {
mBundle = new Bundle(bundle);
@@ -316,6 +349,73 @@
return bmp;
}
+ /**
+ * Returns a simple description of this metadata for display purposes.
+ *
+ * @return A simple description of this metadata.
+ */
+ public MediaDescriptionCompat getDescription() {
+ if (mDescription != null) {
+ return mDescription;
+ }
+
+ String mediaId = getString(METADATA_KEY_MEDIA_ID);
+
+ CharSequence[] text = new CharSequence[3];
+ Bitmap icon = null;
+ Uri iconUri = null;
+
+ // First handle the case where display data is set already
+ CharSequence displayText = getText(METADATA_KEY_DISPLAY_TITLE);
+ if (!TextUtils.isEmpty(displayText)) {
+ // If they have a display title use only display data, otherwise use
+ // our best bets
+ text[0] = displayText;
+ text[1] = getText(METADATA_KEY_DISPLAY_SUBTITLE);
+ text[2] = getText(METADATA_KEY_DISPLAY_DESCRIPTION);
+ } else {
+ // Use whatever fields we can
+ int textIndex = 0;
+ int keyIndex = 0;
+ while (textIndex < text.length && keyIndex < PREFERRED_DESCRIPTION_ORDER.length) {
+ CharSequence next = getText(PREFERRED_DESCRIPTION_ORDER[keyIndex++]);
+ if (!TextUtils.isEmpty(next)) {
+ // Fill in the next empty bit of text
+ text[textIndex++] = next;
+ }
+ }
+ }
+
+ // Get the best art bitmap we can find
+ for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) {
+ Bitmap next = getBitmap(PREFERRED_BITMAP_ORDER[i]);
+ if (next != null) {
+ icon = next;
+ break;
+ }
+ }
+
+ // Get the best Uri we can find
+ for (int i = 0; i < PREFERRED_URI_ORDER.length; i++) {
+ String next = getString(PREFERRED_URI_ORDER[i]);
+ if (!TextUtils.isEmpty(next)) {
+ iconUri = Uri.parse(next);
+ break;
+ }
+ }
+
+ MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
+ bob.setMediaId(mediaId);
+ bob.setTitle(text[0]);
+ bob.setSubtitle(text[1]);
+ bob.setDescription(text[2]);
+ bob.setIconBitmap(icon);
+ bob.setIconUri(iconUri);
+ mDescription = bob.build();
+
+ return mDescription;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -345,13 +445,27 @@
}
/**
- * Creates an instance from a framework {@link android.media.MediaMetadata} object.
+ * Gets the bundle backing the metadata object. This is available to support
+ * backwards compatibility. Apps should not modify the bundle directly.
+ *
+ * @return The Bundle backing this metadata.
+ */
+ public Bundle getBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Creates an instance from a framework {@link android.media.MediaMetadata}
+ * object.
* <p>
- * This method is only supported on API 21+.
+ * This method is only supported on
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
* </p>
*
- * @param metadataObj A {@link android.media.MediaMetadata} object, or null if none.
- * @return An equivalent {@link MediaMetadataCompat} object, or null if none.
+ * @param metadataObj A {@link android.media.MediaMetadata} object, or null
+ * if none.
+ * @return An equivalent {@link MediaMetadataCompat} object, or null if
+ * none.
*/
public static MediaMetadataCompat fromMediaMetadata(Object metadataObj) {
if (metadataObj == null || Build.VERSION.SDK_INT < 21) {
@@ -390,10 +504,12 @@
/**
* Gets the underlying framework {@link android.media.MediaMetadata} object.
* <p>
- * This method is only supported on API 21+.
+ * This method is only supported on
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
* </p>
*
- * @return An equivalent {@link android.media.MediaMetadata} object, or null if none.
+ * @return An equivalent {@link android.media.MediaMetadata} object, or null
+ * if none.
*/
public Object getMediaMetadata() {
if (mMetadataObj != null || Build.VERSION.SDK_INT < 21) {
diff --git a/v4/java/android/support/v4/media/RatingCompat.aidl b/v4/java/android/support/v4/media/RatingCompat.aidl
new file mode 100644
index 0000000..223fd5c
--- /dev/null
+++ b/v4/java/android/support/v4/media/RatingCompat.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media;
+
+parcelable RatingCompat;
diff --git a/v4/java/android/support/v4/media/session/IMediaControllerCallback.aidl b/v4/java/android/support/v4/media/session/IMediaControllerCallback.aidl
new file mode 100644
index 0000000..d905350
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/IMediaControllerCallback.aidl
@@ -0,0 +1,40 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.ParcelableVolumeInfo;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+/**
+ * Callback interface for a MediaSessionCompat to send updates to a
+ * MediaControllerCompat. This is only used on pre-Lollipop systems.
+ * @hide
+ */
+oneway interface IMediaControllerCallback {
+ void onEvent(String event, in Bundle extras);
+ void onSessionDestroyed();
+
+ // These callbacks are for the TransportController
+ void onPlaybackStateChanged(in PlaybackStateCompat state);
+ void onMetadataChanged(in MediaMetadataCompat metadata);
+ void onQueueChanged(in List<MediaSessionCompat.QueueItem> queue);
+ void onQueueTitleChanged(CharSequence title);
+ void onExtrasChanged(in Bundle extras);
+ void onVolumeInfoChanged(in ParcelableVolumeInfo info);
+}
diff --git a/v4/java/android/support/v4/media/session/IMediaSession.aidl b/v4/java/android/support/v4/media/session/IMediaSession.aidl
new file mode 100644
index 0000000..1ce425d
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/IMediaSession.aidl
@@ -0,0 +1,69 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.IMediaControllerCallback;
+import android.support.v4.media.session.ParcelableVolumeInfo;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import java.util.List;
+
+/**
+ * Interface to a MediaSessionCompat. This is only used on pre-Lollipop systems.
+ * @hide
+ */
+interface IMediaSession {
+ void sendCommand(String command, in Bundle args, in MediaSessionCompat.ResultReceiverWrapper cb);
+ boolean sendMediaButton(in KeyEvent mediaButton);
+ void registerCallbackListener(in IMediaControllerCallback cb);
+ void unregisterCallbackListener(in IMediaControllerCallback cb);
+ boolean isTransportControlEnabled();
+ String getPackageName();
+ String getTag();
+ PendingIntent getLaunchPendingIntent();
+ long getFlags();
+ ParcelableVolumeInfo getVolumeAttributes();
+ void adjustVolume(int direction, int flags, String packageName);
+ void setVolumeTo(int value, int flags, String packageName);
+
+ // These commands are for the TransportControls
+ void play();
+ void playFromMediaId(String uri, in Bundle extras);
+ void playFromSearch(String string, in Bundle extras);
+ void skipToQueueItem(long id);
+ void pause();
+ void stop();
+ void next();
+ void previous();
+ void fastForward();
+ void rewind();
+ void seekTo(long pos);
+ void rate(in RatingCompat rating);
+ void sendCustomAction(String action, in Bundle args);
+ MediaMetadataCompat getMetadata();
+ PlaybackStateCompat getPlaybackState();
+ List<MediaSessionCompat.QueueItem> getQueue();
+ CharSequence getQueueTitle();
+ Bundle getExtras();
+ int getRatingType();
+}
diff --git a/v4/java/android/support/v4/media/session/MediaControllerCompat.java b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
index 9b07694..ef99f43 100644
--- a/v4/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -16,17 +16,29 @@
package android.support.v4.media.session;
+import android.app.PendingIntent;
import android.content.Context;
+import android.media.AudioManager;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
import android.text.TextUtils;
+import android.util.Log;
import android.view.KeyEvent;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Allows an app to interact with an ongoing media session. Media buttons and
* other commands can be sent to the session. A callback may be registered to
@@ -41,7 +53,10 @@
* introduced after API level 4 in a backwards compatible fashion.
*/
public final class MediaControllerCompat {
+ private static final String TAG = "MediaControllerCompat";
+
private final MediaControllerImpl mImpl;
+ private final MediaSessionCompat.Token mToken;
/**
* Creates a media controller from a session.
@@ -52,11 +67,12 @@
if (session == null) {
throw new IllegalArgumentException("session must not be null");
}
+ mToken = session.getSessionToken();
if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaControllerImplApi21(context, session);
} else {
- mImpl = new MediaControllerImplBase();
+ mImpl = new MediaControllerImplBase(mToken);
}
}
@@ -72,11 +88,12 @@
if (sessionToken == null) {
throw new IllegalArgumentException("sessionToken must not be null");
}
+ mToken = sessionToken;
if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaControllerImplApi21(context, sessionToken);
} else {
- mImpl = new MediaControllerImplBase();
+ mImpl = new MediaControllerImplBase(mToken);
}
}
@@ -122,6 +139,30 @@
}
/**
+ * Get the current play queue for this session if one is set. If you only
+ * care about the current item {@link #getMetadata()} should be used.
+ *
+ * @return The current play queue or null.
+ */
+ public List<MediaSessionCompat.QueueItem> getQueue() {
+ return mImpl.getQueue();
+ }
+
+ /**
+ * Get the queue title for this session.
+ */
+ public CharSequence getQueueTitle() {
+ return mImpl.getQueueTitle();
+ }
+
+ /**
+ * Get the extras for this session.
+ */
+ public Bundle getExtras() {
+ return mImpl.getExtras();
+ }
+
+ /**
* Get the rating type supported by the session. One of:
* <ul>
* <li>{@link RatingCompat#RATING_NONE}</li>
@@ -140,6 +181,16 @@
}
/**
+ * Get the flags for this session. Flags are defined in
+ * {@link MediaSessionCompat}.
+ *
+ * @return The current set of flags for the session.
+ */
+ public long getFlags() {
+ return mImpl.getFlags();
+ }
+
+ /**
* Get the current playback info for this session.
*
* @return The current playback info or null.
@@ -149,6 +200,57 @@
}
/**
+ * Get an intent for launching UI associated with this session if one
+ * exists.
+ *
+ * @return A {@link PendingIntent} to launch UI or null.
+ */
+ public PendingIntent getSessionActivity() {
+ return mImpl.getSessionActivity();
+ }
+
+ /**
+ * Get the token for the session this controller is connected to.
+ *
+ * @return The session's token.
+ */
+ public MediaSessionCompat.Token getSessionToken() {
+ return mToken;
+ }
+
+ /**
+ * Set the volume of the output this session is playing on. The command will
+ * be ignored if it does not support
+ * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
+ * {@link AudioManager} may be used to affect the handling.
+ *
+ * @see #getPlaybackInfo()
+ * @param value The value to set it to, between 0 and the reported max.
+ * @param flags Flags from {@link AudioManager} to include with the volume
+ * request.
+ */
+ public void setVolumeTo(int value, int flags) {
+ mImpl.setVolumeTo(value, flags);
+ }
+
+ /**
+ * Adjust the volume of the output this session is playing on. The direction
+ * must be one of {@link AudioManager#ADJUST_LOWER},
+ * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+ * The command will be ignored if the session does not support
+ * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
+ * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
+ * {@link AudioManager} may be used to affect the handling.
+ *
+ * @see #getPlaybackInfo()
+ * @param direction The direction to adjust the volume in.
+ * @param flags Any flags to pass with the command.
+ */
+ public void adjustVolume(int direction, int flags) {
+ mImpl.adjustVolume(direction, flags);
+ }
+
+ /**
* Adds a callback to receive updates from the Session. Updates will be
* posted on the caller's thread.
*
@@ -206,13 +308,23 @@
}
/**
- * Gets the underlying framework {@link android.media.session.MediaController} object.
+ * Get the session owner's package name.
+ *
+ * @return The package name of of the session owner.
+ */
+ public String getPackageName() {
+ return mImpl.getPackageName();
+ }
+
+ /**
+ * Gets the underlying framework
+ * {@link android.media.session.MediaController} object.
* <p>
* This method is only supported on API 21+.
* </p>
*
- * @return The underlying {@link android.media.session.MediaController} object,
- * or null if none.
+ * @return The underlying {@link android.media.session.MediaController}
+ * object, or null if none.
*/
public Object getMediaController() {
return mImpl.getMediaController();
@@ -222,14 +334,17 @@
* Callback for receiving updates on from the session. A Callback can be
* registered using {@link #registerCallback}
*/
- public static abstract class Callback {
- final Object mCallbackObj;
+ public static abstract class Callback implements IBinder.DeathRecipient {
+ private final Object mCallbackObj;
+ private MessageHandler mHandler;
+
+ private boolean mRegistered = false;
public Callback() {
if (android.os.Build.VERSION.SDK_INT >= 21) {
mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21());
} else {
- mCallbackObj = null;
+ mCallbackObj = new StubCompat();
}
}
@@ -268,6 +383,56 @@
public void onMetadataChanged(MediaMetadataCompat metadata) {
}
+ /**
+ * Override to handle changes to items in the queue.
+ *
+ * @see MediaSessionCompat.QueueItem
+ * @param queue A list of items in the current play queue. It should
+ * include the currently playing item as well as previous and
+ * upcoming items if applicable.
+ */
+ public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
+ }
+
+ /**
+ * Override to handle changes to the queue title.
+ *
+ * @param title The title that should be displayed along with the play
+ * queue such as "Now Playing". May be null if there is no
+ * such title.
+ */
+ public void onQueueTitleChanged(CharSequence title) {
+ }
+
+ /**
+ * Override to handle chagnes to the {@link MediaSessionCompat} extras.
+ *
+ * @param extras The extras that can include other information
+ * associated with the {@link MediaSessionCompat}.
+ */
+ public void onExtrasChanged(Bundle extras) {
+ }
+
+ /**
+ * Override to handle changes to the audio info.
+ *
+ * @param info The current audio info for this session.
+ */
+ public void onAudioInfoChanged(PlaybackInfo info) {
+ }
+
+ @Override
+ public void binderDied() {
+ onSessionDestroyed();
+ }
+
+ /**
+ * Set the handler to use for pre 21 callbacks.
+ */
+ private void setHandler(Handler handler) {
+ mHandler = new MessageHandler(handler.getLooper());
+ }
+
private class StubApi21 implements MediaControllerCompatApi21.Callback {
@Override
public void onSessionDestroyed() {
@@ -291,6 +456,106 @@
MediaMetadataCompat.fromMediaMetadata(metadataObj));
}
}
+
+ private class StubCompat extends IMediaControllerCallback.Stub {
+
+ @Override
+ public void onEvent(String event, Bundle extras) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_EVENT, event, extras);
+ }
+
+ @Override
+ public void onSessionDestroyed() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_DESTROYED, null, null);
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
+ }
+
+ @Override
+ public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
+ }
+
+ @Override
+ public void onQueueTitleChanged(CharSequence title) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
+ }
+
+ @Override
+ public void onExtrasChanged(Bundle extras) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
+ }
+
+ @Override
+ public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
+ PlaybackInfo pi = null;
+ if (info != null) {
+ pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
+ info.maxVolume, info.currentVolume);
+ }
+ mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
+ }
+ }
+
+ private class MessageHandler extends Handler {
+ private static final int MSG_EVENT = 1;
+ private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
+ private static final int MSG_UPDATE_METADATA = 3;
+ private static final int MSG_UPDATE_VOLUME = 4;
+ private static final int MSG_UPDATE_QUEUE = 5;
+ private static final int MSG_UPDATE_QUEUE_TITLE = 6;
+ private static final int MSG_UPDATE_EXTRAS = 7;
+ private static final int MSG_DESTROYED = 8;
+
+ public MessageHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (!mRegistered) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_EVENT:
+ onSessionEvent((String) msg.obj, msg.getData());
+ break;
+ case MSG_UPDATE_PLAYBACK_STATE:
+ onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
+ break;
+ case MSG_UPDATE_METADATA:
+ onMetadataChanged((MediaMetadataCompat) msg.obj);
+ break;
+ case MSG_UPDATE_QUEUE:
+ onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj);
+ break;
+ case MSG_UPDATE_QUEUE_TITLE:
+ onQueueTitleChanged((CharSequence) msg.obj);
+ break;
+ case MSG_UPDATE_EXTRAS:
+ onExtrasChanged((Bundle) msg.obj);
+ break;
+ case MSG_UPDATE_VOLUME:
+ onAudioInfoChanged((PlaybackInfo) msg.obj);
+ break;
+ case MSG_DESTROYED:
+ onSessionDestroyed();
+ break;
+ }
+ }
+
+ public void post(int what, Object obj, Bundle data) {
+ obtainMessage(what, obj).sendToTarget();
+ }
+ }
}
/**
@@ -307,6 +572,32 @@
public abstract void play();
/**
+ * Request that the player start playback for a specific {@link Uri}.
+ *
+ * @param mediaId The uri of the requested media.
+ * @param extras Optional extras that can include extra information
+ * about the media item to be played.
+ */
+ public abstract void playFromMediaId(String mediaId, Bundle extras);
+
+ /**
+ * Request that the player start playback for a specific search query.
+ * An empty or null query should be treated as a request to play any
+ * music.
+ *
+ * @param query The search query.
+ * @param extras Optional extras that can include extra information
+ * about the query.
+ */
+ public abstract void playFromSearch(String query, Bundle extras);
+
+ /**
+ * Play an item with a specific id in the play queue. If you specify an
+ * id that is not in the play queue, the behavior is undefined.
+ */
+ public abstract void skipToQueueItem(long id);
+
+ /**
* Request that the player pause its playback and stay at its current
* position.
*/
@@ -355,6 +646,30 @@
* @param rating The rating to set for the current content
*/
public abstract void setRating(RatingCompat rating);
+
+ /**
+ * Send a custom action for the {@link MediaSessionCompat} to perform.
+ *
+ * @param customAction The action to perform.
+ * @param args Optional arguments to supply to the
+ * {@link MediaSessionCompat} for this custom action.
+ */
+ public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
+ Bundle args);
+
+ /**
+ * Send the id and args from a custom action for the
+ * {@link MediaSessionCompat} to perform.
+ *
+ * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
+ * Bundle args)
+ * @param action The action identifier of the
+ * {@link PlaybackStateCompat.CustomAction} as specified by
+ * the {@link MediaSessionCompat}.
+ * @param args Optional arguments to supply to the
+ * {@link MediaSessionCompat} for this custom action.
+ */
+ public abstract void sendCustomAction(String action, Bundle args);
}
/**
@@ -406,6 +721,7 @@
* @return The stream this session is playing on.
*/
public int getAudioStream() {
+ // TODO switch to AudioAttributesCompat when it is added.
return mAudioStream;
}
@@ -451,54 +767,215 @@
TransportControls getTransportControls();
PlaybackStateCompat getPlaybackState();
MediaMetadataCompat getMetadata();
+
+ List<MediaSessionCompat.QueueItem> getQueue();
+ CharSequence getQueueTitle();
+ Bundle getExtras();
int getRatingType();
+ long getFlags();
PlaybackInfo getPlaybackInfo();
+ PendingIntent getSessionActivity();
+
+ void setVolumeTo(int value, int flags);
+ void adjustVolume(int direction, int flags);
void sendCommand(String command, Bundle params, ResultReceiver cb);
+
+ String getPackageName();
Object getMediaController();
}
- // TODO: compatibility implementation
static class MediaControllerImplBase implements MediaControllerImpl {
+ private MediaSessionCompat.Token mToken;
+ private IMediaSession mBinder;
+ private TransportControls mTransportControls;
+
+ public MediaControllerImplBase(MediaSessionCompat.Token token) {
+ mToken = token;
+ mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
+ }
+
@Override
public void registerCallback(Callback callback, Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback may not be null.");
+ }
+ try {
+ mBinder.asBinder().linkToDeath(callback, 0);
+ mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
+ callback.setHandler(handler);
+ callback.mRegistered = true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in registerCallback. " + e);
+ callback.onSessionDestroyed();
+ }
}
@Override
public void unregisterCallback(Callback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback may not be null.");
+ }
+ try {
+ mBinder.unregisterCallbackListener(
+ (IMediaControllerCallback) callback.mCallbackObj);
+ mBinder.asBinder().unlinkToDeath(callback, 0);
+ callback.mRegistered = false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in unregisterCallback. " + e);
+ }
}
@Override
public boolean dispatchMediaButtonEvent(KeyEvent event) {
+ if (event == null) {
+ throw new IllegalArgumentException("event may not be null.");
+ }
+ try {
+ mBinder.sendMediaButton(event);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in dispatchMediaButtonEvent. " + e);
+ }
return false;
}
@Override
public TransportControls getTransportControls() {
- return null;
+ if (mTransportControls == null) {
+ mTransportControls = new TransportControlsBase(mBinder);
+ }
+
+ return mTransportControls;
}
@Override
public PlaybackStateCompat getPlaybackState() {
+ try {
+ return mBinder.getPlaybackState();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getPlaybackState. " + e);
+ }
return null;
}
@Override
public MediaMetadataCompat getMetadata() {
+ try {
+ return mBinder.getMetadata();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getMetadata. " + e);
+ }
+ return null;
+ }
+
+ @Override
+ public List<MediaSessionCompat.QueueItem> getQueue() {
+ try {
+ return mBinder.getQueue();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getQueue. " + e);
+ }
+ return null;
+ }
+
+ @Override
+ public CharSequence getQueueTitle() {
+ try {
+ return mBinder.getQueueTitle();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getQueueTitle. " + e);
+ }
+ return null;
+ }
+
+ @Override
+ public Bundle getExtras() {
+ try {
+ return mBinder.getExtras();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getExtras. " + e);
+ }
return null;
}
@Override
public int getRatingType() {
+ try {
+ return mBinder.getRatingType();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getRatingType. " + e);
+ }
+ return 0;
+ }
+
+ @Override
+ public long getFlags() {
+ try {
+ return mBinder.getFlags();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getFlags. " + e);
+ }
return 0;
}
@Override
public PlaybackInfo getPlaybackInfo() {
+ try {
+ ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
+ PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
+ info.controlType, info.maxVolume, info.currentVolume);
+ return pi;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getPlaybackInfo. " + e);
+ }
return null;
}
@Override
+ public PendingIntent getSessionActivity() {
+ try {
+ return mBinder.getLaunchPendingIntent();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getSessionActivity. " + e);
+ }
+ return null;
+ }
+
+ @Override
+ public void setVolumeTo(int value, int flags) {
+ try {
+ mBinder.setVolumeTo(value, flags, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in setVolumeTo. " + e);
+ }
+ }
+
+ @Override
+ public void adjustVolume(int direction, int flags) {
+ try {
+ mBinder.adjustVolume(direction, flags, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in adjustVolume. " + e);
+ }
+ }
+
+ @Override
public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+ try {
+ mBinder.sendCommand(command, params,
+ new MediaSessionCompat.ResultReceiverWrapper(cb));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in sendCommand. " + e);
+ }
+ }
+
+ @Override
+ public String getPackageName() {
+ try {
+ return mBinder.getPackageName();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getPackageName. " + e);
+ }
+ return null;
}
@Override
@@ -507,6 +984,136 @@
}
}
+ static class TransportControlsBase extends TransportControls {
+ private IMediaSession mBinder;
+
+ public TransportControlsBase(IMediaSession binder) {
+ mBinder = binder;
+ }
+
+ @Override
+ public void play() {
+ try {
+ mBinder.play();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in play. " + e);
+ }
+ }
+
+ @Override
+ public void playFromMediaId(String mediaId, Bundle extras) {
+ try {
+ mBinder.playFromMediaId(mediaId, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in playFromMediaId. " + e);
+ }
+ }
+
+ @Override
+ public void playFromSearch(String query, Bundle extras) {
+ try {
+ mBinder.playFromSearch(query, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in playFromSearch. " + e);
+ }
+ }
+
+ @Override
+ public void skipToQueueItem(long id) {
+ try {
+ mBinder.skipToQueueItem(id);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in skipToQueueItem. " + e);
+ }
+ }
+
+ @Override
+ public void pause() {
+ try {
+ mBinder.pause();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in pause. " + e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ try {
+ mBinder.stop();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in stop. " + e);
+ }
+ }
+
+ @Override
+ public void seekTo(long pos) {
+ try {
+ mBinder.seekTo(pos);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in seekTo. " + e);
+ }
+ }
+
+ @Override
+ public void fastForward() {
+ try {
+ mBinder.fastForward();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in fastForward. " + e);
+ }
+ }
+
+ @Override
+ public void skipToNext() {
+ try {
+ mBinder.next();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in skipToNext. " + e);
+ }
+ }
+
+ @Override
+ public void rewind() {
+ try {
+ mBinder.rewind();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in rewind. " + e);
+ }
+ }
+
+ @Override
+ public void skipToPrevious() {
+ try {
+ mBinder.previous();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in skipToPrevious. " + e);
+ }
+ }
+
+ @Override
+ public void setRating(RatingCompat rating) {
+ try {
+ mBinder.rate(rating);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in setRating. " + e);
+ }
+ }
+
+ @Override
+ public void sendCustomAction(CustomAction customAction, Bundle args) {
+ sendCustomAction(customAction.getAction(), args);
+ }
+
+ @Override
+ public void sendCustomAction(String action, Bundle args) {
+ try {
+ mBinder.sendCustomAction(action, args);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in sendCustomAction. " + e);
+ }
+ }
+ }
+
static class MediaControllerImplApi21 implements MediaControllerImpl {
private final Object mControllerObj;
@@ -517,7 +1124,6 @@
public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
throws RemoteException {
- // TODO: refactor framework implementation
mControllerObj = MediaControllerCompatApi21.fromToken(context,
sessionToken.getToken());
if (mControllerObj == null) throw new RemoteException();
@@ -557,11 +1163,40 @@
}
@Override
+ public List<MediaSessionCompat.QueueItem> getQueue() {
+ List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
+ if (queueObjs == null) {
+ return null;
+ }
+ List<MediaSessionCompat.QueueItem> queue =
+ new ArrayList<MediaSessionCompat.QueueItem>();
+ for (Object item : queueObjs) {
+ queue.add(MediaSessionCompat.QueueItem.obtain(item));
+ }
+ return queue;
+ }
+
+ @Override
+ public CharSequence getQueueTitle() {
+ return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return MediaControllerCompatApi21.getExtras(mControllerObj);
+ }
+
+ @Override
public int getRatingType() {
return MediaControllerCompatApi21.getRatingType(mControllerObj);
}
@Override
+ public long getFlags() {
+ return MediaControllerCompatApi21.getFlags(mControllerObj);
+ }
+
+ @Override
public PlaybackInfo getPlaybackInfo() {
Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
return volumeInfoObj != null ? new PlaybackInfo(
@@ -573,11 +1208,31 @@
}
@Override
+ public PendingIntent getSessionActivity() {
+ return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
+ }
+
+ @Override
+ public void setVolumeTo(int value, int flags) {
+ MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
+ }
+
+ @Override
+ public void adjustVolume(int direction, int flags) {
+ MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
+ }
+
+ @Override
public void sendCommand(String command, Bundle params, ResultReceiver cb) {
MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
}
@Override
+ public String getPackageName() {
+ return MediaControllerCompatApi21.getPackageName(mControllerObj);
+ }
+
+ @Override
public Object getMediaController() {
return mControllerObj;
}
@@ -635,5 +1290,34 @@
MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
rating != null ? rating.getRating() : null);
}
+
+ @Override
+ public void playFromMediaId(String mediaId, Bundle extras) {
+ MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
+ extras);
+ }
+
+ @Override
+ public void playFromSearch(String query, Bundle extras) {
+ MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
+ extras);
+ }
+
+ @Override
+ public void skipToQueueItem(long id) {
+ MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
+ }
+
+ @Override
+ public void sendCustomAction(CustomAction customAction, Bundle args) {
+ MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
+ customAction.getAction(), args);
+ }
+
+ @Override
+ public void sendCustomAction(String action, Bundle args) {
+ MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
+ args);
+ }
}
}
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.aidl b/v4/java/android/support/v4/media/session/MediaSessionCompat.aidl
new file mode 100644
index 0000000..d0c2f6f
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.aidl
@@ -0,0 +1,20 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media.session;
+
+parcelable MediaSessionCompat.Token;
+parcelable MediaSessionCompat.QueueItem;
+parcelable MediaSessionCompat.ResultReceiverWrapper;
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.java b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
index e92b09a..8f11ef4 100644
--- a/v4/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -17,18 +17,34 @@
package android.support.v4.media.session;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.VolumeProviderCompat;
import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
/**
* Allows interaction with media controllers, volume keys, media buttons, and
@@ -49,7 +65,8 @@
* When an app is finished performing playback it must call {@link #release()}
* to clean up the session and notify any controllers.
* <p>
- * MediaSession objects are thread safe.
+ * MediaSessionCompat objects are not thread safe and all calls should be made
+ * from the same thread.
* <p>
* This is a helper for accessing features in
* {@link android.media.session.MediaSession} introduced after API level 4 in a
@@ -57,6 +74,9 @@
*/
public class MediaSessionCompat {
private final MediaSessionImpl mImpl;
+ private final MediaControllerCompat mController;
+ private final ArrayList<OnActiveChangeListener>
+ mActiveListeners = new ArrayList<OnActiveChangeListener>();
/**
* Set this flag on the session to indicate that it can handle media button
@@ -75,8 +95,16 @@
*
* @param context The context.
* @param tag A short name for debugging purposes.
+ * @param mediaButtonEventReceiver The component name for your receiver.
+ * This must be non-null to support platform versions earlier
+ * than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+ * @param mbrIntent The PendingIntent for your receiver component that
+ * handles media button events. This is optional and will be used
+ * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
+ * later instead of the component name.
*/
- public MediaSessionCompat(Context context, String tag) {
+ public MediaSessionCompat(Context context, String tag, ComponentName mediaButtonEventReceiver,
+ PendingIntent mbrIntent) {
if (context == null) {
throw new IllegalArgumentException("context must not be null");
}
@@ -86,13 +114,16 @@
if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaSessionImplApi21(context, tag);
+ mImpl.setMediaButtonReceiver(mbrIntent);
} else {
- mImpl = new MediaSessionImplBase();
+ mImpl = new MediaSessionImplBase(context, tag, mediaButtonEventReceiver, mbrIntent);
}
+ mController = new MediaControllerCompat(context, this);
}
- private MediaSessionCompat(MediaSessionImpl impl) {
+ private MediaSessionCompat(Context context, MediaSessionImpl impl) {
mImpl = impl;
+ mController = new MediaControllerCompat(context, this);
}
/**
@@ -119,6 +150,35 @@
}
/**
+ * Set an intent for launching UI for this Session. This can be used as a
+ * quick link to an ongoing media screen. The intent should be for an
+ * activity that may be started using
+ * {@link Activity#startActivity(Intent)}.
+ *
+ * @param pi The intent to launch to show UI for this Session.
+ */
+ public void setSessionActivity(PendingIntent pi) {
+ mImpl.setSessionActivity(pi);
+ }
+
+ /**
+ * Set a pending intent for your media button receiver to allow restarting
+ * playback after the session has been stopped. If your app is started in
+ * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
+ * the pending intent.
+ * <p>
+ * This method will only work on
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier
+ * platform versions must include the media button receiver in the
+ * constructor.
+ *
+ * @param mbr The {@link PendingIntent} to send the media button event to.
+ */
+ public void setMediaButtonReceiver(PendingIntent mbr) {
+ mImpl.setMediaButtonReceiver(mbr);
+ }
+
+ /**
* Set any flags for the session.
*
* @param flags The flags to set for this session.
@@ -147,6 +207,11 @@
* current stream volume for this session. If {@link #setPlaybackToLocal}
* was previously called that stream will stop receiving volume changes for
* this session.
+ * <p>
+ * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}
+ * this will only allow an app to handle volume commands sent directly to
+ * the session by a {@link MediaControllerCompat}. System routing of volume
+ * keys will not use the volume provider.
*
* @param volumeProvider The provider that will handle volume changes. May
* not be null.
@@ -163,11 +228,19 @@
* set to false your session's controller may not be discoverable. You must
* set the session to active before it can start receiving media button
* events or transport commands.
+ * <p>
+ * On platforms earlier than
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP},
+ * {@link #setMediaButtonReceiver(PendingIntent)} must be called before
+ * setting this to true.
*
* @param active Whether this session is active or not.
*/
public void setActive(boolean active) {
mImpl.setActive(active);
+ for (OnActiveChangeListener listener : mActiveListeners) {
+ listener.onActiveChanged();
+ }
}
/**
@@ -205,10 +278,16 @@
/**
* Retrieve a token object that can be used by apps to create a
- * {@link MediaControllerCompat} for interacting with this session. The owner of
- * the session is responsible for deciding how to distribute these tokens.
+ * {@link MediaControllerCompat} for interacting with this session. The
+ * owner of the session is responsible for deciding how to distribute these
+ * tokens.
+ * <p>
+ * On platform versions before
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be
+ * used within your app as there is no way to guarantee other apps are using
+ * the same version of the support library.
*
- * @return A token that can be used to create a MediaController for this
+ * @return A token that can be used to create a media controller for this
* session.
*/
public Token getSessionToken() {
@@ -216,6 +295,16 @@
}
/**
+ * Get a controller for this session. This is a convenience method to avoid
+ * having to cache your own controller in process.
+ *
+ * @return A controller for this session.
+ */
+ public MediaControllerCompat getController() {
+ return mController;
+ }
+
+ /**
* Update the current playback state.
*
* @param state The current state of playback
@@ -235,27 +324,124 @@
}
/**
- * Gets the underlying framework {@link android.media.session.MediaSession} object.
+ * Update the list of items in the play queue. It is an ordered list and
+ * should contain the current item, and previous or upcoming items if they
+ * exist. Specify null if there is no current play queue.
+ * <p>
+ * The queue should be of reasonable size. If the play queue is unbounded
+ * within your app, it is better to send a reasonable amount in a sliding
+ * window instead.
+ *
+ * @param queue A list of items in the play queue.
+ */
+ public void setQueue(List<QueueItem> queue) {
+ mImpl.setQueue(queue);
+ }
+
+ /**
+ * Set the title of the play queue. The UI should display this title along
+ * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
+ * name.
+ *
+ * @param title The title of the play queue.
+ */
+ public void setQueueTitle(CharSequence title) {
+ mImpl.setQueueTitle(title);
+ }
+
+ /**
+ * Set the style of rating used by this session. Apps trying to set the
+ * rating should use this style. Must be one of the following:
+ * <ul>
+ * <li>{@link RatingCompat#RATING_NONE}</li>
+ * <li>{@link RatingCompat#RATING_3_STARS}</li>
+ * <li>{@link RatingCompat#RATING_4_STARS}</li>
+ * <li>{@link RatingCompat#RATING_5_STARS}</li>
+ * <li>{@link RatingCompat#RATING_HEART}</li>
+ * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
+ * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
+ * </ul>
+ */
+ public void setRatingType(int type) {
+ mImpl.setRatingType(type);
+ }
+
+ /**
+ * Set some extras that can be associated with the
+ * {@link MediaSessionCompat}. No assumptions should be made as to how a
+ * {@link MediaControllerCompat} will handle these extras. Keys should be
+ * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
+ *
+ * @param extras The extras associated with the session.
+ */
+ public void setExtras(Bundle extras) {
+ mImpl.setExtras(extras);
+ }
+
+ /**
+ * Gets the underlying framework {@link android.media.session.MediaSession}
+ * object.
* <p>
* This method is only supported on API 21+.
* </p>
*
* @return The underlying {@link android.media.session.MediaSession} object,
- * or null if none.
+ * or null if none.
*/
public Object getMediaSession() {
return mImpl.getMediaSession();
}
/**
+ * Gets the underlying framework {@link android.media.RemoteControlClient}
+ * object.
+ * <p>
+ * This method is only supported on APIs 14-20. On API 21+
+ * {@link #getMediaSession()} should be used instead.
+ *
+ * @return The underlying {@link android.media.RemoteControlClient} object,
+ * or null if none.
+ */
+ public Object getRemoteControlClient() {
+ return mImpl.getRemoteControlClient();
+ }
+
+ /**
+ * Adds a listener to be notified when the active status of this session
+ * changes. This is primarily used by the support library and should not be
+ * needed by apps.
+ *
+ * @param listener The listener to add.
+ */
+ public void addOnActiveChangeListener(OnActiveChangeListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener may not be null");
+ }
+ mActiveListeners.add(listener);
+ }
+
+ /**
+ * Stops the listener from being notified when the active status of this
+ * session changes.
+ *
+ * @param listener The listener to remove.
+ */
+ public void removeOnActiveChangeListener(OnActiveChangeListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener may not be null");
+ }
+ mActiveListeners.remove(listener);
+ }
+
+ /**
* Obtain a compat wrapper for an existing MediaSession.
*
* @param mediaSession The {@link android.media.session.MediaSession} to
* wrap.
* @return A compat wrapper for the provided session.
*/
- public static MediaSessionCompat obtain(Object mediaSession) {
- return new MediaSessionCompat(new MediaSessionImplApi21(mediaSession));
+ public static MediaSessionCompat obtain(Context context, Object mediaSession) {
+ return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession));
}
/**
@@ -302,6 +488,29 @@
}
/**
+ * Override to handle requests to play a specific mediaId that was
+ * provided by your app.
+ */
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ }
+
+ /**
+ * Override to handle requests to begin playback from a search query. An
+ * empty query indicates that the app may play any music. The
+ * implementation should attempt to make a smart choice about what to
+ * play.
+ */
+ public void onPlayFromSearch(String query, Bundle extras) {
+ }
+
+ /**
+ * Override to handle requests to play an item with a given id from the
+ * play queue.
+ */
+ public void onSkipToQueueItem(long id) {
+ }
+
+ /**
* Override to handle requests to pause playback.
*/
public void onPause() {
@@ -353,6 +562,18 @@
public void onSetRating(RatingCompat rating) {
}
+ /**
+ * Called when a {@link MediaControllerCompat} wants a
+ * {@link PlaybackStateCompat.CustomAction} to be performed.
+ *
+ * @param action The action that was originally sent in the
+ * {@link PlaybackStateCompat.CustomAction}.
+ * @param extras Optional extras specified by the
+ * {@link MediaControllerCompat}.
+ */
+ public void onCustomAction(String action, Bundle extras) {
+ }
+
private class StubApi21 implements MediaSessionCompatApi21.Callback {
@Override
@@ -371,6 +592,21 @@
}
@Override
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ Callback.this.onPlayFromMediaId(mediaId, extras);
+ }
+
+ @Override
+ public void onPlayFromSearch(String search, Bundle extras) {
+ Callback.this.onPlayFromSearch(search, extras);
+ }
+
+ @Override
+ public void onSkipToQueueItem(long id) {
+ Callback.this.onSkipToQueueItem(id);
+ }
+
+ @Override
public void onPause() {
Callback.this.onPause();
}
@@ -409,6 +645,11 @@
public void onSetRating(Object ratingObj) {
Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
}
+
+ @Override
+ public void onCustomAction(String action, Bundle extras) {
+ Callback.this.onCustomAction(action, extras);
+ }
}
}
@@ -418,20 +659,42 @@
* the session.
*/
public static final class Token implements Parcelable {
- private final Parcelable mInner;
+ private final Object mInner;
- Token(Parcelable inner) {
+ Token(Object inner) {
mInner = inner;
}
+ /**
+ * Creates a compat Token from a framework
+ * {@link android.media.session.MediaSession.Token} object.
+ * <p>
+ * This method is only supported on
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
+ * </p>
+ *
+ * @param token The framework token object.
+ * @return A compat Token for use with {@link MediaControllerCompat}.
+ */
+ public static Token fromToken(Object token) {
+ if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
+ return null;
+ }
+ return new Token(MediaSessionCompatApi21.verifyToken(token));
+ }
+
@Override
public int describeContents() {
- return mInner.describeContents();
+ return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mInner, flags);
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ dest.writeParcelable((Parcelable) mInner, flags);
+ } else {
+ dest.writeStrongBinder((IBinder) mInner);
+ }
}
/**
@@ -451,7 +714,13 @@
= new Parcelable.Creator<Token>() {
@Override
public Token createFromParcel(Parcel in) {
- return new Token(in.readParcelable(null));
+ Object inner;
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ inner = in.readParcelable(null);
+ } else {
+ inner = in.readStrongBinder();
+ }
+ return new Token(inner);
}
@Override
@@ -461,6 +730,174 @@
};
}
+ /**
+ * A single item that is part of the play queue. It contains a description
+ * of the item and its id in the queue.
+ */
+ public static final class QueueItem implements Parcelable {
+ /**
+ * This id is reserved. No items can be explicitly asigned this id.
+ */
+ public static final int UNKNOWN_ID = -1;
+
+ private final MediaDescriptionCompat mDescription;
+ private final long mId;
+
+ private Object mItem;
+
+ /**
+ * Create a new {@link MediaSessionCompat.QueueItem}.
+ *
+ * @param description The {@link MediaDescriptionCompat} for this item.
+ * @param id An identifier for this item. It must be unique within the
+ * play queue and cannot be {@link #UNKNOWN_ID}.
+ */
+ public QueueItem(MediaDescriptionCompat description, long id) {
+ this(null, description, id);
+ }
+
+ private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
+ if (description == null) {
+ throw new IllegalArgumentException("Description cannot be null.");
+ }
+ if (id == UNKNOWN_ID) {
+ throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
+ }
+ mDescription = description;
+ mId = id;
+ mItem = queueItem;
+ }
+
+ private QueueItem(Parcel in) {
+ mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
+ mId = in.readLong();
+ }
+
+ /**
+ * Get the description for this item.
+ */
+ public MediaDescriptionCompat getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Get the queue id for this item.
+ */
+ public long getQueueId() {
+ return mId;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mDescription.writeToParcel(dest, flags);
+ dest.writeLong(mId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Get the underlying
+ * {@link android.media.session.MediaSession.QueueItem}.
+ * <p>
+ * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
+ * is returned.
+ *
+ * @return The underlying
+ * {@link android.media.session.MediaSession.QueueItem} or null.
+ */
+ public Object getQueueItem() {
+ if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
+ return mItem;
+ }
+ mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
+ mId);
+ return mItem;
+ }
+
+ /**
+ * Obtain a compat wrapper for an existing QueueItem.
+ *
+ * @param queueItem The {@link android.media.session.MediaSession.QueueItem} to
+ * wrap.
+ * @return A compat wrapper for the provided item.
+ */
+ public static QueueItem obtain(Object queueItem) {
+ Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
+ MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
+ descriptionObj);
+ long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
+ return new QueueItem(queueItem, description, id);
+ }
+
+ public static final Creator<MediaSessionCompat.QueueItem>
+ CREATOR = new Creator<MediaSessionCompat.QueueItem>() {
+
+ @Override
+ public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
+ return new MediaSessionCompat.QueueItem(p);
+ }
+
+ @Override
+ public MediaSessionCompat.QueueItem[] newArray(int size) {
+ return new MediaSessionCompat.QueueItem[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "MediaSession.QueueItem {" +
+ "Description=" + mDescription +
+ ", Id=" + mId + " }";
+ }
+ }
+
+ /**
+ * This is a wrapper for {@link ResultReceiver} for sending over aidl
+ * interfaces. The framework version was not exposed to aidls until
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+ */
+ static final class ResultReceiverWrapper implements Parcelable {
+ private ResultReceiver mResultReceiver;
+
+ public ResultReceiverWrapper(ResultReceiver resultReceiver) {
+ mResultReceiver = resultReceiver;
+ }
+
+ ResultReceiverWrapper(Parcel in) {
+ mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
+ }
+
+ public static final Creator<ResultReceiverWrapper>
+ CREATOR = new Creator<ResultReceiverWrapper>() {
+ @Override
+ public ResultReceiverWrapper createFromParcel(Parcel p) {
+ return new ResultReceiverWrapper(p);
+ }
+
+ @Override
+ public ResultReceiverWrapper[] newArray(int size) {
+ return new ResultReceiverWrapper[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mResultReceiver.writeToParcel(dest, flags);
+ }
+ }
+
+ public interface OnActiveChangeListener {
+ void onActiveChanged();
+ }
+
interface MediaSessionImpl {
void setCallback(Callback callback, Handler handler);
void setFlags(int flags);
@@ -473,67 +910,888 @@
Token getSessionToken();
void setPlaybackState(PlaybackStateCompat state);
void setMetadata(MediaMetadataCompat metadata);
+
+ void setSessionActivity(PendingIntent pi);
+
+ void setMediaButtonReceiver(PendingIntent mbr);
+ void setQueue(List<QueueItem> queue);
+ void setQueueTitle(CharSequence title);
+
+ void setRatingType(int type);
+ void setExtras(Bundle extras);
+
Object getMediaSession();
+
+ Object getRemoteControlClient();
}
// TODO: compatibility implementation
static class MediaSessionImplBase implements MediaSessionImpl {
+ private final Context mContext;
+ private final ComponentName mComponentName;
+ private final PendingIntent mMediaButtonEventReceiver;
+ private final Object mRccObj;
+ private final MediaSessionStub mStub;
+ private final Token mToken;
+ private final MessageHandler mHandler;
+ private final String mPackageName;
+ private final String mTag;
+ private final AudioManager mAudioManager;
+
+ private final Object mLock = new Object();
+ private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
+ = new RemoteCallbackList<IMediaControllerCallback>();
+
+ private boolean mDestroyed = false;
+ private boolean mIsActive = false;
+ private boolean mIsRccRegistered = false;
+ private boolean mIsMbrRegistered = false;
+ private Callback mCallback;
+
+ private int mFlags;
+
+ private MediaMetadataCompat mMetadata;
+ private PlaybackStateCompat mState;
+ private PendingIntent mSessionActivity;
+ private List<QueueItem> mQueue;
+ private CharSequence mQueueTitle;
+ private int mRatingType;
+ private Bundle mExtras;
+
+ private int mVolumeType;
+ private int mLocalStream;
+ private VolumeProviderCompat mVolumeProvider;
+
+ private VolumeProviderCompat.Callback mVolumeCallback
+ = new VolumeProviderCompat.Callback() {
+ @Override
+ public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
+ if (mVolumeProvider != volumeProvider) {
+ return;
+ }
+ ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
+ volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
+ volumeProvider.getCurrentVolume());
+ sendVolumeInfoChanged(info);
+ }
+ };
+
+ public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
+ PendingIntent mbr) {
+ if (mbrComponent == null) {
+ throw new IllegalArgumentException(
+ "MediaButtonReceiver component may not be null.");
+ }
+ if (mbr == null) {
+ // construct a PendingIntent for the media button
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being
+ // registered
+ mediaButtonIntent.setComponent(mbrComponent);
+ mbr = PendingIntent.getBroadcast(context,
+ 0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
+ }
+ mContext = context;
+ mPackageName = context.getPackageName();
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mTag = tag;
+ mComponentName = mbrComponent;
+ mMediaButtonEventReceiver = mbr;
+ mStub = new MediaSessionStub();
+ mToken = new Token(mStub);
+ mHandler = new MessageHandler(Looper.myLooper());
+
+ mRatingType = RatingCompat.RATING_NONE;
+ mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ mLocalStream = AudioManager.STREAM_MUSIC;
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbr);
+ } else {
+ mRccObj = null;
+ }
+ }
+
@Override
- public void setCallback(Callback callback, Handler handler) {
+ public void setCallback(final Callback callback, Handler handler) {
+ if (callback == mCallback) {
+ return;
+ }
+ if (callback == null || android.os.Build.VERSION.SDK_INT < 18) {
+ // There's nothing to register on API < 18 since media buttons
+ // all go through the media button receiver
+ if (android.os.Build.VERSION.SDK_INT >= 18) {
+ MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
+ }
+ if (android.os.Build.VERSION.SDK_INT >= 19) {
+ MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
+ }
+ } else {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ MediaSessionCompatApi14.Callback cb14 = new MediaSessionCompatApi14.Callback() {
+ @Override
+ public void onStop() {
+ callback.onStop();
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ callback.onSkipToPrevious();
+ }
+
+ @Override
+ public void onSkipToNext() {
+ callback.onSkipToNext();
+ }
+
+ @Override
+ public void onSetRating(Object ratingObj) {
+ callback.onSetRating(RatingCompat.fromRating(ratingObj));
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ callback.onSeekTo(pos);
+ }
+
+ @Override
+ public void onRewind() {
+ callback.onRewind();
+ }
+
+ @Override
+ public void onPlay() {
+ callback.onPlay();
+ }
+
+ @Override
+ public void onPause() {
+ callback.onPause();
+ }
+
+ @Override
+ public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+ return callback.onMediaButtonEvent(mediaButtonIntent);
+ }
+
+ @Override
+ public void onFastForward() {
+ callback.onFastForward();
+ }
+
+ @Override
+ public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+ callback.onCommand(command, extras, cb);
+ }
+ };
+ if (android.os.Build.VERSION.SDK_INT >= 18) {
+ Object onPositionUpdateObj = MediaSessionCompatApi18
+ .createPlaybackPositionUpdateListener(cb14);
+ MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
+ onPositionUpdateObj);
+ }
+ if (android.os.Build.VERSION.SDK_INT >= 19) {
+ Object onMetadataUpdateObj = MediaSessionCompatApi19
+ .createMetadataUpdateListener(cb14);
+ MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
+ onMetadataUpdateObj);
+ }
+ }
+ mCallback = callback;
}
@Override
public void setFlags(int flags) {
+ synchronized (mLock) {
+ mFlags = flags;
+ }
+ update();
}
@Override
public void setPlaybackToLocal(int stream) {
+ if (mVolumeProvider != null) {
+ mVolumeProvider.setCallback(null);
+ }
+ mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
+ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
+ mAudioManager.getStreamMaxVolume(mLocalStream),
+ mAudioManager.getStreamVolume(mLocalStream));
+ sendVolumeInfoChanged(info);
}
@Override
public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
+ if (volumeProvider == null) {
+ throw new IllegalArgumentException("volumeProvider may not be null");
+ }
+ if (mVolumeProvider != null) {
+ mVolumeProvider.setCallback(null);
+ }
+ mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ mVolumeProvider = volumeProvider;
+ ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
+ mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
+ mVolumeProvider.getCurrentVolume());
+ sendVolumeInfoChanged(info);
+
+ volumeProvider.setCallback(mVolumeCallback);
}
@Override
public void setActive(boolean active) {
+ if (active == mIsActive) {
+ return;
+ }
+ mIsActive = active;
+ if (update()) {
+ setMetadata(mMetadata);
+ setPlaybackState(mState);
+ }
}
@Override
public boolean isActive() {
- return false;
+ return mIsActive;
}
@Override
public void sendSessionEvent(String event, Bundle extras) {
+ sendEvent(event, extras);
}
@Override
public void release() {
+ mIsActive = false;
+ mDestroyed = true;
+ update();
+ sendSessionDestroyed();
}
@Override
public Token getSessionToken() {
- return null;
+ return mToken;
}
@Override
public void setPlaybackState(PlaybackStateCompat state) {
+ synchronized (mLock) {
+ mState = state;
+ }
+ sendState(state);
+ if (!mIsActive) {
+ // Don't set the state until after the RCC is registered
+ return;
+ }
+ if (state == null) {
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
+ }
+ } else {
+ if (android.os.Build.VERSION.SDK_INT >= 18) {
+ MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
+ state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
+ } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+ MediaSessionCompatApi14.setState(mRccObj, state.getState());
+ }
+ }
}
@Override
public void setMetadata(MediaMetadataCompat metadata) {
+ synchronized (mLock) {
+ mMetadata = metadata;
+ }
+ sendMetadata(metadata);
+ if (!mIsActive) {
+ // Don't set metadata until after the rcc has been registered
+ return;
+ }
+ if (android.os.Build.VERSION.SDK_INT >= 19) {
+ boolean canRate = mState != null
+ && (mState.getActions() & PlaybackStateCompat.ACTION_SET_RATING) != 0;
+ MediaSessionCompatApi19.setMetadata(mRccObj,
+ metadata == null ? null : metadata.getBundle(), canRate);
+ } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+ MediaSessionCompatApi14.setMetadata(mRccObj,
+ metadata == null ? null : metadata.getBundle());
+ }
+ }
+
+ @Override
+ public void setSessionActivity(PendingIntent pi) {
+ synchronized (mLock) {
+ mSessionActivity = pi;
+ }
+ }
+
+ @Override
+ public void setMediaButtonReceiver(PendingIntent mbr) {
+ // Do nothing, changing this is not supported before API 21.
+ }
+
+ @Override
+ public void setQueue(List<QueueItem> queue) {
+ mQueue = queue;
+ sendQueue(queue);
+ }
+
+ @Override
+ public void setQueueTitle(CharSequence title) {
+ mQueueTitle = title;
+ sendQueueTitle(title);
}
@Override
public Object getMediaSession() {
return null;
}
+
+ @Override
+ public Object getRemoteControlClient() {
+ return mRccObj;
+ }
+
+ @Override
+ public void setRatingType(int type) {
+ mRatingType = type;
+ }
+
+ @Override
+ public void setExtras(Bundle extras) {
+ mExtras = extras;
+ }
+
+ // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
+ private boolean update() {
+ boolean registeredRcc = false;
+ if (mIsActive) {
+ // On API 8+ register a MBR if it's supported, unregister it
+ // if support was removed.
+ if (android.os.Build.VERSION.SDK_INT >= 8) {
+ if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
+ if (android.os.Build.VERSION.SDK_INT >= 18) {
+ MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
+ mMediaButtonEventReceiver);
+ } else {
+ MediaSessionCompatApi8.registerMediaButtonEventReceiver(mContext,
+ mComponentName);
+ }
+ mIsMbrRegistered = true;
+ } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
+ if (android.os.Build.VERSION.SDK_INT >= 18) {
+ MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
+ mMediaButtonEventReceiver);
+ } else {
+ MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
+ mComponentName);
+ }
+ mIsMbrRegistered = false;
+ }
+ }
+ // On API 14+ register a RCC if it's supported, unregister it if
+ // not.
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
+ MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
+ mIsRccRegistered = true;
+ registeredRcc = true;
+ } else if (mIsRccRegistered
+ && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
+ MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
+ mIsRccRegistered = false;
+ }
+ }
+ } else {
+ // When inactive remove any registered components.
+ if (mIsMbrRegistered) {
+ if (android.os.Build.VERSION.SDK_INT >= 18) {
+ MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
+ mMediaButtonEventReceiver);
+ } else {
+ MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
+ mComponentName);
+ }
+ mIsMbrRegistered = false;
+ }
+ if (mIsRccRegistered) {
+ MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
+ mIsRccRegistered = false;
+ }
+ }
+ return registeredRcc;
+ }
+
+ private void adjustVolume(int direction, int flags) {
+ if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ if (mVolumeProvider != null) {
+ mVolumeProvider.onAdjustVolume(direction);
+ }
+ } else {
+ mAudioManager.adjustStreamVolume(direction, mLocalStream, flags);
+ }
+ }
+
+ private void setVolumeTo(int value, int flags) {
+ if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ if (mVolumeProvider != null) {
+ mVolumeProvider.onSetVolumeTo(value);
+ }
+ } else {
+ mAudioManager.setStreamVolume(mLocalStream, value, flags);
+ }
+ }
+
+ private PlaybackStateCompat getStateWithUpdatedPosition() {
+ PlaybackStateCompat state;
+ long duration = -1;
+ synchronized (mLock) {
+ state = mState;
+ if (mMetadata != null
+ && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
+ duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
+ }
+ }
+
+ PlaybackStateCompat result = null;
+ if (state != null) {
+ if (state.getState() == PlaybackStateCompat.STATE_PLAYING
+ || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
+ || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
+ long updateTime = state.getLastPositionUpdateTime();
+ long currentTime = SystemClock.elapsedRealtime();
+ if (updateTime > 0) {
+ long position = (long) (state.getPlaybackSpeed()
+ * (currentTime - updateTime)) + state.getPosition();
+ if (duration >= 0 && position > duration) {
+ position = duration;
+ } else if (position < 0) {
+ position = 0;
+ }
+ PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
+ state);
+ builder.setState(state.getState(), position, state.getPlaybackSpeed(),
+ currentTime);
+ result = builder.build();
+ }
+ }
+ }
+ return result == null ? state : result;
+ }
+
+ private void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onVolumeInfoChanged(info);
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ }
+
+ private void sendSessionDestroyed() {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onSessionDestroyed();;
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ mControllerCallbacks.kill();
+ }
+
+ private void sendEvent(String event, Bundle extras) {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onEvent(event, extras);
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ }
+
+ private void sendState(PlaybackStateCompat state) {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onPlaybackStateChanged(state);
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ }
+
+ private void sendMetadata(MediaMetadataCompat metadata) {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onMetadataChanged(metadata);
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ }
+
+ private void sendQueue(List<QueueItem> queue) {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onQueueChanged(queue);
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ }
+
+ private void sendQueueTitle(CharSequence queueTitle) {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onQueueTitleChanged(queueTitle);
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ }
+
+ class MediaSessionStub extends IMediaSession.Stub {
+ @Override
+ public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
+ mHandler.post(MessageHandler.MSG_COMMAND,
+ new Command(command, args, cb.mResultReceiver));
+ }
+
+ @Override
+ public boolean sendMediaButton(KeyEvent mediaButton) {
+ boolean handlesMediaButtons =
+ (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
+ if (handlesMediaButtons) {
+ mHandler.post(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
+ }
+ return handlesMediaButtons;
+ }
+
+ @Override
+ public void registerCallbackListener(IMediaControllerCallback cb) {
+ // If this session is already destroyed tell the caller and
+ // don't add them.
+ if (mDestroyed) {
+ try {
+ cb.onSessionDestroyed();
+ } catch (Exception e) {
+ // ignored
+ }
+ return;
+ }
+ mControllerCallbacks.register(cb);
+ }
+
+ @Override
+ public void unregisterCallbackListener(IMediaControllerCallback cb) {
+ mControllerCallbacks.unregister(cb);
+ }
+
+ @Override
+ public String getPackageName() {
+ // mPackageName is final so doesn't need synchronize block
+ return mPackageName;
+ }
+
+ @Override
+ public String getTag() {
+ // mTag is final so doesn't need synchronize block
+ return mTag;
+ }
+
+ @Override
+ public PendingIntent getLaunchPendingIntent() {
+ synchronized (mLock) {
+ return mSessionActivity;
+ }
+ }
+
+ @Override
+ public long getFlags() {
+ synchronized (mLock) {
+ return mFlags;
+ }
+ }
+
+ @Override
+ public ParcelableVolumeInfo getVolumeAttributes() {
+ int controlType;
+ int max;
+ int current;
+ int stream;
+ int volumeType;
+ synchronized (mLock) {
+ volumeType = mVolumeType;
+ stream = mLocalStream;
+ VolumeProviderCompat vp = mVolumeProvider;
+ if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ controlType = vp.getVolumeControl();
+ max = vp.getMaxVolume();
+ current = vp.getCurrentVolume();
+ } else {
+ controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ max = mAudioManager.getStreamMaxVolume(stream);
+ current = mAudioManager.getStreamVolume(stream);
+ }
+ }
+ return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
+ }
+
+ @Override
+ public void adjustVolume(int direction, int flags, String packageName) {
+ MediaSessionImplBase.this.adjustVolume(direction, flags);
+ }
+
+ @Override
+ public void setVolumeTo(int value, int flags, String packageName) {
+ MediaSessionImplBase.this.setVolumeTo(value, flags);
+ }
+
+ @Override
+ public void play() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_PLAY);
+ }
+
+ @Override
+ public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+ }
+
+ @Override
+ public void playFromSearch(String query, Bundle extras) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_PLAY_SEARCH, query, extras);
+ }
+
+ @Override
+ public void skipToQueueItem(long id) {
+ mHandler.post(MessageHandler.MSG_SKIP_TO_ITEM, id);
+ }
+
+ @Override
+ public void pause() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_PAUSE);
+ }
+
+ @Override
+ public void stop() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_STOP);
+ }
+
+ @Override
+ public void next() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_NEXT);
+ }
+
+ @Override
+ public void previous() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_PREVIOUS);
+ }
+
+ @Override
+ public void fastForward() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_FAST_FORWARD);
+ }
+
+ @Override
+ public void rewind() throws RemoteException {
+ mHandler.post(MessageHandler.MSG_REWIND);
+ }
+
+ @Override
+ public void seekTo(long pos) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_SEEK_TO, pos);
+ }
+
+ @Override
+ public void rate(RatingCompat rating) throws RemoteException {
+ mHandler.post(MessageHandler.MSG_RATE, rating);
+ }
+
+ @Override
+ public void sendCustomAction(String action, Bundle args)
+ throws RemoteException {
+ mHandler.post(MessageHandler.MSG_CUSTOM_ACTION, action, args);
+ }
+
+ @Override
+ public MediaMetadataCompat getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public PlaybackStateCompat getPlaybackState() {
+ return getStateWithUpdatedPosition();
+ }
+
+ @Override
+ public List<QueueItem> getQueue() {
+ synchronized (mLock) {
+ return mQueue;
+ }
+ }
+
+ @Override
+ public CharSequence getQueueTitle() {
+ return mQueueTitle;
+ }
+
+ @Override
+ public Bundle getExtras() {
+ synchronized (mLock) {
+ return mExtras;
+ }
+ }
+
+ @Override
+ public int getRatingType() {
+ return mRatingType;
+ }
+
+ @Override
+ public boolean isTransportControlEnabled() {
+ return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
+ }
+ }
+
+ private static final class Command {
+ public final String command;
+ public final Bundle extras;
+ public final ResultReceiver stub;
+
+ public Command(String command, Bundle extras, ResultReceiver stub) {
+ this.command = command;
+ this.extras = extras;
+ this.stub = stub;
+ }
+ }
+
+ private class MessageHandler extends Handler {
+
+ private static final int MSG_PLAY = 1;
+ private static final int MSG_PLAY_MEDIA_ID = 2;
+ private static final int MSG_PLAY_SEARCH = 3;
+ private static final int MSG_SKIP_TO_ITEM = 4;
+ private static final int MSG_PAUSE = 5;
+ private static final int MSG_STOP = 6;
+ private static final int MSG_NEXT = 7;
+ private static final int MSG_PREVIOUS = 8;
+ private static final int MSG_FAST_FORWARD = 9;
+ private static final int MSG_REWIND = 10;
+ private static final int MSG_SEEK_TO = 11;
+ private static final int MSG_RATE = 12;
+ private static final int MSG_CUSTOM_ACTION = 13;
+ private static final int MSG_MEDIA_BUTTON = 14;
+ private static final int MSG_COMMAND = 15;
+ private static final int MSG_ADJUST_VOLUME = 16;
+ private static final int MSG_SET_VOLUME = 17;
+
+ public MessageHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void post(int what, Object obj, Bundle bundle) {
+ Message msg = obtainMessage(what, obj);
+ msg.setData(bundle);
+ msg.sendToTarget();
+ }
+
+ public void post(int what, Object obj) {
+ obtainMessage(what, obj).sendToTarget();
+ }
+
+ public void post(int what) {
+ post(what, null);
+ }
+
+ public void post(int what, Object obj, int arg1) {
+ obtainMessage(what, arg1, 0, obj).sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mCallback == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_PLAY:
+ mCallback.onPlay();
+ break;
+ case MSG_PLAY_MEDIA_ID:
+ mCallback.onPlayFromMediaId((String) msg.obj, msg.getData());
+ break;
+ case MSG_PLAY_SEARCH:
+ mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
+ break;
+ case MSG_SKIP_TO_ITEM:
+ mCallback.onSkipToQueueItem((Long) msg.obj);
+ break;
+ case MSG_PAUSE:
+ mCallback.onPause();
+ break;
+ case MSG_STOP:
+ mCallback.onStop();
+ break;
+ case MSG_NEXT:
+ mCallback.onSkipToNext();
+ break;
+ case MSG_PREVIOUS:
+ mCallback.onSkipToPrevious();
+ break;
+ case MSG_FAST_FORWARD:
+ mCallback.onFastForward();
+ break;
+ case MSG_REWIND:
+ mCallback.onRewind();
+ break;
+ case MSG_SEEK_TO:
+ mCallback.onSeekTo((Long) msg.obj);
+ break;
+ case MSG_RATE:
+ mCallback.onSetRating((RatingCompat) msg.obj);
+ break;
+ case MSG_CUSTOM_ACTION:
+ mCallback.onCustomAction((String) msg.obj, msg.getData());
+ break;
+ case MSG_MEDIA_BUTTON:
+ mCallback.onMediaButtonEvent((Intent) msg.obj);
+ break;
+ case MSG_COMMAND:
+ Command cmd = (Command) msg.obj;
+ mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
+ break;
+ case MSG_ADJUST_VOLUME:
+ adjustVolume((int) msg.obj, 0);
+ break;
+ case MSG_SET_VOLUME:
+ setVolumeTo((int) msg.obj, 0);
+ break;
+ }
+ }
+ }
}
static class MediaSessionImplApi21 implements MediaSessionImpl {
private final Object mSessionObj;
private final Token mToken;
+ private PendingIntent mMediaButtonIntent;
+
public MediaSessionImplApi21(Context context, String tag) {
mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
@@ -601,8 +1859,55 @@
}
@Override
+ public void setSessionActivity(PendingIntent pi) {
+ MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
+ }
+
+ @Override
+ public void setMediaButtonReceiver(PendingIntent mbr) {
+ mMediaButtonIntent = mbr;
+ MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
+ }
+
+ @Override
+ public void setQueue(List<QueueItem> queue) {
+ List<Object> queueObjs = null;
+ if (queue != null) {
+ queueObjs = new ArrayList<Object>();
+ for (QueueItem item : queue) {
+ queueObjs.add(item.getQueueItem());
+ }
+ }
+ MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
+ }
+
+ @Override
+ public void setQueueTitle(CharSequence title) {
+ MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
+ }
+
+ @Override
+ public void setRatingType(int type) {
+ if (android.os.Build.VERSION.SDK_INT < 22) {
+ // TODO figure out 21 implementation
+ } else {
+ MediaSessionCompatApi22.setRatingType(mSessionObj, type);
+ }
+ }
+
+ @Override
+ public void setExtras(Bundle extras) {
+ MediaSessionCompatApi21.setExtras(mSessionObj, extras);
+ }
+
+ @Override
public Object getMediaSession() {
return mSessionObj;
}
+
+ @Override
+ public Object getRemoteControlClient() {
+ return null;
+ }
}
}
diff --git a/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.aidl b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.aidl
new file mode 100644
index 0000000..2e77c4f
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media.session;
+
+parcelable ParcelableVolumeInfo;
diff --git a/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.java b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.java
new file mode 100644
index 0000000..678b33e
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.java
@@ -0,0 +1,77 @@
+/* Copyright 2014, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Convenience class for passing information about the audio configuration of a
+ * {@link MediaSessionCompat}.
+ */
+public class ParcelableVolumeInfo implements Parcelable {
+ public int volumeType;
+ public int audioStream;
+ public int controlType;
+ public int maxVolume;
+ public int currentVolume;
+
+ public ParcelableVolumeInfo(int volumeType, int audioStream, int controlType,
+ int maxVolume,
+ int currentVolume) {
+ this.volumeType = volumeType;
+ this.audioStream = audioStream;
+ this.controlType = controlType;
+ this.maxVolume = maxVolume;
+ this.currentVolume = currentVolume;
+ }
+
+ public ParcelableVolumeInfo(Parcel from) {
+ volumeType = from.readInt();
+ controlType = from.readInt();
+ maxVolume = from.readInt();
+ currentVolume = from.readInt();
+ audioStream = from.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(volumeType);
+ dest.writeInt(controlType);
+ dest.writeInt(maxVolume);
+ dest.writeInt(currentVolume);
+ dest.writeInt(audioStream);
+ }
+
+
+ public static final Parcelable.Creator<ParcelableVolumeInfo> CREATOR
+ = new Parcelable.Creator<ParcelableVolumeInfo>() {
+ @Override
+ public ParcelableVolumeInfo createFromParcel(Parcel in) {
+ return new ParcelableVolumeInfo(in);
+ }
+
+ @Override
+ public ParcelableVolumeInfo[] newArray(int size) {
+ return new ParcelableVolumeInfo[size];
+ }
+ };
+}
diff --git a/v4/java/android/support/v4/media/session/PlaybackStateCompat.aidl b/v4/java/android/support/v4/media/session/PlaybackStateCompat.aidl
new file mode 100644
index 0000000..3d4ef59
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/PlaybackStateCompat.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media.session;
+
+parcelable PlaybackStateCompat;
diff --git a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
index 9152ab7..cb5fdb8 100644
--- a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -16,6 +16,7 @@
package android.support.v4.media.session;
import android.os.Build;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -405,6 +406,176 @@
};
/**
+ * {@link PlaybackStateCompat.CustomAction CustomActions} can be used to
+ * extend the capabilities of the standard transport controls by exposing
+ * app specific actions to {@link MediaControllerCompat Controllers}.
+ */
+ public static final class CustomAction implements Parcelable {
+ private final String mAction;
+ private final CharSequence mName;
+ private final int mIcon;
+ private final Bundle mExtras;
+
+ /**
+ * Use {@link PlaybackStateCompat.CustomAction.Builder#build()}.
+ */
+ private CustomAction(String action, CharSequence name, int icon, Bundle extras) {
+ mAction = action;
+ mName = name;
+ mIcon = icon;
+ mExtras = extras;
+ }
+
+ private CustomAction(Parcel in) {
+ mAction = in.readString();
+ mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mIcon = in.readInt();
+ mExtras = in.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mAction);
+ TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeInt(mIcon);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR
+ = new Parcelable.Creator<PlaybackStateCompat.CustomAction>() {
+
+ @Override
+ public PlaybackStateCompat.CustomAction createFromParcel(Parcel p) {
+ return new PlaybackStateCompat.CustomAction(p);
+ }
+
+ @Override
+ public PlaybackStateCompat.CustomAction[] newArray(int size) {
+ return new PlaybackStateCompat.CustomAction[size];
+ }
+ };
+
+ /**
+ * Returns the action of the {@link CustomAction}.
+ *
+ * @return The action of the {@link CustomAction}.
+ */
+ public String getAction() {
+ return mAction;
+ }
+
+ /**
+ * Returns the display name of this action. e.g. "Favorite"
+ *
+ * @return The display name of this {@link CustomAction}.
+ */
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the resource id of the icon in the {@link MediaSessionCompat
+ * Session's} package.
+ *
+ * @return The resource id of the icon in the {@link MediaSessionCompat
+ * Session's} package.
+ */
+ public int getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns extras which provide additional application-specific
+ * information about the action, or null if none. These arguments are
+ * meant to be consumed by a {@link MediaControllerCompat} if it knows
+ * how to handle them.
+ *
+ * @return Optional arguments for the {@link CustomAction}.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public String toString() {
+ return "Action:" +
+ "mName='" + mName +
+ ", mIcon=" + mIcon +
+ ", mExtras=" + mExtras;
+ }
+
+ /**
+ * Builder for {@link CustomAction} objects.
+ */
+ public static final class Builder {
+ private final String mAction;
+ private final CharSequence mName;
+ private final int mIcon;
+ private Bundle mExtras;
+
+ /**
+ * Creates a {@link CustomAction} builder with the id, name, and
+ * icon set.
+ *
+ * @param action The action of the {@link CustomAction}.
+ * @param name The display name of the {@link CustomAction}. This
+ * name will be displayed along side the action if the UI
+ * supports it.
+ * @param icon The icon resource id of the {@link CustomAction}.
+ * This resource id must be in the same package as the
+ * {@link MediaSessionCompat}. It will be displayed with
+ * the custom action if the UI supports it.
+ */
+ public Builder(String action, CharSequence name, int icon) {
+ if (TextUtils.isEmpty(action)) {
+ throw new IllegalArgumentException(
+ "You must specify an action to build a CustomAction.");
+ }
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException(
+ "You must specify a name to build a CustomAction.");
+ }
+ if (icon == 0) {
+ throw new IllegalArgumentException(
+ "You must specify an icon resource id to build a CustomAction.");
+ }
+ mAction = action;
+ mName = name;
+ mIcon = icon;
+ }
+
+ /**
+ * Set optional extras for the {@link CustomAction}. These extras
+ * are meant to be consumed by a {@link MediaControllerCompat} if it
+ * knows how to handle them. Keys should be fully qualified (e.g.
+ * "com.example.MY_ARG") to avoid collisions.
+ *
+ * @param extras Optional extras for the {@link CustomAction}.
+ * @return this.
+ */
+ public Builder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Build and return the {@link CustomAction} instance with the
+ * specified values.
+ *
+ * @return A new {@link CustomAction} instance.
+ */
+ public CustomAction build() {
+ return new CustomAction(mAction, mName, mIcon, mExtras);
+ }
+ }
+ }
+
+ /**
* Builder for {@link PlaybackStateCompat} objects.
*/
public static final class Builder {
@@ -441,12 +612,12 @@
/**
* Set the current state of playback.
* <p>
- * The position must be in ms and indicates the current playback position
- * within the track. If the position is unknown use
+ * The position must be in ms and indicates the current playback
+ * position within the track. If the position is unknown use
* {@link #PLAYBACK_POSITION_UNKNOWN}.
* <p>
- * The rate is a multiple of normal playback and should be 0 when paused and
- * negative when rewinding. Normal playback rate is 1.0.
+ * The rate is a multiple of normal playback and should be 0 when paused
+ * and negative when rewinding. Normal playback rate is 1.0.
* <p>
* The state must be one of the following:
* <ul>
@@ -462,28 +633,66 @@
*
* @param state The current state of playback.
* @param position The position in the current track in ms.
- * @param playbackRate The current rate of playback as a multiple of normal
- * playback.
+ * @param playbackSpeed The current rate of playback as a multiple of
+ * normal playback.
*/
- public void setState(int state, long position, float playbackRate) {
- this.mState = state;
- this.mPosition = position;
- this.mRate = playbackRate;
- mUpdateTime = SystemClock.elapsedRealtime();
+ public Builder setState(int state, long position, float playbackSpeed) {
+ return setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());
+ }
+
+ /**
+ * Set the current state of playback.
+ * <p>
+ * The position must be in ms and indicates the current playback
+ * position within the track. If the position is unknown use
+ * {@link #PLAYBACK_POSITION_UNKNOWN}.
+ * <p>
+ * The rate is a multiple of normal playback and should be 0 when paused
+ * and negative when rewinding. Normal playback rate is 1.0.
+ * <p>
+ * The state must be one of the following:
+ * <ul>
+ * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
+ * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
+ * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
+ * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
+ * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
+ * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
+ * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
+ * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
+ * </ul>
+ *
+ * @param state The current state of playback.
+ * @param position The position in the current item in ms.
+ * @param playbackSpeed The current speed of playback as a multiple of
+ * normal playback.
+ * @param updateTime The time in the {@link SystemClock#elapsedRealtime}
+ * timebase that the position was updated at.
+ * @return this
+ */
+ public Builder setState(int state, long position, float playbackSpeed, long updateTime) {
+ mState = state;
+ mPosition = position;
+ mUpdateTime = updateTime;
+ mRate = playbackSpeed;
+ return this;
}
/**
* Set the current buffered position in ms. This is the farthest
* playback point that can be reached from the current position using
* only buffered content.
+ *
+ * @return this
*/
- public void setBufferedPosition(long bufferPosition) {
+ public Builder setBufferedPosition(long bufferPosition) {
mBufferedPosition = bufferPosition;
+ return this;
}
/**
- * Set the current capabilities available on this session. This should use a
- * bitmask of the available capabilities.
+ * Set the current capabilities available on this session. This should
+ * use a bitmask of the available capabilities.
* <ul>
* <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
* <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
@@ -495,17 +704,23 @@
* <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
* <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
* </ul>
+ *
+ * @return this
*/
- public void setActions(long capabilities) {
+ public Builder setActions(long capabilities) {
mActions = capabilities;
+ return this;
}
/**
- * Set a user readable error message. This should be set when the state is
- * {@link PlaybackStateCompat#STATE_ERROR}.
+ * Set a user readable error message. This should be set when the state
+ * is {@link PlaybackStateCompat#STATE_ERROR}.
+ *
+ * @return this
*/
- public void setErrorMessage(CharSequence errorMessage) {
+ public Builder setErrorMessage(CharSequence errorMessage) {
mErrorMessage = errorMessage;
+ return this;
}
/**
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index d883e00..aaabf04 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -23,6 +23,7 @@
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
import android.util.Log;
@@ -261,7 +262,7 @@
public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
- public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate);
+ public void setAccessibilityDelegate(View v, @Nullable AccessibilityDelegateCompat delegate);
public boolean hasAccessibilityDelegate(View v);
public boolean hasTransientState(View view);
public void setHasTransientState(View view, boolean hasTransientState);
@@ -271,6 +272,7 @@
public void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
public int getImportantForAccessibility(View view);
public void setImportantForAccessibility(View view, int mode);
+ public boolean isImportantForAccessibility(View view);
public boolean performAccessibilityAction(View view, int action, Bundle arguments);
public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
public float getAlpha(View view);
@@ -330,8 +332,11 @@
public void requestApplyInsets(View view);
public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
public boolean getFitsSystemWindows(View view);
+ void setFitsSystemWindows(View view, boolean fitSystemWindows);
void jumpDrawablesToCurrentState(View v);
void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener);
+ void setSaveFromParentEnabled(View view, boolean enabled);
+ void setActivated(View view, boolean activated);
}
static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -399,6 +404,9 @@
public void setImportantForAccessibility(View view, int mode) {
}
+ public boolean isImportantForAccessibility(View view) {
+ return true;
+ }
public boolean performAccessibilityAction(View view, int action, Bundle arguments) {
return false;
}
@@ -715,6 +723,11 @@
}
@Override
+ public void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+ // noop
+ }
+
+ @Override
public void jumpDrawablesToCurrentState(View view) {
// Do nothing; API didn't exist.
}
@@ -724,6 +737,16 @@
OnApplyWindowInsetsListener listener) {
// noop
}
+
+ @Override
+ public void setSaveFromParentEnabled(View v, boolean enabled) {
+ // noop
+ }
+
+ @Override
+ public void setActivated(View view, boolean activated) {
+ // noop
+ }
}
static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -893,6 +916,16 @@
public void jumpDrawablesToCurrentState(View view) {
ViewCompatHC.jumpDrawablesToCurrentState(view);
}
+
+ @Override
+ public void setSaveFromParentEnabled(View view, boolean enabled) {
+ ViewCompatHC.setSaveFromParentEnabled(view, enabled);
+ }
+
+ @Override
+ public void setActivated(View view, boolean activated) {
+ ViewCompatHC.setActivated(view, activated);
+ }
}
static class ICSViewCompatImpl extends HCViewCompatImpl {
@@ -919,8 +952,10 @@
ViewCompatICS.onInitializeAccessibilityNodeInfo(v, info.getInfo());
}
@Override
- public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
- ViewCompatICS.setAccessibilityDelegate(v, delegate.getBridge());
+ public void setAccessibilityDelegate(View v,
+ @Nullable AccessibilityDelegateCompat delegate) {
+ ViewCompatICS.setAccessibilityDelegate(v,
+ delegate == null ? null : delegate.getBridge());
}
@Override
@@ -959,6 +994,11 @@
}
return vpa;
}
+
+ @Override
+ public void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+ ViewCompatICS.setFitsSystemWindows(view, fitSystemWindows);
+ }
}
static class JBViewCompatImpl extends ICSViewCompatImpl {
@@ -1144,6 +1184,11 @@
public void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener) {
ViewCompatApi21.setOnApplyWindowInsetsListener(view, listener);
}
+
+ @Override
+ public boolean isImportantForAccessibility(View view) {
+ return ViewCompatApi21.isImportantForAccessibility(view);
+ }
}
static final ViewCompatImpl IMPL;
@@ -2243,6 +2288,16 @@
}
/**
+ * Sets whether or not this view should account for system screen decorations
+ * such as the status bar and inset its content; that is, controlling whether
+ * the default implementation of {@link View#fitSystemWindows(Rect)} will be
+ * executed. See that method for more details.
+ */
+ public static void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+ IMPL.setFitsSystemWindows(view, fitSystemWindows);
+ }
+
+ /**
* On API 11 devices and above, call <code>Drawable.jumpToCurrentState()</code>
* on all Drawable objects associated with this view.
* <p>
@@ -2262,5 +2317,29 @@
IMPL.setOnApplyWindowInsetsListener(v, listener);
}
+ /**
+ * Controls whether the entire hierarchy under this view will save its
+ * state when a state saving traversal occurs from its parent.
+ *
+ * @param enabled Set to false to <em>disable</em> state saving, or true
+ * (the default) to allow it.
+ */
+ public static void setSaveFromParentEnabled(View v, boolean enabled) {
+ IMPL.setSaveFromParentEnabled(v, enabled);
+ }
+
+ /**
+ * Changes the activated state of this view. A view can be activated or not.
+ * Note that activation is not the same as selection. Selection is
+ * a transient property, representing the view (hierarchy) the user is
+ * currently interacting with. Activation is a longer-term state that the
+ * user can move views in and out of.
+ *
+ * @param activated true if the view must be activated, false otherwise
+ */
+ public static void setActivated(View view, boolean activated) {
+ IMPL.setActivated(view, activated);
+ }
+
// TODO: getters for various view properties (rotation, etc)
}
diff --git a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
index 0acc4b9..8813929 100644
--- a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
+++ b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
@@ -300,7 +300,10 @@
@Override
public void run() {
- startAnimation(mVpa, mViewRef.get());
+ final View view = mViewRef.get();
+ if (view != null) {
+ startAnimation(mVpa, view);
+ }
}
};
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 88bd985..055598f 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -36,6 +36,16 @@
public static class AccessibilityActionCompat {
private final Object mAction;
+ /**
+ * Creates a new instance.
+ *
+ * @param actionId The action id.
+ * @param label The action label.
+ */
+ public AccessibilityActionCompat(int actionId, CharSequence label) {
+ this(IMPL.newAccessibilityAction(actionId, label));
+ }
+
private AccessibilityActionCompat(Object action) {
mAction = action;
}
@@ -46,7 +56,7 @@
* @return The action id.
*/
public int getId() {
- return AccessibilityNodeInfoCompatApi21.AccessibilityAction.getId(mAction);
+ return IMPL.getAccessibilityActionId(mAction);
}
/**
@@ -56,7 +66,7 @@
* @return The label.
*/
public CharSequence getLabel() {
- return AccessibilityNodeInfoCompatApi21.AccessibilityAction.getLabel(mAction);
+ return IMPL.getAccessibilityActionLabel(mAction);
}
}
@@ -176,6 +186,7 @@
}
static interface AccessibilityNodeInfoImpl {
+ public Object newAccessibilityAction(int actionId, CharSequence label);
public Object obtain();
public Object obtain(View source);
public Object obtain(Object info);
@@ -191,6 +202,9 @@
public void addChild(Object info, View child, int virtualDescendantId);
public int getActions(Object info);
public void addAction(Object info, int action);
+ public void addAction(Object info, Object action);
+ public int getAccessibilityActionId(Object action);
+ public CharSequence getAccessibilityActionLabel(Object action);
public boolean performAction(Object info, int action);
public boolean performAction(Object info, int action, Bundle arguments);
public void setMovementGranularities(Object info, int granularities);
@@ -246,7 +260,6 @@
public void setCollectionItemInfo(Object info, Object collectionItemInfo);
public Object getRangeInfo(Object info);
public List<Object> getActionList(Object info);
- public void addAction(Object info, int id, CharSequence label);
public Object obtainCollectionInfo(int rowCount, int columnCount, boolean hierarchical,
int selectionMode);
public int getCollectionInfoColumnCount(Object info);
@@ -260,10 +273,21 @@
public int getCollectionItemRowSpan(Object info);
public boolean isCollectionItemHeading(Object info);
public boolean isCollectionItemSelected(Object info);
+ public AccessibilityNodeInfoCompat getTraversalBefore(Object info);
+ public void setTraversalBefore(Object info, View view);
+ public void setTraversalBefore(Object info, View root, int virtualDescendantId);
+ public AccessibilityNodeInfoCompat getTraversalAfter(Object info);
+ public void setTraversalAfter(Object info, View view);
+ public void setTraversalAfter(Object info, View root, int virtualDescendantId);
}
static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl {
@Override
+ public Object newAccessibilityAction(int actionId, CharSequence label) {
+ return null;
+ }
+
+ @Override
public Object obtain() {
return null;
}
@@ -289,6 +313,21 @@
}
@Override
+ public void addAction(Object info, Object action) {
+
+ }
+
+ @Override
+ public int getAccessibilityActionId(Object action) {
+ return 0;
+ }
+
+ @Override
+ public CharSequence getAccessibilityActionLabel(Object action) {
+ return null;
+ }
+
+ @Override
public void addChild(Object info, View child) {
}
@@ -612,10 +651,6 @@
}
@Override
- public void addAction(Object info, int id, CharSequence label) {
- }
-
- @Override
public Object obtainCollectionInfo(int rowCount, int columnCount, boolean hierarchical,
int selectionMode) {
return null;
@@ -671,6 +706,32 @@
public boolean isCollectionItemSelected(Object info) {
return false;
}
+
+ @Override
+ public AccessibilityNodeInfoCompat getTraversalBefore(Object info) {
+ return null;
+ }
+
+ @Override
+ public void setTraversalBefore(Object info, View view) {
+ }
+
+ @Override
+ public void setTraversalBefore(Object info, View root, int virtualDescendantId) {
+ }
+
+ @Override
+ public AccessibilityNodeInfoCompat getTraversalAfter(Object info) {
+ return null;
+ }
+
+ @Override
+ public void setTraversalAfter(Object info, View view) {
+ }
+
+ @Override
+ public void setTraversalAfter(Object info, View root, int virtualDescendantId) {
+ }
}
static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl {
@@ -908,13 +969,6 @@
public void recycle(Object info) {
AccessibilityNodeInfoCompatIcs.recycle(info);
}
-
- @Override
- public void addAction(Object info, int id, CharSequence label) {
- if (Integer.bitCount(id) == 1) {
- addAction(info, id);
- }
- }
}
static class AccessibilityNodeInfoJellybeanImpl extends AccessibilityNodeInfoIcsImpl {
@@ -1090,6 +1144,11 @@
static class AccessibilityNodeInfoApi21Impl extends AccessibilityNodeInfoKitKatImpl {
@Override
+ public Object newAccessibilityAction(int actionId, CharSequence label) {
+ return AccessibilityNodeInfoCompatApi21.newAccessibilityAction(actionId, label);
+ }
+
+ @Override
public List<Object> getActionList(Object info) {
return AccessibilityNodeInfoCompatApi21.getActionList(info);
}
@@ -1102,8 +1161,18 @@
}
@Override
- public void addAction(Object info, int id, CharSequence label) {
- AccessibilityNodeInfoCompatApi21.addAction(info, id, label);
+ public void addAction(Object info, Object action) {
+ AccessibilityNodeInfoCompatApi21.addAction(info, action);
+ }
+
+ @Override
+ public int getAccessibilityActionId(Object action) {
+ return AccessibilityNodeInfoCompatApi21.getAccessibilityActionId(action);
+ }
+
+ @Override
+ public CharSequence getAccessibilityActionLabel(Object action) {
+ return AccessibilityNodeInfoCompatApi21.getAccessibilityActionLabel(action);
}
@Override
@@ -1119,8 +1188,52 @@
}
}
+ static class AccessibilityNodeInfoApi22Impl extends AccessibilityNodeInfoApi21Impl {
+ @Override
+ public AccessibilityNodeInfoCompat getTraversalBefore(Object info) {
+ Object nodeInfo = AccessibilityNodeInfoCompatApi22.getTraversalBefore(info);
+ if (nodeInfo == null) {
+ return null;
+ }
+
+ return new AccessibilityNodeInfoCompat(nodeInfo);
+ }
+
+ @Override
+ public void setTraversalBefore(Object info, View view) {
+ AccessibilityNodeInfoCompatApi22.setTraversalBefore(info, view);
+ }
+
+ @Override
+ public void setTraversalBefore(Object info, View root, int virtualDescendantId) {
+ AccessibilityNodeInfoCompatApi22.setTraversalBefore(info, root, virtualDescendantId);
+ }
+
+ @Override
+ public AccessibilityNodeInfoCompat getTraversalAfter(Object info) {
+ Object nodeInfo = AccessibilityNodeInfoCompatApi22.getTraversalAfter(info);
+ if (nodeInfo == null) {
+ return null;
+ }
+
+ return new AccessibilityNodeInfoCompat(nodeInfo);
+ }
+
+ @Override
+ public void setTraversalAfter(Object info, View view) {
+ AccessibilityNodeInfoCompatApi22.setTraversalAfter(info, view);
+ }
+
+ @Override
+ public void setTraversalAfter(Object info, View root, int virtualDescendantId) {
+ AccessibilityNodeInfoCompatApi22.setTraversalAfter(info, root, virtualDescendantId);
+ }
+ }
+
static {
- if (Build.VERSION.SDK_INT >= 21) {
+ if (Build.VERSION.SDK_INT >= 22) {
+ IMPL = new AccessibilityNodeInfoApi22Impl();
+ } else if (Build.VERSION.SDK_INT >= 21) {
IMPL = new AccessibilityNodeInfoApi21Impl();
} else if (Build.VERSION.SDK_INT >= 19) { // KitKat
IMPL = new AccessibilityNodeInfoKitKatImpl();
@@ -1693,6 +1806,21 @@
}
/**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}. This class is
+ * made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action.
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addAction(AccessibilityActionCompat action) {
+ IMPL.addAction(mInfo, action.mAction);
+ }
+
+ /**
* Performs an action on the node.
* <p>
* <strong>Note:</strong> An action can be performed only if the request is
@@ -2389,14 +2517,18 @@
* @return A list of AccessibilityActions.
*/
public List<AccessibilityActionCompat> getActionList() {
- List<AccessibilityActionCompat> result = new ArrayList<AccessibilityActionCompat>();
List<Object> actions = IMPL.getActionList(mInfo);
- final int actionCount = actions.size();
- for (int i = 0; i < actionCount; i++) {
- Object action = actions.get(i);
- result.add(new AccessibilityActionCompat(action));
+ if (actions != null) {
+ List<AccessibilityActionCompat> result = new ArrayList<AccessibilityActionCompat>();
+ final int actionCount = actions.size();
+ for (int i = 0; i < actionCount; i++) {
+ Object action = actions.get(i);
+ result.add(new AccessibilityActionCompat(action));
+ }
+ return result;
+ } else {
+ return Collections.<AccessibilityActionCompat>emptyList();
}
- return result;
}
diff --git a/v4/java/android/support/v4/widget/CircleImageView.java b/v4/java/android/support/v4/widget/CircleImageView.java
index 5010680..246ffa7 100644
--- a/v4/java/android/support/v4/widget/CircleImageView.java
+++ b/v4/java/android/support/v4/widget/CircleImageView.java
@@ -17,7 +17,6 @@
package android.support.v4.widget;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -68,7 +67,7 @@
ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
KEY_SHADOW_COLOR);
- final int padding = (int) mShadowRadius;
+ final int padding = mShadowRadius;
// set padding so the inner image sits correctly within the shadow.
setPadding(padding, padding, padding, padding);
}
@@ -111,17 +110,22 @@
/**
* Update the background color of the circle image view.
+ *
+ * @param colorRes Id of a color resource.
*/
- public void setBackgroundColor(int colorRes) {
+ public void setBackgroundColorRes(int colorRes) {
+ setBackgroundColor(getContext().getResources().getColor(colorRes));
+ }
+
+ @Override
+ public void setBackgroundColor(int color) {
if (getBackground() instanceof ShapeDrawable) {
- final Resources res = getResources();
- ((ShapeDrawable) getBackground()).getPaint().setColor(res.getColor(colorRes));
+ ((ShapeDrawable) getBackground()).getPaint().setColor(color);
}
}
private class OvalShadow extends OvalShape {
private RadialGradient mRadialGradient;
- private int mShadowRadius;
private Paint mShadowPaint;
private int mCircleDiameter;
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index 25ac1c0..8204ec7 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -160,6 +160,9 @@
android.R.attr.layout_gravity
};
+ /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */
+ private static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19;
+
private final ChildAccessibilityDelegate mChildAccessibilityDelegate =
new ChildAccessibilityDelegate();
@@ -258,6 +261,7 @@
void dispatchChildInsets(View child, Object insets, int drawerGravity);
void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity);
int getTopInset(Object lastInsets);
+ Drawable getDefaultStatusBarBackground(Context context);
}
static class DrawerLayoutCompatImplBase implements DrawerLayoutCompatImpl {
@@ -276,6 +280,11 @@
public int getTopInset(Object insets) {
return 0;
}
+
+ @Override
+ public Drawable getDefaultStatusBarBackground(Context context) {
+ return null;
+ }
}
static class DrawerLayoutCompatImplApi21 implements DrawerLayoutCompatImpl {
@@ -294,6 +303,11 @@
public int getTopInset(Object insets) {
return DrawerLayoutCompatApi21.getTopInset(insets);
}
+
+ @Override
+ public Drawable getDefaultStatusBarBackground(Context context) {
+ return DrawerLayoutCompatApi21.getDefaultStatusBarBackground(context);
+ }
}
static {
@@ -345,6 +359,7 @@
ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
if (ViewCompat.getFitsSystemWindows(this)) {
IMPL.configureApplyInsets(this);
+ mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
}
}
@@ -631,15 +646,7 @@
mListener.onDrawerClosed(drawerView);
}
- // If no drawer is opened, all drawers are not shown
- // for accessibility and the content is shown.
- View content = getChildAt(0);
- if (content != null) {
- ViewCompat.setImportantForAccessibility(content,
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
- ViewCompat.setImportantForAccessibility(drawerView,
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ updateChildrenImportantForAccessibility(drawerView, false);
// Only send WINDOW_STATE_CHANGE if the host has window focus. This
// may change if support for multiple foreground windows (e.g. IME)
@@ -661,18 +668,31 @@
mListener.onDrawerOpened(drawerView);
}
- // If a drawer is opened, only it is shown for
- // accessibility and the content is not shown.
- View content = getChildAt(0);
- if (content != null) {
- ViewCompat.setImportantForAccessibility(content,
+ updateChildrenImportantForAccessibility(drawerView, true);
+
+ // Only send WINDOW_STATE_CHANGE if the host has window focus.
+ if (hasWindowFocus()) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ drawerView.requestFocus();
+ }
+ }
+
+ private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (!isDrawerOpen && !isDrawerView(child)
+ || isDrawerOpen && child == drawerView) {
+ // Drawer is closed and this is a content view or this is an
+ // open drawer view, so it should be visible.
+ ViewCompat.setImportantForAccessibility(child,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ } else {
+ ViewCompat.setImportantForAccessibility(child,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
- ViewCompat.setImportantForAccessibility(drawerView,
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- drawerView.requestFocus();
}
}
@@ -995,6 +1015,15 @@
}
/**
+ * Gets the drawable used to draw in the insets area for the status bar.
+ *
+ * @return The status bar background drawable, or null if none set
+ */
+ public Drawable getStatusBarBackgroundDrawable() {
+ return mStatusBarBackground;
+ }
+
+ /**
* Set a drawable to draw in the insets area for the status bar.
* Note that this will only be activated if this DrawerLayout fitsSystemWindows.
*
@@ -1116,9 +1145,11 @@
final float y = ev.getY();
mInitialMotionX = x;
mInitialMotionY = y;
- if (mScrimOpacity > 0 &&
- isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
- interceptForTap = true;
+ if (mScrimOpacity > 0) {
+ final View child = mLeftDragger.findTopChildUnder((int) x, (int) y);
+ if (child != null && isContentView(child)) {
+ interceptForTap = true;
+ }
}
mDisallowInterceptRequested = false;
mChildrenCanceledTouch = false;
@@ -1264,13 +1295,7 @@
lp.onScreen = 1.f;
lp.knownOpen = true;
- View content = getChildAt(0);
- if (content != null) {
- ViewCompat.setImportantForAccessibility(content,
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- }
- ViewCompat.setImportantForAccessibility(drawerView,
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ updateChildrenImportantForAccessibility(drawerView, true);
} else {
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
@@ -1507,22 +1532,11 @@
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
-
final SavedState ss = new SavedState(superState);
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (!isDrawerView(child)) {
- continue;
- }
-
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp.knownOpen) {
- ss.openDrawerGravity = lp.gravity;
- // Only one drawer can be open at a time.
- break;
- }
+ final View openDrawer = findOpenDrawer();
+ if (openDrawer != null) {
+ ss.openDrawerGravity = ((LayoutParams) openDrawer.getLayoutParams()).gravity;
}
ss.lockModeLeft = mLockModeLeft;
@@ -1533,19 +1547,26 @@
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
- // Until a drawer is open, it is hidden from accessibility.
- if (index > 0 || (index < 0 && getChildCount() > 0)) {
+ super.addView(child, index, params);
+
+ final View openDrawer = findOpenDrawer();
+ if (openDrawer != null || isDrawerView(child)) {
+ // A drawer is already open or the new view is a drawer, so the
+ // new view should start out hidden.
ViewCompat.setImportantForAccessibility(child,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- // Also set a delegate to break the child-parent relation if the
- // child is hidden. For details (see incluceChildForAccessibility).
- ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
- } else {
- // Initially, the content is shown for accessibility.
+ } else {
+ // Otherwise this is a content view and no drawer is open, so the
+ // new view should start out visible.
ViewCompat.setImportantForAccessibility(child,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- super.addView(child, index, params);
+
+ // We only need a delegate here if the framework doesn't understand
+ // NO_HIDE_DESCENDANTS importance.
+ if (!CAN_HIDE_DESCENDANTS) {
+ ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
+ }
}
private static boolean includeChildForAccessibility(View child) {
@@ -1806,20 +1827,33 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
- super.onInitializeAccessibilityNodeInfo(host, superNode);
+ if (CAN_HIDE_DESCENDANTS) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ } else {
+ // Obtain a node for the host, then manually generate the list
+ // of children to only include non-obscured views.
+ final AccessibilityNodeInfoCompat superNode =
+ AccessibilityNodeInfoCompat.obtain(info);
+ super.onInitializeAccessibilityNodeInfo(host, superNode);
+
+ info.setSource(host);
+ final ViewParent parent = ViewCompat.getParentForAccessibility(host);
+ if (parent instanceof View) {
+ info.setParent((View) parent);
+ }
+ copyNodeInfoNoChildren(info, superNode);
+ superNode.recycle();
+
+ addChildrenForAccessibility(info, (ViewGroup) host);
+ }
info.setClassName(DrawerLayout.class.getName());
- info.setSource(host);
- final ViewParent parent = ViewCompat.getParentForAccessibility(host);
- if (parent instanceof View) {
- info.setParent((View) parent);
- }
- copyNodeInfoNoChildren(info, superNode);
- superNode.recycle();
-
- addChildrenForAccessibility(info, (ViewGroup) host);
+ // This view reports itself as focusable so that it can intercept
+ // the back button, but we should prevent this view from reporting
+ // itself as focusable to accessibility services.
+ info.setFocusable(false);
+ info.setFocused(false);
}
@Override
@@ -1853,6 +1887,15 @@
return super.dispatchPopulateAccessibilityEvent(host, event);
}
+ @Override
+ public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+ AccessibilityEvent event) {
+ if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) {
+ return super.onRequestSendAccessibilityEvent(host, child, event);
+ }
+ return false;
+ }
+
private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) {
final int childCount = v.getChildCount();
for (int i = 0; i < childCount; i++) {
@@ -1863,15 +1906,6 @@
}
}
- @Override
- public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
- AccessibilityEvent event) {
- if (includeChildForAccessibility(child)) {
- return super.onRequestSendAccessibilityEvent(host, child, event);
- }
- return false;
- }
-
/**
* This should really be in AccessibilityNodeInfoCompat, but there unfortunately
* seem to be a few elements that are not easily cloneable using the underlying API.
@@ -1909,6 +1943,7 @@
public void onInitializeAccessibilityNodeInfo(View child,
AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(child, info);
+
if (!includeChildForAccessibility(child)) {
// If we are ignoring the sub-tree rooted at the child,
// break the connection to the rest of the node tree.
diff --git a/v4/java/android/support/v4/widget/MaterialProgressDrawable.java b/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
index 153c493..fc7eb26 100644
--- a/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
+++ b/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
@@ -26,7 +26,6 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
@@ -107,7 +106,7 @@
private float mRotationCount;
private double mWidth;
private double mHeight;
- private Animation mFinishAnimation;
+ boolean mFinishing;
public MaterialProgressDrawable(Context context, View parent) {
mParent = parent;
@@ -273,10 +272,13 @@
mRing.storeOriginals();
// Already showing some part of the ring
if (mRing.getEndTrim() != mRing.getStartTrim()) {
- mParent.startAnimation(mFinishAnimation);
+ mFinishing = true;
+ mAnimation.setDuration(ANIMATION_DURATION/2);
+ mParent.startAnimation(mAnimation);
} else {
mRing.setColorIndex(0);
mRing.resetOriginals();
+ mAnimation.setDuration(ANIMATION_DURATION);
mParent.startAnimation(mAnimation);
}
}
@@ -290,99 +292,88 @@
mRing.resetOriginals();
}
+ private void applyFinishTranslation(float interpolatedTime, Ring ring) {
+ // shrink back down and complete a full rotation before
+ // starting other circles
+ // Rotation goes between [0..1].
+ float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
+ + 1f);
+ final float startTrim = ring.getStartingStartTrim()
+ + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) * interpolatedTime;
+ ring.setStartTrim(startTrim);
+ final float rotation = ring.getStartingRotation()
+ + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
+ ring.setRotation(rotation);
+ }
+
private void setupAnimators() {
final Ring ring = mRing;
- final Animation finishRingAnimation = new Animation() {
- public void applyTransformation(float interpolatedTime, Transformation t) {
- // shrink back down and complete a full rotation before starting other circles
- // Rotation goes between [0..1].
- float targetRotation = (float) (Math.floor(ring.getStartingRotation()
- / MAX_PROGRESS_ARC) + 1f);
- final float startTrim = ring.getStartingStartTrim()
- + (ring.getStartingEndTrim() - ring.getStartingStartTrim())
- * interpolatedTime;
- ring.setStartTrim(startTrim);
- final float rotation = ring.getStartingRotation()
- + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
- ring.setRotation(rotation);
- ring.setArrowScale(1 - interpolatedTime);
- }
- };
- finishRingAnimation.setInterpolator(EASE_INTERPOLATOR);
- finishRingAnimation.setDuration(ANIMATION_DURATION/2);
- finishRingAnimation.setAnimationListener(new Animation.AnimationListener() {
-
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- ring.goToNextColor();
- ring.storeOriginals();
- ring.setShowArrow(false);
- mParent.startAnimation(mAnimation);
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- });
final Animation animation = new Animation() {
- @Override
+ @Override
public void applyTransformation(float interpolatedTime, Transformation t) {
- // The minProgressArc is calculated from 0 to create an angle that
- // matches the stroke width.
- final float minProgressArc = (float) Math.toRadians(ring.getStrokeWidth()
- / (2 * Math.PI * ring.getCenterRadius()));
- final float startingEndTrim = ring.getStartingEndTrim();
- final float startingTrim = ring.getStartingStartTrim();
- final float startingRotation = ring.getStartingRotation();
+ if (mFinishing) {
+ applyFinishTranslation(interpolatedTime, ring);
+ } else {
+ // The minProgressArc is calculated from 0 to create an
+ // angle that
+ // matches the stroke width.
+ final float minProgressArc = (float) Math.toRadians(
+ ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
+ final float startingEndTrim = ring.getStartingEndTrim();
+ final float startingTrim = ring.getStartingStartTrim();
+ final float startingRotation = ring.getStartingRotation();
- // Offset the minProgressArc to where the endTrim is located.
- final float minArc = MAX_PROGRESS_ARC - minProgressArc;
- final float endTrim = startingEndTrim
- + (minArc * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
- ring.setEndTrim(endTrim);
+ // Offset the minProgressArc to where the endTrim is
+ // located.
+ final float minArc = MAX_PROGRESS_ARC - minProgressArc;
+ final float endTrim = startingEndTrim + (minArc
+ * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
+ ring.setEndTrim(endTrim);
- final float startTrim = startingTrim
- + (MAX_PROGRESS_ARC * END_CURVE_INTERPOLATOR
- .getInterpolation(interpolatedTime));
- ring.setStartTrim(startTrim);
+ final float startTrim = startingTrim + (MAX_PROGRESS_ARC
+ * END_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
+ ring.setStartTrim(startTrim);
- final float rotation = startingRotation + (0.25f * interpolatedTime);
- ring.setRotation(rotation);
+ final float rotation = startingRotation + (0.25f * interpolatedTime);
+ ring.setRotation(rotation);
- float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
- + (720.0f * (mRotationCount / NUM_POINTS));
- setRotation(groupRotation);
+ float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
+ + (720.0f * (mRotationCount / NUM_POINTS));
+ setRotation(groupRotation);
+ }
}
};
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.RESTART);
animation.setInterpolator(LINEAR_INTERPOLATOR);
- animation.setDuration(ANIMATION_DURATION);
animation.setAnimationListener(new Animation.AnimationListener() {
- @Override
+ @Override
public void onAnimationStart(Animation animation) {
mRotationCount = 0;
}
- @Override
+ @Override
public void onAnimationEnd(Animation animation) {
// do nothing
}
- @Override
+ @Override
public void onAnimationRepeat(Animation animation) {
ring.storeOriginals();
ring.goToNextColor();
ring.setStartTrim(ring.getEndTrim());
- mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
+ if (mFinishing) {
+ // finished closing the last ring from the swipe gesture; go
+ // into progress mode
+ mFinishing = false;
+ animation.setDuration(ANIMATION_DURATION);
+ ring.setShowArrow(false);
+ } else {
+ mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
+ }
}
});
- mFinishAnimation = finishRingAnimation;
mAnimation = animation;
}
diff --git a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
index 2496ef9..2e056e6 100644
--- a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -101,6 +101,7 @@
private boolean mOriginalOffsetCalculated = false;
private float mInitialMotionY;
+ private float mInitialDownY;
private boolean mIsBeingDragged;
private int mActivePointerId = INVALID_POINTER;
// Whether this item is scaled up rather than clipped
@@ -446,13 +447,30 @@
}
/**
+ * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)}
+ */
+ @Deprecated
+ public void setProgressBackgroundColor(int colorRes) {
+ setProgressBackgroundColorSchemeResource(colorRes);
+ }
+
+ /**
* Set the background color of the progress spinner disc.
*
* @param colorRes Resource id of the color.
*/
- public void setProgressBackgroundColor(int colorRes) {
- mCircleView.setBackgroundColor(colorRes);
- mProgress.setBackgroundColor(getResources().getColor(colorRes));
+ public void setProgressBackgroundColorSchemeResource(int colorRes) {
+ setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes));
+ }
+
+ /**
+ * Set the background color of the progress spinner disc.
+ *
+ * @param color
+ */
+ public void setProgressBackgroundColorSchemeColor(int color) {
+ mCircleView.setBackgroundColor(color);
+ mProgress.setBackgroundColor(color);
}
/**
@@ -577,6 +595,17 @@
}
/**
+ * Get the diameter of the progress circle that is displayed as part of the
+ * swipe to refresh layout. This is not valid until a measure pass has
+ * completed.
+ *
+ * @return Diameter in pixels of the progress circle view.
+ */
+ public int getProgressCircleDiameter() {
+ return mCircleView != null ?mCircleView.getMeasuredHeight() : 0;
+ }
+
+ /**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
@@ -615,11 +644,12 @@
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsBeingDragged = false;
- final float initialMotionY = getMotionEventY(ev, mActivePointerId);
- if (initialMotionY == -1) {
+ final float initialDownY = getMotionEventY(ev, mActivePointerId);
+ if (initialDownY == -1) {
return false;
}
- mInitialMotionY = initialMotionY;
+ mInitialDownY = initialDownY;
+ break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
@@ -631,8 +661,9 @@
if (y == -1) {
return false;
}
- final float yDiff = y - mInitialMotionY;
+ final float yDiff = y - mInitialDownY;
if (yDiff > mTouchSlop && !mIsBeingDragged) {
+ mInitialMotionY = mInitialDownY + mTouchSlop;
mIsBeingDragged = true;
mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
}
@@ -733,7 +764,7 @@
// Animate the alpha
startProgressAlphaStartAnimation();
}
- float strokeStart = (float) (adjustedPercent * .8f);
+ float strokeStart = adjustedPercent * .8f;
mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
mProgress.setArrowScale(Math.min(1f, adjustedPercent));
} else {
@@ -852,6 +883,7 @@
targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
int offset = targetTop - mCircleView.getTop();
setTargetOffsetTopAndBottom(offset, false /* requires update */);
+ mProgress.setArrowScale(1 - interpolatedTime);
}
};
@@ -920,4 +952,4 @@
public interface OnRefreshListener {
public void onRefresh();
}
-}
\ No newline at end of file
+}
diff --git a/v4/java/android/support/v4/widget/ViewDragHelper.java b/v4/java/android/support/v4/widget/ViewDragHelper.java
index 2112b23..c6bebd3 100644
--- a/v4/java/android/support/v4/widget/ViewDragHelper.java
+++ b/v4/java/android/support/v4/widget/ViewDragHelper.java
@@ -868,6 +868,7 @@
}
void setDragState(int state) {
+ mParentView.removeCallbacks(mSetIdleRunnable);
if (mDragState != state) {
mDragState = state;
mCallback.onViewDragStateChanged(state);
diff --git a/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
new file mode 100644
index 0000000..48e2c48
--- /dev/null
+++ b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.RemoteControlClient;
+import android.os.SystemClock;
+
+public class MediaSessionCompatApi18 {
+
+ public static Object createPlaybackPositionUpdateListener(
+ MediaSessionCompatApi14.Callback callback) {
+ return new OnPlaybackPositionUpdateListener<MediaSessionCompatApi14.Callback>(callback);
+ }
+
+ public static void registerMediaButtonEventReceiver(Context context, PendingIntent pi) {
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ am.registerMediaButtonEventReceiver(pi);
+ }
+
+ public static void unregisterMediaButtonEventReceiver(Context context, PendingIntent pi) {
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ am.unregisterMediaButtonEventReceiver(pi);
+ }
+
+ public static void setState(Object rccObj, int state, long position, float speed,
+ long updateTime) {
+ long currTime = SystemClock.elapsedRealtime();
+ if (state == MediaSessionCompatApi14.STATE_PLAYING && position > 0) {
+ long diff = 0;
+ if (updateTime > 0) {
+ diff = currTime - updateTime;
+ if (speed > 0 && speed != 1f) {
+ diff *= speed;
+ }
+ }
+ position += diff;
+ }
+ state = MediaSessionCompatApi14.getRccStateFromState(state);
+ ((RemoteControlClient) rccObj).setPlaybackState(state, position, speed);
+ }
+
+ public static void setOnPlaybackPositionUpdateListener(Object rccObj,
+ Object onPositionUpdateObj) {
+ ((RemoteControlClient) rccObj).setPlaybackPositionUpdateListener(
+ (RemoteControlClient.OnPlaybackPositionUpdateListener) onPositionUpdateObj);
+ }
+
+ static class OnPlaybackPositionUpdateListener<T extends MediaSessionCompatApi14.Callback>
+ implements RemoteControlClient.OnPlaybackPositionUpdateListener {
+ protected final T mCallback;
+
+ public OnPlaybackPositionUpdateListener(T callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onPlaybackPositionUpdate(long newPositionMs) {
+ mCallback.onSeekTo(newPositionMs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
new file mode 100644
index 0000000..261f4ca
--- /dev/null
+++ b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.graphics.Bitmap;
+import android.media.MediaMetadataEditor;
+import android.media.Rating;
+import android.media.RemoteControlClient;
+import android.os.Bundle;
+
+public class MediaSessionCompatApi19 {
+ /***** MediaMetadata keys ********/
+ private static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ private static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ private static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ private static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+
+ public static Object createMetadataUpdateListener(MediaSessionCompatApi14.Callback callback) {
+ return new OnMetadataUpdateListener<MediaSessionCompatApi14.Callback>(callback);
+ }
+
+ public static void setMetadata(Object rccObj, Bundle metadata, boolean supportRating) {
+ RemoteControlClient.MetadataEditor editor = ((RemoteControlClient) rccObj).editMetadata(
+ true);
+ MediaSessionCompatApi14.buildOldMetadata(metadata, editor);
+ addNewMetadata(metadata, editor);
+ if (supportRating && android.os.Build.VERSION.SDK_INT > 19) {
+ editor.addEditableKey(RemoteControlClient.MetadataEditor.RATING_KEY_BY_USER);
+ }
+ editor.apply();
+ }
+
+ public static void setOnMetadataUpdateListener(Object rccObj, Object onMetadataUpdateObj) {
+ ((RemoteControlClient) rccObj).setMetadataUpdateListener(
+ (RemoteControlClient.OnMetadataUpdateListener) onMetadataUpdateObj);
+ }
+
+ static void addNewMetadata(Bundle metadata, RemoteControlClient.MetadataEditor editor) {
+ if (metadata.containsKey(METADATA_KEY_RATING)) {
+ editor.putObject(MediaMetadataEditor.RATING_KEY_BY_OTHERS,
+ metadata.getParcelable(METADATA_KEY_RATING));
+ }
+ if (metadata.containsKey(METADATA_KEY_USER_RATING)) {
+ editor.putObject(MediaMetadataEditor.RATING_KEY_BY_USER,
+ metadata.getParcelable(METADATA_KEY_USER_RATING));
+ }
+ if (metadata.containsKey(METADATA_KEY_ART)) {
+ Bitmap art = metadata.getParcelable(METADATA_KEY_ART);
+ editor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, art);
+ } else if (metadata.containsKey(METADATA_KEY_ALBUM_ART)) {
+ // Fall back to album art if the track art wasn't available
+ Bitmap art = metadata.getParcelable(METADATA_KEY_ALBUM_ART);
+ editor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, art);
+ }
+ }
+
+ static class OnMetadataUpdateListener<T extends MediaSessionCompatApi14.Callback> implements
+ RemoteControlClient.OnMetadataUpdateListener {
+ protected final T mCallback;
+
+ public OnMetadataUpdateListener(T callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onMetadataUpdate(int key, Object newValue) {
+ if (key == MediaMetadataEditor.RATING_KEY_BY_USER && newValue instanceof Rating) {
+ mCallback.onSetRating(newValue);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index cbc4d2b..b827b1f 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -302,7 +302,7 @@
PrintDocumentAdapter printDocumentAdapter = new PrintDocumentAdapter() {
private PrintAttributes mAttributes;
- AsyncTask<Uri, Boolean, Bitmap> loadBitmap;
+ AsyncTask<Uri, Boolean, Bitmap> mLoadBitmap;
Bitmap mBitmap = null;
@Override
@@ -311,9 +311,11 @@
final CancellationSignal cancellationSignal,
final LayoutResultCallback layoutResultCallback,
Bundle bundle) {
+
+ mAttributes = newPrintAttributes;
+
if (cancellationSignal.isCanceled()) {
layoutResultCallback.onLayoutCancelled();
- mAttributes = newPrintAttributes;
return;
}
// we finished the load
@@ -327,7 +329,7 @@
return;
}
- loadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
+ mLoadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
@Override
protected void onPreExecute() {
@@ -368,17 +370,16 @@
} else {
layoutResultCallback.onLayoutFailed(null);
}
+ mLoadBitmap = null;
}
@Override
protected void onCancelled(Bitmap result) {
// Task was cancelled, report that.
layoutResultCallback.onLayoutCancelled();
+ mLoadBitmap = null;
}
- };
- loadBitmap.execute();
-
- mAttributes = newPrintAttributes;
+ }.execute();
}
private void cancelLoad() {
@@ -394,7 +395,9 @@
public void onFinish() {
super.onFinish();
cancelLoad();
- loadBitmap.cancel(true);
+ if (mLoadBitmap != null) {
+ mLoadBitmap.cancel(true);
+ }
if (callback != null) {
callback.onFinish();
}
diff --git a/v7/appcompat/res/anim/abc_grow_fade_in_from_bottom.xml b/v7/appcompat/res/anim/abc_grow_fade_in_from_bottom.xml
new file mode 100644
index 0000000..79d02d4
--- /dev/null
+++ b/v7/appcompat/res/anim/abc_grow_fade_in_from_bottom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+ <scale android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromXScale="0.9" android:toXScale="1.0"
+ android:fromYScale="0.9" android:toYScale="1.0"
+ android:pivotX="50%" android:pivotY="100%"
+ android:duration="@integer/abc_config_activityDefaultDur" />
+ <alpha android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="@integer/abc_config_activityShortDur" />
+</set>
\ No newline at end of file
diff --git a/v7/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml b/v7/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml
new file mode 100644
index 0000000..9a23cd2
--- /dev/null
+++ b/v7/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+ <scale android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromXScale="1.0" android:toXScale="0.9"
+ android:fromYScale="1.0" android:toYScale="0.9"
+ android:pivotX="50%" android:pivotY="100%"
+ android:duration="@integer/abc_config_activityDefaultDur" />
+ <alpha android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:duration="@integer/abc_config_activityShortDur" />
+</set>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index 6c14157..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index f4ff16b..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..b07da0c
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..51a895d
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..2f59488
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
index 0eacedd..06e7a17 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index ed4ba34..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index 8f10bd5..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..f31730d
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..d38aed2
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..87dade3
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
index e0d5ac4..2cabe7b 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png
new file mode 100644
index 0000000..d675993
--- /dev/null
+++ b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png
new file mode 100644
index 0000000..8da42fe
--- /dev/null
+++ b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index 55099d4..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index 3c4701f..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..8337ffe
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..33ec44c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..0166d70
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
index 7accf52..0750fcc 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index d8cdf1a..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index a49a207..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..469f736
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..4b49faf
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..561d9ef
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
index 66f7d16..7e181d1 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png
new file mode 100644
index 0000000..279afe3
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml b/v7/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml
new file mode 100644
index 0000000..c50d4b1
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Used as the canonical button shape. -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="@dimen/abc_button_inset_horizontal_material"
+ android:insetTop="@dimen/abc_button_inset_vertical_material"
+ android:insetRight="@dimen/abc_button_inset_horizontal_material"
+ android:insetBottom="@dimen/abc_button_inset_vertical_material">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/abc_control_corner_material" />
+ <solid android:color="@android:color/white" />
+ <padding android:left="@dimen/abc_button_padding_horizontal_material"
+ android:top="@dimen/abc_button_padding_vertical_material"
+ android:right="@dimen/abc_button_padding_horizontal_material"
+ android:bottom="@dimen/abc_button_padding_vertical_material" />
+ </shape>
+</inset>
diff --git a/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
new file mode 100644
index 0000000..535e2da
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background"
+ android:drawable="@drawable/abc_btn_rating_star_off_mtrl_alpha" />
+ <item android:id="@android:id/secondaryProgress"
+ android:drawable="@drawable/abc_btn_rating_star_off_mtrl_alpha" />
+ <item android:id="@android:id/progress"
+ android:drawable="@drawable/abc_btn_rating_star_on_mtrl_alpha" />
+</layer-list>
diff --git a/v7/appcompat/res/drawable/abc_spinner_textfield_background_material.xml b/v7/appcompat/res/drawable/abc_spinner_textfield_background_material.xml
new file mode 100644
index 0000000..d0f46a8
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_spinner_textfield_background_material.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="@dimen/abc_control_inset_material"
+ android:insetTop="@dimen/abc_control_inset_material"
+ android:insetBottom="@dimen/abc_control_inset_material"
+ android:insetRight="@dimen/abc_control_inset_material">
+ <selector>
+ <item android:state_checked="false" android:state_pressed="false">
+ <layer-list>
+ <item android:drawable="@drawable/abc_textfield_default_mtrl_alpha" />
+ <item android:drawable="@drawable/abc_spinner_mtrl_am_alpha" />
+ </layer-list>
+ </item>
+ <item>
+ <layer-list>
+ <item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha" />
+ <item android:drawable="@drawable/abc_spinner_mtrl_am_alpha" />
+ </layer-list>
+ </item>
+ </selector>
+</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_activity_chooser_view.xml b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
index 99c2395..923fda3 100644
--- a/v7/appcompat/res/layout/abc_activity_chooser_view.xml
+++ b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
@@ -16,14 +16,56 @@
** limitations under the License.
*/
-->
-<android.support.v7.widget.LinearLayoutCompat
- xmlns:android="http://schemas.android.com/apk/res/android"
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+ class="android.support.v7.internal.widget.ActivityChooserView$InnerLayout"
android:id="@+id/activity_chooser_view_content"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
style="?attr/activityChooserViewStyle">
- <include layout="@layout/abc_activity_chooser_view_include" />
+ <FrameLayout
+ android:id="@+id/expand_activities_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:focusable="true"
+ android:addStatesFromChildren="true"
+ android:background="?attr/actionBarItemBackground">
-</android.support.v7.widget.LinearLayoutCompat>
\ No newline at end of file
+ <ImageView android:id="@+id/image"
+ android:layout_width="32dip"
+ android:layout_height="32dip"
+ android:layout_gravity="center"
+ android:layout_marginTop="2dip"
+ android:layout_marginBottom="2dip"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/default_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:focusable="true"
+ android:addStatesFromChildren="true"
+ android:background="?attr/actionBarItemBackground">
+
+ <ImageView android:id="@+id/image"
+ android:layout_width="32dip"
+ android:layout_height="32dip"
+ android:layout_gravity="center"
+ android:layout_marginTop="2dip"
+ android:layout_marginLeft="12dip"
+ android:layout_marginRight="12dip"
+ android:layout_marginEnd="12dip"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true" />
+
+ </FrameLayout>
+
+</view>
diff --git a/v7/appcompat/res/layout/abc_activity_chooser_view_include.xml b/v7/appcompat/res/layout/abc_activity_chooser_view_include.xml
deleted file mode 100644
index 975b13e..0000000
--- a/v7/appcompat/res/layout/abc_activity_chooser_view_include.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<merge
- xmlns:android="http://schemas.android.com/apk/res/android">
-
- <FrameLayout
- android:id="@+id/expand_activities_button"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:focusable="true"
- android:addStatesFromChildren="true"
- android:background="?attr/actionBarItemBackground">
-
- <ImageView
- android:id="@+id/image"
- android:layout_width="56dip"
- android:layout_height="36dip"
- android:layout_gravity="center"
- android:paddingTop="2dip"
- android:paddingBottom="2dip"
- android:paddingLeft="12dip"
- android:paddingRight="12dip"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true" />
-
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/default_activity_button"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:focusable="true"
- android:addStatesFromChildren="true"
- android:background="?attr/actionBarItemBackground">
-
- <ImageView
- android:id="@+id/image"
- android:layout_width="56dip"
- android:layout_height="36dip"
- android:layout_gravity="center"
- android:paddingTop="2dip"
- android:paddingBottom="2dip"
- android:paddingLeft="12dip"
- android:paddingRight="12dip"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true" />
-
- </FrameLayout>
-
-</merge>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_expanded_menu_layout.xml b/v7/appcompat/res/layout/abc_expanded_menu_layout.xml
index 20e8b19..371151f 100644
--- a/v7/appcompat/res/layout/abc_expanded_menu_layout.xml
+++ b/v7/appcompat/res/layout/abc_expanded_menu_layout.xml
@@ -18,5 +18,4 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/expanded_menu"
android:layout_width="?attr/panelMenuListWidth"
- android:layout_height="wrap_content"
- android:background="?attr/panelBackground"/>
+ android:layout_height="wrap_content" />
diff --git a/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml b/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
index 5a29686..7407498 100644
--- a/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
+++ b/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
@@ -24,7 +24,8 @@
<!-- Icons come first in the layout, since their placement doesn't depend on
the placement of the text views. -->
- <ImageView android:id="@android:id/icon1"
+ <android.support.v7.internal.widget.TintImageView
+ android:id="@android:id/icon1"
android:layout_width="@dimen/abc_dropdownitem_icon_width"
android:layout_height="48dip"
android:scaleType="centerInside"
@@ -33,7 +34,8 @@
android:visibility="invisible"
style="@style/RtlOverlay.Widget.AppCompat.Search.DropDown.Icon1" />
- <ImageView android:id="@+id/edit_query"
+ <android.support.v7.internal.widget.TintImageView
+ android:id="@+id/edit_query"
android:layout_width="48dip"
android:layout_height="48dip"
android:scaleType="centerInside"
@@ -43,7 +45,8 @@
android:visibility="gone"
style="@style/RtlOverlay.Widget.AppCompat.Search.DropDown.Query" />
- <ImageView android:id="@id/android:icon2"
+ <android.support.v7.internal.widget.TintImageView
+ android:id="@id/android:icon2"
android:layout_width="48dip"
android:layout_height="48dip"
android:scaleType="centerInside"
diff --git a/v7/appcompat/res/values-af/strings.xml b/v7/appcompat/res/values-af/strings.xml
index 474f3aa..563258e 100644
--- a/v7/appcompat/res/values-af/strings.xml
+++ b/v7/appcompat/res/values-af/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeer tuis"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigeer op"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Nog opsies"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Vou in"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Soek"</string>
diff --git a/v7/appcompat/res/values-am/strings.xml b/v7/appcompat/res/values-am/strings.xml
index dbd53d6..8621f43 100644
--- a/v7/appcompat/res/values-am/strings.xml
+++ b/v7/appcompat/res/values-am/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"ወደ መነሻ ይዳስሱ"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"ወደ ላይ ይዳስሱ"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ተጨማሪ አማራጮች"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ሰብስብ"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s፣ %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s፣ %2$s፣ %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ፍለጋ"</string>
diff --git a/v7/appcompat/res/values-ar/strings.xml b/v7/appcompat/res/values-ar/strings.xml
index 84d6fba..1f9167c 100644
--- a/v7/appcompat/res/values-ar/strings.xml
+++ b/v7/appcompat/res/values-ar/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"التنقل إلى الشاشة الرئيسية"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"التنقل إلى أعلى"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"خيارات إضافية"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"تصغير"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s، %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s، %2$s، %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"بحث"</string>
diff --git a/v7/appcompat/res/values-bg/strings.xml b/v7/appcompat/res/values-bg/strings.xml
index 9d87ef7..e35b172 100644
--- a/v7/appcompat/res/values-bg/strings.xml
+++ b/v7/appcompat/res/values-bg/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Придвижване към „Начало“"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Придвижване нагоре"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Още опции"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Свиване"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"„%1$s“ – %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"„%1$s“, „%2$s“ – %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Търсене"</string>
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn-rBD/strings.xml
index ee522c6..79b78ca 100644
--- a/v7/appcompat/res/values-bn-rBD/strings.xml
+++ b/v7/appcompat/res/values-bn-rBD/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"হোম এ নেভিগেট করুন"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"উপরের দিকে নেভিগেট করুন"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"আরো বিকল্প"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"সঙ্কুচিত করুন"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"অনুসন্ধান করুন"</string>
diff --git a/v7/appcompat/res/values-ca/strings.xml b/v7/appcompat/res/values-ca/strings.xml
index 5fe4b0d..d989428 100644
--- a/v7/appcompat/res/values-ca/strings.xml
+++ b/v7/appcompat/res/values-ca/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navega a la pàgina d\'inici"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navega cap a dalt"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Més opcions"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Replega"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Cerca"</string>
diff --git a/v7/appcompat/res/values-cs/strings.xml b/v7/appcompat/res/values-cs/strings.xml
index 13c9ba8..aff38a1 100644
--- a/v7/appcompat/res/values-cs/strings.xml
+++ b/v7/appcompat/res/values-cs/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Přejít na plochu"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Přejít nahoru"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Více možností"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sbalit"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s – %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s – %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Hledat"</string>
diff --git a/v7/appcompat/res/values-da/strings.xml b/v7/appcompat/res/values-da/strings.xml
index 03fec32..c06c2e9 100644
--- a/v7/appcompat/res/values-da/strings.xml
+++ b/v7/appcompat/res/values-da/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Naviger hjem"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Naviger op"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Flere muligheder"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skjul"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Søg"</string>
diff --git a/v7/appcompat/res/values-de/strings.xml b/v7/appcompat/res/values-de/strings.xml
index 8a0224c..3ac2579 100644
--- a/v7/appcompat/res/values-de/strings.xml
+++ b/v7/appcompat/res/values-de/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Zur Startseite"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Nach oben"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Weitere Optionen"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Minimieren"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s: %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s: %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Suchen"</string>
diff --git a/v7/appcompat/res/values-el/strings.xml b/v7/appcompat/res/values-el/strings.xml
index 52d1b81..f0acb4d 100644
--- a/v7/appcompat/res/values-el/strings.xml
+++ b/v7/appcompat/res/values-el/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Πλοήγηση στην αρχική σελίδα"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Πλοήγηση προς τα επάνω"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Περισσότερες επιλογές"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Σύμπτυξη"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Αναζήτηση"</string>
diff --git a/v7/appcompat/res/values-en-rGB/strings.xml b/v7/appcompat/res/values-en-rGB/strings.xml
index 8a8a111..f0f3beb 100644
--- a/v7/appcompat/res/values-en-rGB/strings.xml
+++ b/v7/appcompat/res/values-en-rGB/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigate home"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigate up"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"More options"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Collapse"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Search"</string>
diff --git a/v7/appcompat/res/values-en-rIN/strings.xml b/v7/appcompat/res/values-en-rIN/strings.xml
index 8a8a111..f0f3beb 100644
--- a/v7/appcompat/res/values-en-rIN/strings.xml
+++ b/v7/appcompat/res/values-en-rIN/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigate home"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigate up"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"More options"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Collapse"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Search"</string>
diff --git a/v7/appcompat/res/values-es-rUS/strings.xml b/v7/appcompat/res/values-es-rUS/strings.xml
index ea5004c..3be6353 100644
--- a/v7/appcompat/res/values-es-rUS/strings.xml
+++ b/v7/appcompat/res/values-es-rUS/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar a la página principal"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar hacia arriba"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Más opciones"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Contraer"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Búsqueda"</string>
diff --git a/v7/appcompat/res/values-es/strings.xml b/v7/appcompat/res/values-es/strings.xml
index c50796e..fc51934 100644
--- a/v7/appcompat/res/values-es/strings.xml
+++ b/v7/appcompat/res/values-es/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ir a la pantalla de inicio"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Desplazarse hacia arriba"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Más opciones"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Contraer"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Buscar"</string>
diff --git a/v7/appcompat/res/values-et-rEE/strings.xml b/v7/appcompat/res/values-et-rEE/strings.xml
index 139fcf9..79b7036 100644
--- a/v7/appcompat/res/values-et-rEE/strings.xml
+++ b/v7/appcompat/res/values-et-rEE/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeerimine avaekraanile"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigeerimine üles"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Rohkem valikuid"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Ahendamine"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Otsing"</string>
diff --git a/v7/appcompat/res/values-eu-rES/strings.xml b/v7/appcompat/res/values-eu-rES/strings.xml
index 541c2ed..38b0018 100644
--- a/v7/appcompat/res/values-eu-rES/strings.xml
+++ b/v7/appcompat/res/values-eu-rES/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Joan orri nagusira"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Joan gora"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Aukera gehiago"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Tolestu"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Bilatu"</string>
diff --git a/v7/appcompat/res/values-fa/strings.xml b/v7/appcompat/res/values-fa/strings.xml
index c317bda..a813372 100644
--- a/v7/appcompat/res/values-fa/strings.xml
+++ b/v7/appcompat/res/values-fa/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"پیمایش به صفحه اصلی"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"پیمایش به بالا"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"گزینههای بیشتر"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"کوچک کردن"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s، %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s، %2$s، %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"جستجو"</string>
diff --git a/v7/appcompat/res/values-fi/strings.xml b/v7/appcompat/res/values-fi/strings.xml
index 218229b..dc56b79 100644
--- a/v7/appcompat/res/values-fi/strings.xml
+++ b/v7/appcompat/res/values-fi/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Siirry etusivulle"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Siirry ylös"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Lisää"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Kutista"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Haku"</string>
diff --git a/v7/appcompat/res/values-fr-rCA/strings.xml b/v7/appcompat/res/values-fr-rCA/strings.xml
index 571ff9a..d401a01 100644
--- a/v7/appcompat/res/values-fr-rCA/strings.xml
+++ b/v7/appcompat/res/values-fr-rCA/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Revenir à l\'accueil"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Revenir en haut de la page"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Plus d\'options"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Réduire"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Rechercher"</string>
diff --git a/v7/appcompat/res/values-fr/strings.xml b/v7/appcompat/res/values-fr/strings.xml
index 353665a..5cb6f18 100644
--- a/v7/appcompat/res/values-fr/strings.xml
+++ b/v7/appcompat/res/values-fr/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Revenir à l\'accueil"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Revenir en haut de la page"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Plus d\'options"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Réduire"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Rechercher"</string>
diff --git a/v7/appcompat/res/values-gl-rES/strings.xml b/v7/appcompat/res/values-gl-rES/strings.xml
index 3f665ed..76ce675 100644
--- a/v7/appcompat/res/values-gl-rES/strings.xml
+++ b/v7/appcompat/res/values-gl-rES/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ir á páxina de inicio"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Desprazarse cara arriba"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Máis opcións"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Contraer"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Buscar"</string>
diff --git a/v7/appcompat/res/values-hdpi/styles_base.xml b/v7/appcompat/res/values-hdpi/styles_base.xml
new file mode 100644
index 0000000..442ea29
--- /dev/null
+++ b/v7/appcompat/res/values-hdpi/styles_base.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <style name="Base.Widget.AppCompat.DrawerArrowToggle" parent="Base.Widget.AppCompat.DrawerArrowToggle.Common">
+ <item name="barSize">18.66dp</item>
+ <item name="gapBetweenBars">3.33dp</item>
+ <item name="drawableSize">24dp</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-hi/strings.xml b/v7/appcompat/res/values-hi/strings.xml
index 23cfaca..7287d1a 100644
--- a/v7/appcompat/res/values-hi/strings.xml
+++ b/v7/appcompat/res/values-hi/strings.xml
@@ -17,9 +17,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="abc_action_mode_done" msgid="4076576682505996667">"पूर्ण"</string>
- <string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुखपृष्ठ पर नेविगेट करें"</string>
+ <string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुख्यपृष्ठ पर नेविगेट करें"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"ऊपर नेविगेट करें"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"अधिक विकल्प"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"संक्षिप्त करें"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"खोजें"</string>
diff --git a/v7/appcompat/res/values-hr/strings.xml b/v7/appcompat/res/values-hr/strings.xml
index 0348596..7ee2a03 100644
--- a/v7/appcompat/res/values-hr/strings.xml
+++ b/v7/appcompat/res/values-hr/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Idi na početnu"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Idi gore"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Dodatne opcije"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sažmi"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Pretraživanje"</string>
diff --git a/v7/appcompat/res/values-hu/strings.xml b/v7/appcompat/res/values-hu/strings.xml
index fc67f00..4f9294a 100644
--- a/v7/appcompat/res/values-hu/strings.xml
+++ b/v7/appcompat/res/values-hu/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ugrás a főoldalra"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Felfelé mozgatás"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"További lehetőségek"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Összecsukás"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Keresés"</string>
diff --git a/v7/appcompat/res/values-hy-rAM/strings.xml b/v7/appcompat/res/values-hy-rAM/strings.xml
index da67fe4..b75fe59 100644
--- a/v7/appcompat/res/values-hy-rAM/strings.xml
+++ b/v7/appcompat/res/values-hy-rAM/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ուղղվել տուն"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Ուղղվել վերև"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Այլ ընտրանքներ"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Թաքցնել"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Որոնել"</string>
diff --git a/v7/appcompat/res/values-in/strings.xml b/v7/appcompat/res/values-in/strings.xml
index 3c31755..b25f01f 100644
--- a/v7/appcompat/res/values-in/strings.xml
+++ b/v7/appcompat/res/values-in/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigasi ke beranda"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigasi naik"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Opsi lain"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Ciutkan"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Telusuri"</string>
diff --git a/v7/appcompat/res/values-is-rIS/strings.xml b/v7/appcompat/res/values-is-rIS/strings.xml
index 7846b51..83baf81 100644
--- a/v7/appcompat/res/values-is-rIS/strings.xml
+++ b/v7/appcompat/res/values-is-rIS/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Fara heim"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Fara upp"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Fleiri valkostir"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Minnka"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Leita"</string>
diff --git a/v7/appcompat/res/values-it/strings.xml b/v7/appcompat/res/values-it/strings.xml
index 6ed52be..21802b3 100644
--- a/v7/appcompat/res/values-it/strings.xml
+++ b/v7/appcompat/res/values-it/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Vai alla home page"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Vai in alto"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Altre opzioni"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Comprimi"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Cerca"</string>
diff --git a/v7/appcompat/res/values-iw/strings.xml b/v7/appcompat/res/values-iw/strings.xml
index fec0e62..08c814e 100644
--- a/v7/appcompat/res/values-iw/strings.xml
+++ b/v7/appcompat/res/values-iw/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"נווט לדף הבית"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"נווט למעלה"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"עוד אפשרויות"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"כווץ"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"חפש"</string>
diff --git a/v7/appcompat/res/values-ja/strings.xml b/v7/appcompat/res/values-ja/strings.xml
index 181dd5e..d74c342 100644
--- a/v7/appcompat/res/values-ja/strings.xml
+++ b/v7/appcompat/res/values-ja/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"ホームへ移動"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"上へ移動"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"その他のオプション"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"折りたたむ"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s、%2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s、%2$s、%3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"検索"</string>
diff --git a/v7/appcompat/res/values-ka-rGE/strings.xml b/v7/appcompat/res/values-ka-rGE/strings.xml
index a96f26c..7aeeaea 100644
--- a/v7/appcompat/res/values-ka-rGE/strings.xml
+++ b/v7/appcompat/res/values-ka-rGE/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"მთავარზე ნავიგაცია"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"ზემოთ ნავიგაცია"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"მეტი ვარიანტები"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"აკეცვა"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ძიება"</string>
diff --git a/v7/appcompat/res/values-kk-rKZ/strings.xml b/v7/appcompat/res/values-kk-rKZ/strings.xml
index fb20a00..3bc7ec0 100644
--- a/v7/appcompat/res/values-kk-rKZ/strings.xml
+++ b/v7/appcompat/res/values-kk-rKZ/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Негізгі бетте қозғалу"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Жоғары қозғалу"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Басқа опциялар"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Тасалау"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Іздеу"</string>
diff --git a/v7/appcompat/res/values-km-rKH/strings.xml b/v7/appcompat/res/values-km-rKH/strings.xml
index 704f4ee..32b8f68 100644
--- a/v7/appcompat/res/values-km-rKH/strings.xml
+++ b/v7/appcompat/res/values-km-rKH/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"រកមើលទៅដើម"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"រកមើលឡើងលើ"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ជម្រើសច្រើនទៀត"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"បង្រួម"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ស្វែងរក"</string>
@@ -27,7 +28,7 @@
<string name="abc_searchview_description_clear" msgid="3691816814315814921">"សម្អាតសំណួរ"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"ដាក់ស្នើសំណួរ"</string>
<string name="abc_searchview_description_voice" msgid="893419373245838918">"ការស្វែងរកសំឡេង"</string>
- <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ជ្រើសកម្មវិធី"</string>
+ <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ជ្រើសកម្មវិធី"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"មើលទាំងអស់"</string>
<string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"ចែករំលែកជាមួយ %s"</string>
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ចែករំលែកជាមួយ"</string>
diff --git a/v7/appcompat/res/values-kn-rIN/strings.xml b/v7/appcompat/res/values-kn-rIN/strings.xml
index d472ff3..5a93d5a 100644
--- a/v7/appcompat/res/values-kn-rIN/strings.xml
+++ b/v7/appcompat/res/values-kn-rIN/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"ಮುಖಪುಟವನ್ನು ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"ಮೇಲಕ್ಕೆ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ಸಂಕುಚಿಸು"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ಹುಡುಕು"</string>
diff --git a/v7/appcompat/res/values-ko/strings.xml b/v7/appcompat/res/values-ko/strings.xml
index 0a92a83..9955a58 100644
--- a/v7/appcompat/res/values-ko/strings.xml
+++ b/v7/appcompat/res/values-ko/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"홈 탐색"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"위로 탐색"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"옵션 더보기"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"접기"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"검색"</string>
diff --git a/v7/appcompat/res/values-ky-rKG/strings.xml b/v7/appcompat/res/values-ky-rKG/strings.xml
index b091f60..c3cc6f7 100644
--- a/v7/appcompat/res/values-ky-rKG/strings.xml
+++ b/v7/appcompat/res/values-ky-rKG/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Үйгө багыттоо"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Жогору"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Көбүрөөк мүмкүнчүлүктөр"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Жыйнап коюу"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Издөө"</string>
diff --git a/v7/appcompat/res/values-lo-rLA/strings.xml b/v7/appcompat/res/values-lo-rLA/strings.xml
index 33614e6..cc4170f 100644
--- a/v7/appcompat/res/values-lo-rLA/strings.xml
+++ b/v7/appcompat/res/values-lo-rLA/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"ກັບໄປໜ້າຫຼັກ"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"ຂຶ້ນເທິງ"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ໂຕເລືອກອື່ນ"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ຫຍໍ້"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ຊອກຫາ"</string>
diff --git a/v7/appcompat/res/values-lt/strings.xml b/v7/appcompat/res/values-lt/strings.xml
index 3c992a7..e5d8975 100644
--- a/v7/appcompat/res/values-lt/strings.xml
+++ b/v7/appcompat/res/values-lt/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Eiti į pagrindinį puslapį"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Eiti į viršų"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Daugiau parinkčių"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sutraukti"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Paieška"</string>
diff --git a/v7/appcompat/res/values-lv/strings.xml b/v7/appcompat/res/values-lv/strings.xml
index 3bd7259..9f1a0f0 100644
--- a/v7/appcompat/res/values-lv/strings.xml
+++ b/v7/appcompat/res/values-lv/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Pārvietoties uz sākuma ekrānu"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Pārvietoties augšup"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Vairāk opciju"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sakļaut"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s: %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s: %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Meklēt"</string>
diff --git a/v7/appcompat/res/values-mk-rMK/strings.xml b/v7/appcompat/res/values-mk-rMK/strings.xml
index b1abf10..5df2278 100644
--- a/v7/appcompat/res/values-mk-rMK/strings.xml
+++ b/v7/appcompat/res/values-mk-rMK/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Движи се кон дома"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Движи се нагоре"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Повеќе опции"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Собери"</string>
<!-- String.format failed for translation -->
<!-- no translation found for abc_action_bar_home_description_format (1397052879051804371) -->
<skip />
diff --git a/v7/appcompat/res/values-ml-rIN/strings.xml b/v7/appcompat/res/values-ml-rIN/strings.xml
index f7512ad..3a0f82c 100644
--- a/v7/appcompat/res/values-ml-rIN/strings.xml
+++ b/v7/appcompat/res/values-ml-rIN/strings.xml
@@ -20,13 +20,14 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"ഹോമിലേക്ക് നാവിഗേറ്റുചെയ്യുക"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"മുകളിലേക്ക് നാവിഗേറ്റുചെയ്യുക"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"കൂടുതല് ഓപ്ഷനുകള്"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ചുരുക്കുക"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"തിരയൽ"</string>
<string name="abc_searchview_description_query" msgid="2550479030709304392">"തിരയൽ അന്വേഷണം"</string>
<string name="abc_searchview_description_clear" msgid="3691816814315814921">"അന്വേഷണം മായ്ക്കുക"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"അന്വേഷണം സമർപ്പിക്കുക"</string>
- <string name="abc_searchview_description_voice" msgid="893419373245838918">"വോയ്സ് തിരയൽ"</string>
+ <string name="abc_searchview_description_voice" msgid="893419373245838918">"ശബ്ദ തിരയൽ"</string>
<string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ഒരു അപ്ലിക്കേഷൻ തിരഞ്ഞെടുക്കുക"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"എല്ലാം കാണുക"</string>
<string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s എന്നതുമായി പങ്കിടുക"</string>
diff --git a/v7/appcompat/res/values-mn-rMN/strings.xml b/v7/appcompat/res/values-mn-rMN/strings.xml
index a1a9c6f..073f0df 100644
--- a/v7/appcompat/res/values-mn-rMN/strings.xml
+++ b/v7/appcompat/res/values-mn-rMN/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Нүүр хуудас руу шилжих"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Дээш шилжих"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Нэмэлт сонголтууд"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Хумих"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Хайх"</string>
diff --git a/v7/appcompat/res/values-mr-rIN/strings.xml b/v7/appcompat/res/values-mr-rIN/strings.xml
index 3d0e718..3177012 100644
--- a/v7/appcompat/res/values-mr-rIN/strings.xml
+++ b/v7/appcompat/res/values-mr-rIN/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुख्यपृष्ठ नेव्हिगेट करा"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"वर नेव्हिगेट करा"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"अधिक पर्याय"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"संक्षिप्त करा"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"शोध"</string>
diff --git a/v7/appcompat/res/values-ms-rMY/strings.xml b/v7/appcompat/res/values-ms-rMY/strings.xml
index d2886a1..d77560f 100644
--- a/v7/appcompat/res/values-ms-rMY/strings.xml
+++ b/v7/appcompat/res/values-ms-rMY/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigasi skrin utama"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigasi ke atas"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Lagi pilihan"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Runtuhkan"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Cari"</string>
diff --git a/v7/appcompat/res/values-my-rMM/strings.xml b/v7/appcompat/res/values-my-rMM/strings.xml
index 3ac8472..38602a2 100644
--- a/v7/appcompat/res/values-my-rMM/strings.xml
+++ b/v7/appcompat/res/values-my-rMM/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"မူလနေရာကို သွားရန်"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"အပေါ်သို့သွားရန်"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ပိုမိုရွေးချယ်စရာများ"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ခေါက်ရန်"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s၊ %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s ၊ %2$s ၊ %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ရှာဖွေရန်"</string>
diff --git a/v7/appcompat/res/values-nb/strings.xml b/v7/appcompat/res/values-nb/strings.xml
index 3dbd071..0cf77a1 100644
--- a/v7/appcompat/res/values-nb/strings.xml
+++ b/v7/appcompat/res/values-nb/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Gå til startsiden"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Gå opp"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Flere alternativer"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skjul"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s – %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s – %2$s – %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Søk"</string>
diff --git a/v7/appcompat/res/values-ne-rNP/strings.xml b/v7/appcompat/res/values-ne-rNP/strings.xml
index 0154662..dee552a 100644
--- a/v7/appcompat/res/values-ne-rNP/strings.xml
+++ b/v7/appcompat/res/values-ne-rNP/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"गृह खोज्नुहोस्"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"माथि खोज्नुहोस्"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"थप विकल्पहरू"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"संक्षिप्त पार्नुहोस्"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"खोज्नुहोस्"</string>
diff --git a/v7/appcompat/res/values-nl/strings.xml b/v7/appcompat/res/values-nl/strings.xml
index 330de8d..cd67586 100644
--- a/v7/appcompat/res/values-nl/strings.xml
+++ b/v7/appcompat/res/values-nl/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeren naar startpositie"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Omhoog navigeren"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Meer opties"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Samenvouwen"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Zoeken"</string>
diff --git a/v7/appcompat/res/values-pl/strings.xml b/v7/appcompat/res/values-pl/strings.xml
index 8e32155..7245cad 100644
--- a/v7/appcompat/res/values-pl/strings.xml
+++ b/v7/appcompat/res/values-pl/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Przejdź do strony głównej"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Przejdź wyżej"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Więcej opcji"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Zwiń"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Szukaj"</string>
diff --git a/v7/appcompat/res/values-pt-rPT/strings.xml b/v7/appcompat/res/values-pt-rPT/strings.xml
index e1c622e..8d05c98 100644
--- a/v7/appcompat/res/values-pt-rPT/strings.xml
+++ b/v7/appcompat/res/values-pt-rPT/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar para a página inicial"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar para cima"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mais opções"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Reduzir"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Pesquisar"</string>
diff --git a/v7/appcompat/res/values-pt/strings.xml b/v7/appcompat/res/values-pt/strings.xml
index abdd650..4fecc5c 100644
--- a/v7/appcompat/res/values-pt/strings.xml
+++ b/v7/appcompat/res/values-pt/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar para a página inicial"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar para cima"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mais opções"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Recolher"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Pesquisar"</string>
diff --git a/v7/appcompat/res/values-ro/strings.xml b/v7/appcompat/res/values-ro/strings.xml
index 6dd2b67..ea1ecbd 100644
--- a/v7/appcompat/res/values-ro/strings.xml
+++ b/v7/appcompat/res/values-ro/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigați la ecranul de pornire"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigați în sus"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mai multe opțiuni"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Restrângeți"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Căutați"</string>
diff --git a/v7/appcompat/res/values-ru/strings.xml b/v7/appcompat/res/values-ru/strings.xml
index 9c5ed89..3042ce6 100644
--- a/v7/appcompat/res/values-ru/strings.xml
+++ b/v7/appcompat/res/values-ru/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Перейти на главный экран"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Перейти вверх"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Другие параметры"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Свернуть"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Поиск"</string>
diff --git a/v7/appcompat/res/values-si-rLK/strings.xml b/v7/appcompat/res/values-si-rLK/strings.xml
index 22809d6..6fb7d6a 100644
--- a/v7/appcompat/res/values-si-rLK/strings.xml
+++ b/v7/appcompat/res/values-si-rLK/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"ගෙදරට සංචාලනය කරන්න"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"ඉහලට සංචාලනය කරන්න"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"තවත් විකල්ප"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"හකුළන්න"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"සෙවීම"</string>
diff --git a/v7/appcompat/res/values-sk/strings.xml b/v7/appcompat/res/values-sk/strings.xml
index 2b09cce..f48351f 100644
--- a/v7/appcompat/res/values-sk/strings.xml
+++ b/v7/appcompat/res/values-sk/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Prejsť na plochu"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Prejsť hore"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Ďalšie možnosti"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Zbaliť"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Hľadať"</string>
diff --git a/v7/appcompat/res/values-sl/strings.xml b/v7/appcompat/res/values-sl/strings.xml
index a522de2..3e8c881 100644
--- a/v7/appcompat/res/values-sl/strings.xml
+++ b/v7/appcompat/res/values-sl/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Krmarjenje domov"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Krmarjenje navzgor"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Več možnosti"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Strni"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Iskanje"</string>
diff --git a/v7/appcompat/res/values-sr/strings.xml b/v7/appcompat/res/values-sr/strings.xml
index c26945b..cb85060 100644
--- a/v7/appcompat/res/values-sr/strings.xml
+++ b/v7/appcompat/res/values-sr/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Одлазак на Почетну"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Кретање нагоре"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Још опција"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Скупи"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Претрага"</string>
diff --git a/v7/appcompat/res/values-sv/strings.xml b/v7/appcompat/res/values-sv/strings.xml
index 3120ad8..be6827e 100644
--- a/v7/appcompat/res/values-sv/strings.xml
+++ b/v7/appcompat/res/values-sv/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Visa startsidan"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigera uppåt"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Fler alternativ"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Komprimera"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Sök"</string>
diff --git a/v7/appcompat/res/values-sw/strings.xml b/v7/appcompat/res/values-sw/strings.xml
index afe54f6..7f07a2a 100644
--- a/v7/appcompat/res/values-sw/strings.xml
+++ b/v7/appcompat/res/values-sw/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Nenda mwanzo"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Nenda juu"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Chaguo zaidi"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Kunja"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Tafuta"</string>
diff --git a/v7/appcompat/res/values-ta-rIN/strings.xml b/v7/appcompat/res/values-ta-rIN/strings.xml
index 542fd34..d72fcc4 100644
--- a/v7/appcompat/res/values-ta-rIN/strings.xml
+++ b/v7/appcompat/res/values-ta-rIN/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"முகப்பிற்கு வழிசெலுத்து"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"மேலே வழிசெலுத்து"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"மேலும் விருப்பங்கள்"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"சுருக்கு"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"தேடு"</string>
diff --git a/v7/appcompat/res/values-te-rIN/strings.xml b/v7/appcompat/res/values-te-rIN/strings.xml
index 5f36cc5..20fbbeb 100644
--- a/v7/appcompat/res/values-te-rIN/strings.xml
+++ b/v7/appcompat/res/values-te-rIN/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"హోమ్కు నావిగేట్ చేయండి"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"పైకి నావిగేట్ చేయండి"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"మరిన్ని ఎంపికలు"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"కుదించండి"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"శోధించు"</string>
diff --git a/v7/appcompat/res/values-th/strings.xml b/v7/appcompat/res/values-th/strings.xml
index d8c04c4..38b5c90 100644
--- a/v7/appcompat/res/values-th/strings.xml
+++ b/v7/appcompat/res/values-th/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"นำทางไปหน้าแรก"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"นำทางขึ้น"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ตัวเลือกอื่น"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ยุบ"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ค้นหา"</string>
diff --git a/v7/appcompat/res/values-tl/strings.xml b/v7/appcompat/res/values-tl/strings.xml
index 0384435..079990f 100644
--- a/v7/appcompat/res/values-tl/strings.xml
+++ b/v7/appcompat/res/values-tl/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Mag-navigate patungo sa home"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Mag-navigate pataas"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Higit pang mga opsyon"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"I-collapse"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Maghanap"</string>
diff --git a/v7/appcompat/res/values-tr/strings.xml b/v7/appcompat/res/values-tr/strings.xml
index c06069c..51ee6ec 100644
--- a/v7/appcompat/res/values-tr/strings.xml
+++ b/v7/appcompat/res/values-tr/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ana ekrana git"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yukarı git"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Diğer seçenekler"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Daralt"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Ara"</string>
diff --git a/v7/appcompat/res/values-uk/strings.xml b/v7/appcompat/res/values-uk/strings.xml
index d07404b..28f6b8e 100644
--- a/v7/appcompat/res/values-uk/strings.xml
+++ b/v7/appcompat/res/values-uk/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Перейти на головний"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Перейти вгору"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Інші опції"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Згорнути"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Пошук"</string>
diff --git a/v7/appcompat/res/values-ur-rPK/strings.xml b/v7/appcompat/res/values-ur-rPK/strings.xml
index 89c0ea6..787651e 100644
--- a/v7/appcompat/res/values-ur-rPK/strings.xml
+++ b/v7/appcompat/res/values-ur-rPK/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"ہوم پر نیویگیٹ کریں"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"اوپر نیویگیٹ کریں"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"مزید اختیارات"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"سکیڑیں"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"تلاش کریں"</string>
diff --git a/v7/appcompat/res/values-uz-rUZ/strings.xml b/v7/appcompat/res/values-uz-rUZ/strings.xml
index 537afa1..033e4e0 100644
--- a/v7/appcompat/res/values-uz-rUZ/strings.xml
+++ b/v7/appcompat/res/values-uz-rUZ/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Boshiga o‘tish"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yuqoriga o‘tish"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Qo‘shimcha sozlamalar"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Yig‘ish"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Izlash"</string>
diff --git a/v7/appcompat/res/values-v11/styles_base_text.xml b/v7/appcompat/res/values-v11/styles_base_text.xml
index d7118c0..4cf4966 100644
--- a/v7/appcompat/res/values-v11/styles_base_text.xml
+++ b/v7/appcompat/res/values-v11/styles_base_text.xml
@@ -17,6 +17,20 @@
<resources>
+ <style name="Base.TextAppearance.AppCompat.Title.Inverse">
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+ <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+ <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+ </style>
+
+ <style name="Base.TextAppearance.AppCompat.Subhead.Inverse">
+ <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
+ <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+ <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+ <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+ </style>
+
<!-- Deprecated text styles -->
<style name="Base.TextAppearance.AppCompat.Inverse">
diff --git a/v7/appcompat/res/values-v11/themes_base.xml b/v7/appcompat/res/values-v11/themes_base.xml
index ca583fa..6880321 100644
--- a/v7/appcompat/res/values-v11/themes_base.xml
+++ b/v7/appcompat/res/values-v11/themes_base.xml
@@ -66,6 +66,7 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+ <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
<item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
<item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -117,6 +118,7 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+ <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
<item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
<item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -167,6 +169,7 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+ <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
<item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
<item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -218,6 +221,7 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+ <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
<item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
<item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -308,7 +312,7 @@
<item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
<!-- ShareActionProvider attributes -->
- <item name="activityChooserViewStyle">@style/Widget.AppCompat.Light.ActivityChooserView</item>
+ <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView</item>
<!-- Toolbar styles -->
<item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
@@ -317,6 +321,7 @@
<item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
<item name="editTextBackground">@drawable/abc_edit_text_material</item>
<item name="editTextColor">?android:attr/textColorPrimary</item>
+ <item name="android:autoCompleteTextViewStyle">@style/Widget.AppCompat.AutoCompleteTextView</item>
<!-- Color palette -->
<item name="colorPrimaryDark">@color/primary_dark_material_light</item>
@@ -326,9 +331,17 @@
<item name="colorControlNormal">?android:attr/textColorSecondary</item>
<item name="colorControlActivated">?attr/colorAccent</item>
<item name="colorControlHighlight">@color/ripple_material_light</item>
+ <item name="colorButtonNormal">@color/button_material_light</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_light</item>
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
+
+ <item name="android:ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+
+ <!-- Button styles -->
+ <item name="android:buttonStyle">@style/Widget.AppCompat.Button</item>
+ <item name="android:buttonStyleSmall">@style/Widget.AppCompat.Button.Small</item>
+ <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item>
</style>
<style name="Base.Theme.AppCompat" parent="Base.V11.Theme.AppCompat">
diff --git a/v7/appcompat/res/values-v14/styles_base_text.xml b/v7/appcompat/res/values-v14/styles_base_text.xml
new file mode 100644
index 0000000..54c8de2
--- /dev/null
+++ b/v7/appcompat/res/values-v14/styles_base_text.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <style name="Base.TextAppearance.AppCompat.Button">
+ <item name="android:textSize">@dimen/abc_text_size_button_material</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textColor">?android:textColorPrimary</item>
+ </style>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index 648dfd2..6a352ef 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -112,6 +112,8 @@
<style name="Base.Widget.AppCompat.Spinner" parent="android:Widget.Material.Spinner" />
+ <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
+
<style name="Base.Widget.AppCompat.Spinner.DropDown.ActionBar" parent="android:Widget.Material.Spinner">
<item name="spinnerMode">dropdown</item>
<item name="disableChildrenWhenDisabled">true</item>
@@ -162,16 +164,20 @@
<!-- Search View result styles -->
<style name="Base.TextAppearance.AppCompat.SearchResult.Title"
- parent="@android:TextAppearance.Material.SearchResult.Title">
+ parent="android:TextAppearance.Material.SearchResult.Title">
</style>
<style name="Base.TextAppearance.AppCompat.SearchResult.Subtitle"
- parent="@android:TextAppearance.Material.SearchResult.Subtitle">
+ parent="android:TextAppearance.Material.SearchResult.Subtitle">
</style>
<style name="Base.Widget.AppCompat.AutoCompleteTextView" parent="android:Widget.Material.AutoCompleteTextView" />
- <style name="Base.Widget.AppCompat.Light.AutoCompleteTextView" parent="android:Widget.Material.AutoCompleteTextView" />
+ <style name="Base.Widget.AppCompat.RatingBar" parent="android:Widget.Material.RatingBar" />
+
+ <style name="Base.Widget.AppCompat.Button" parent="android:Widget.Material.Button" />
+
+ <style name="Base.Widget.AppCompat.Button.Small" parent="android:Widget.Material.Button.Small" />
<!-- Progress Bar -->
@@ -183,13 +189,8 @@
parent="android:Widget.Material.ProgressBar">
</style>
- <!-- TODO. Needs updating for Material -->
- <style name="Base.Widget.AppCompat.ActivityChooserView" parent="">
- <item name="android:gravity">center</item>
- <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
- <item name="android:divider">?attr/dividerVertical</item>
- <item name="android:showDividers">middle</item>
- <item name="android:dividerPadding">6dip</item>
- </style>
+ <style name="Base.Widget.AppCompat.TextView.SpinnerItem" parent="android:Widget.Material.TextView.SpinnerItem" />
+
+ <style name="Base.TextAppearance.AppCompat.Widget.TextView.SpinnerItem" parent="android:TextAppearance.Material.Widget.TextView.SpinnerItem" />
</resources>
diff --git a/v7/appcompat/res/values-v21/themes_base.xml b/v7/appcompat/res/values-v21/themes_base.xml
index 7392b30..c586030 100644
--- a/v7/appcompat/res/values-v21/themes_base.xml
+++ b/v7/appcompat/res/values-v21/themes_base.xml
@@ -200,11 +200,16 @@
<style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V21.Theme.AppCompat.Light.Dialog" />
<style name="Base.ThemeOverlay.AppCompat" parent="android:ThemeOverlay.Material">
+ <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+ <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
</style>
<style name="Base.ThemeOverlay.AppCompat.Dark" parent="android:ThemeOverlay.Material.Dark">
<item name="colorControlHighlight">@color/ripple_material_dark</item>
+ <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+ <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+
<!-- Used by MediaRouter -->
<item name="isLightTheme">false</item>
</style>
@@ -212,18 +217,27 @@
<style name="Base.ThemeOverlay.AppCompat.Light" parent="android:ThemeOverlay.Material.Light">
<item name="colorControlHighlight">@color/ripple_material_light</item>
+ <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+ <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+
<!-- Used by MediaRouter -->
<item name="isLightTheme">true</item>
</style>
<style name="Base.ThemeOverlay.AppCompat.ActionBar" parent="android:ThemeOverlay.Material.ActionBar">
<item name="colorControlNormal">?android:attr/textColorPrimary</item>
+
+ <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+ <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
</style>
<style name="Base.ThemeOverlay.AppCompat.Dark.ActionBar" parent="android:ThemeOverlay.Material.Dark.ActionBar">
<item name="colorControlNormal">?android:attr/textColorPrimary</item>
<item name="colorControlHighlight">@color/ripple_material_dark</item>
+ <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+ <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+
<!-- Used by MediaRouter -->
<item name="isLightTheme">false</item>
</style>
diff --git a/v7/appcompat/res/values-vi/strings.xml b/v7/appcompat/res/values-vi/strings.xml
index 21dd883..28b93ab 100644
--- a/v7/appcompat/res/values-vi/strings.xml
+++ b/v7/appcompat/res/values-vi/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Điều hướng về trang chủ"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Điều hướng lên trên"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Thêm tùy chọn"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Thu gọn"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Tìm kiếm"</string>
diff --git a/v7/appcompat/res/values-zh-rCN/strings.xml b/v7/appcompat/res/values-zh-rCN/strings.xml
index 54e2c86..519abd2 100644
--- a/v7/appcompat/res/values-zh-rCN/strings.xml
+++ b/v7/appcompat/res/values-zh-rCN/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"转到主屏幕"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"转到上一层级"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多选项"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"收起"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s:%2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s - %2$s:%3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"搜索"</string>
diff --git a/v7/appcompat/res/values-zh-rHK/strings.xml b/v7/appcompat/res/values-zh-rHK/strings.xml
index e35d465..4974223 100644
--- a/v7/appcompat/res/values-zh-rHK/strings.xml
+++ b/v7/appcompat/res/values-zh-rHK/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"瀏覽主頁"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"向上瀏覽"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多選項"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"收合"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s:%2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s (%2$s):%3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"搜尋"</string>
diff --git a/v7/appcompat/res/values-zh-rTW/strings.xml b/v7/appcompat/res/values-zh-rTW/strings.xml
index 24d530c..97f2589 100644
--- a/v7/appcompat/res/values-zh-rTW/strings.xml
+++ b/v7/appcompat/res/values-zh-rTW/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"瀏覽首頁"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"向上瀏覽"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多選項"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"收合"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s:%2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s - %2$s:%3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"搜尋"</string>
diff --git a/v7/appcompat/res/values-zu/strings.xml b/v7/appcompat/res/values-zu/strings.xml
index a6a06ab..21ee299 100644
--- a/v7/appcompat/res/values-zu/strings.xml
+++ b/v7/appcompat/res/values-zu/strings.xml
@@ -20,6 +20,7 @@
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Zulazulela ekhaya"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Zulazulela phezulu"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Izinketho eziningi"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Goqa"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"Sesha"</string>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 896a80a..3fd90b2 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -70,6 +70,7 @@
<attr name="windowFixedHeightMajor" format="dimension|fraction" />
<attr name="android:windowIsFloating" />
+ <attr name="android:windowAnimationStyle" />
<!-- =================== -->
<!-- Action bar styles -->
@@ -723,14 +724,10 @@
the specified resource. During XML inflation, any child views under the
view with a theme override will inherit the themed context. -->
<attr name="theme" format="reference" />
-
- <attr name="buttonGravity">
- <!-- Push object to the top of its container, not changing its size. -->
- <flag name="top" value="0x30" />
- <!-- Push object to the bottom of its container, not changing its size. -->
- <flag name="bottom" value="0x50" />
- </attr>
+ <!-- Icon drawable to use for the collapse button. -->
<attr name="collapseIcon" format="reference" />
+ <!-- Text to set as the content description for the collapse button. -->
+ <attr name="collapseContentDescription" format="string" />
<!-- Reference to a theme that should be used to inflate popups
shown by widgets in the toolbar. -->
<attr name="popupTheme" />
@@ -817,4 +814,10 @@
<attr name="showText" format="boolean" />
</declare-styleable>
+ <declare-styleable name="SwitchCompatTextAppearance">
+ <attr name="android:textSize" />
+ <attr name="android:textColor" />
+ <attr name="textAllCaps" />
+ </declare-styleable>
+
</resources>
diff --git a/v7/appcompat/res/values/config.xml b/v7/appcompat/res/values/config.xml
index a57f2e4..0f8b7dc 100644
--- a/v7/appcompat/res/values/config.xml
+++ b/v7/appcompat/res/values/config.xml
@@ -32,4 +32,8 @@
it should be disabled in that locale's resources. -->
<bool name="abc_config_actionMenuItemAllCaps">true</bool>
+ <!-- The duration (in milliseconds) of the activity open/close and fragment open/close animations. -->
+ <integer name="abc_config_activityShortDur">150</integer>
+ <integer name="abc_config_activityDefaultDur">220</integer>
+
</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 54baac3..34a81a6 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -58,9 +58,17 @@
(the screen is in landscape). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_height_minor">100%</item>
+ <dimen name="abc_button_inset_vertical_material">6dp</dimen>
+ <dimen name="abc_button_inset_horizontal_material">@dimen/abc_control_inset_material</dimen>
+ <!-- Default inner padding within buttons -->
+ <dimen name="abc_button_padding_vertical_material">@dimen/abc_control_padding_material</dimen>
+ <dimen name="abc_button_padding_horizontal_material">8dp</dimen>
+
<!-- Default insets (outer padding) around controls -->
<dimen name="abc_control_inset_material">4dp</dimen>
<!-- Default inner padding within controls -->
<dimen name="abc_control_padding_material">4dp</dimen>
+ <!-- Default rounded corner for controls -->
+ <dimen name="abc_control_corner_material">2dp</dimen>
</resources>
diff --git a/v7/appcompat/res/values/strings.xml b/v7/appcompat/res/values/strings.xml
index 765833d..5080070 100644
--- a/v7/appcompat/res/values/strings.xml
+++ b/v7/appcompat/res/values/strings.xml
@@ -24,6 +24,9 @@
<!-- Content description for the action menu overflow button. [CHAR LIMIT=NONE] -->
<string name="abc_action_menu_overflow_description">More options</string>
+ <!-- Content description for the Toolbar icon used to collapse an expanded action mode. [CHAR LIMIT=NONE] -->
+ <string name="abc_toolbar_collapse_description">Collapse</string>
+
<!-- Formatting string for describing the action bar's title/home/up affordance.
This is a single tappable "button" that includes the app icon, the Up indicator
(usually a "<" chevron) and the window title text.
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index 1b8b53b..f80aae5 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -120,6 +120,8 @@
<style name="Widget.AppCompat.Spinner" parent="Base.Widget.AppCompat.Spinner" />
+ <style name="Widget.AppCompat.Spinner.Underlined" parent="Base.Widget.AppCompat.Spinner.Underlined" />
+
<style name="Widget.AppCompat.Spinner.DropDown" />
<style name="Widget.AppCompat.Spinner.DropDown.ActionBar" parent="Base.Widget.AppCompat.Spinner.DropDown.ActionBar" />
@@ -187,18 +189,10 @@
parent="Base.Widget.AppCompat.AutoCompleteTextView">
</style>
- <style name="Widget.AppCompat.Light.AutoCompleteTextView"
- parent="Base.Widget.AppCompat.Light.AutoCompleteTextView">
- </style>
-
<style name="Widget.AppCompat.ActivityChooserView"
parent="Base.Widget.AppCompat.ActivityChooserView">
</style>
- <style name="Widget.AppCompat.Light.ActivityChooserView"
- parent="Base.Widget.AppCompat.Light.ActivityChooserView">
- </style>
-
<style name="Widget.AppCompat.SearchView" parent="Base.Widget.AppCompat.SearchView" />
<style name="Widget.AppCompat.EditText"
@@ -207,6 +201,14 @@
<style name="Widget.AppCompat.CompoundButton.Switch" parent="Base.Widget.AppCompat.CompoundButton.Switch" />
+ <style name="Widget.AppCompat.RatingBar" parent="Base.Widget.AppCompat.RatingBar" />
+
+ <style name="Widget.AppCompat.Button" parent="Base.Widget.AppCompat.Button" />
+
+ <style name="Widget.AppCompat.Button.Small" parent="Base.Widget.AppCompat.Button.Small" />
+
+ <style name="Widget.AppCompat.TextView.SpinnerItem" parent="Base.Widget.AppCompat.TextView.SpinnerItem" />
+
<!-- Toolbar -->
<style name="Widget.AppCompat.Toolbar" parent="Base.Widget.AppCompat.Toolbar" />
@@ -272,6 +274,8 @@
<style name="TextAppearance.AppCompat.Widget.Switch" parent="Base.TextAppearance.AppCompat.Widget.Switch" />
+ <style name="TextAppearance.AppCompat.Widget.TextView.SpinnerItem" parent="Base.TextAppearance.AppCompat.Widget.TextView.SpinnerItem" />
+
<!--
The following themes are deprecated.
-->
@@ -291,5 +295,7 @@
<style name="Widget.AppCompat.Light.Spinner.DropDown.ActionBar" parent="Widget.AppCompat.Spinner.DropDown.ActionBar" />
<style name="Widget.AppCompat.Light.ListView.DropDown" parent="Widget.AppCompat.ListView.DropDown" />
<style name="Widget.AppCompat.Light.ListPopupWindow" parent="Widget.AppCompat.ListPopupWindow" />
+ <style name="Widget.AppCompat.Light.AutoCompleteTextView" parent="Widget.AppCompat.AutoCompleteTextView" />
+ <style name="Widget.AppCompat.Light.ActivityChooserView" parent="Widget.AppCompat.ActivityChooserView" />
</resources>
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index db2cd73..fb28f4e 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -36,9 +36,6 @@
<item name="actionButtonStyle">@style/Widget.AppCompat.ActionButton</item>
<item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow</item>
- <item name="progressBarStyle">@style/Widget.AppCompat.ProgressBar.Horizontal</item>
- <item name="indeterminateProgressStyle">@style/Widget.AppCompat.ProgressBar</item>
-
<item name="android:gravity">center_vertical</item>
<item name="elevation">8dp</item>
<item name="popupTheme">?attr/actionBarPopupTheme</item>
@@ -186,6 +183,10 @@
<item name="android:dropDownVerticalOffset">0dip</item>
</style>
+ <style name="Base.Widget.AppCompat.Spinner.Underlined">
+ <item name="android:background">@drawable/abc_spinner_textfield_background_material</item>
+ </style>
+
<style name="Base.Widget.AppCompat.Spinner.DropDown.ActionBar" parent="android:Widget">
<item name="spinnerMode">dropdown</item>
@@ -281,28 +282,21 @@
</style>
<style name="Base.Widget.AppCompat.AutoCompleteTextView" parent="android:Widget.AutoCompleteTextView">
- <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_dark</item>
+ <item name="android:dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
<item name="android:popupBackground">@drawable/abc_popup_background_mtrl_mult</item>
+ <item name="android:background">?attr/editTextBackground</item>
<item name="android:textColor">?attr/editTextColor</item>
- </style>
-
- <style name="Base.Widget.AppCompat.Light.AutoCompleteTextView" parent="android:Widget.AutoCompleteTextView">
- <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_light</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
</style>
<style name="Base.Widget.AppCompat.ActivityChooserView" parent="">
<item name="android:gravity">center</item>
- <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
+ <item name="android:background">@drawable/abc_ab_share_pack_mtrl_alpha</item>
<item name="divider">?attr/dividerVertical</item>
<item name="showDividers">middle</item>
<item name="dividerPadding">6dip</item>
</style>
- <style name="Base.Widget.AppCompat.Light.ActivityChooserView"
- parent="Base.Widget.AppCompat.ActivityChooserView">
- <item name="android:background">@drawable/abc_ab_share_pack_holo_light</item>
- </style>
-
<style name="Base.Widget.AppCompat.PopupWindow" parent="android:Widget.PopupWindow">
</style>
@@ -312,8 +306,8 @@
<item name="android:minHeight">?attr/actionBarSize</item>
<item name="titleMargins">4dp</item>
<item name="maxButtonHeight">56dp</item>
- <item name="buttonGravity">top</item>
<item name="collapseIcon">?attr/homeAsUpIndicator</item>
+ <item name="collapseContentDescription">@string/abc_toolbar_collapse_description</item>
<item name="contentInsetStart">16dp</item>
</style>
@@ -348,16 +342,20 @@
<item name="android:textColor">?attr/editTextColor</item>
<item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
</style>
-
- <style name="Base.Widget.AppCompat.DrawerArrowToggle" parent="">
+ <!-- contains values used in all dpis -->
+ <style name="Base.Widget.AppCompat.DrawerArrowToggle.Common" parent="">
<item name="color">?android:attr/textColorSecondary</item>
+ <item name="middleBarArrowSize">16dp</item>
+ <item name="spinBars">true</item>
<item name="thickness">2dp</item>
+ <item name="topBottomBarArrowSize">11.31dp</item>
+ </style>
+
+ <!-- contains values used in all dpis except hdpi and xxhdpi -->
+ <style name="Base.Widget.AppCompat.DrawerArrowToggle" parent="Base.Widget.AppCompat.DrawerArrowToggle.Common">
<item name="barSize">18dp</item>
<item name="gapBetweenBars">3dp</item>
- <item name="topBottomBarArrowSize">11.31dp</item>
- <item name="middleBarArrowSize">16dp</item>
<item name="drawableSize">24dp</item>
- <item name="spinBars">true</item>
</style>
<style name="Base.Widget.AppCompat.CompoundButton.Switch" parent="android:Widget.CompoundButton">
@@ -370,4 +368,34 @@
<style name="Base.TextAppearance.AppCompat.Widget.Switch" parent="TextAppearance.AppCompat.Button" />
+ <style name="Base.Widget.AppCompat.RatingBar" parent="android:Widget.RatingBar">
+ <item name="android:progressDrawable">@drawable/abc_ratingbar_full_material</item>
+ <item name="android:indeterminateDrawable">@drawable/abc_ratingbar_full_material</item>
+ </style>
+
+ <!-- Bordered ink button -->
+ <style name="Base.Widget.AppCompat.Button" parent="android:Widget">
+ <item name="android:background">@drawable/abc_btn_default_mtrl_shape</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
+ <item name="android:minHeight">48dip</item>
+ <item name="android:minWidth">88dip</item>
+ <item name="android:focusable">true</item>
+ <item name="android:clickable">true</item>
+ <item name="android:gravity">center_vertical|center_horizontal</item>
+ </style>
+
+ <!-- Small bordered ink button -->
+ <style name="Base.Widget.AppCompat.Button.Small">
+ <item name="android:minHeight">48dip</item>
+ <item name="android:minWidth">48dip</item>
+ </style>
+
+ <style name="Base.Widget.AppCompat.TextView.SpinnerItem" parent="android:Widget.TextView.SpinnerItem">
+ <item name="android:textAppearance">@style/TextAppearance.AppCompat.Widget.TextView.SpinnerItem</item>
+ <item name="android:paddingLeft">8dp</item>
+ <item name="android:paddingRight">8dp</item>
+ </style>
+
+ <style name="Base.TextAppearance.AppCompat.Widget.TextView.SpinnerItem" parent="TextAppearance.AppCompat.Menu" />
+
</resources>
diff --git a/v7/appcompat/res/values/styles_base_text.xml b/v7/appcompat/res/values/styles_base_text.xml
index 92a02bb..78e119c 100644
--- a/v7/appcompat/res/values/styles_base_text.xml
+++ b/v7/appcompat/res/values/styles_base_text.xml
@@ -58,8 +58,6 @@
<style name="Base.TextAppearance.AppCompat.Title.Inverse">
<item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
<item name="android:textColorHint">?android:attr/textColorHintInverse</item>
- <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
- <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
</style>
<style name="Base.TextAppearance.AppCompat.Subhead">
@@ -70,8 +68,6 @@
<style name="Base.TextAppearance.AppCompat.Subhead.Inverse">
<item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
<item name="android:textColorHint">?android:attr/textColorHintInverse</item>
- <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
- <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
</style>
<style name="Base.TextAppearance.AppCompat.Body2">
diff --git a/v7/appcompat/res/values/themes.xml b/v7/appcompat/res/values/themes.xml
index 05b8657..3409c68 100644
--- a/v7/appcompat/res/values/themes.xml
+++ b/v7/appcompat/res/values/themes.xml
@@ -57,9 +57,8 @@
<style name="Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Light.Dialog" />
<!-- Menu/item attributes -->
- <style name="Theme.AppCompat.CompactMenu"
- parent="Base.Theme.AppCompat.CompactMenu">
- </style>
+ <style name="Theme.AppCompat.CompactMenu" parent="Base.Theme.AppCompat.CompactMenu" />
+ <style name="Animation.AppCompat.DropDownUp" parent="Base.Animation.AppCompat.DropDownUp" />
<style name="ThemeOverlay.AppCompat" parent="Base.ThemeOverlay.AppCompat" />
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index 3c0ee65..c151d3b 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -47,7 +47,7 @@
<item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_light</item>
<item name="android:textColorHint">@color/hint_foreground_material_dark</item>
<item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
- <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>m>
+ <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
<item name="android:textColorLink">@color/link_text_material_dark</item>
<!-- Text styles -->
@@ -61,6 +61,7 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+ <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
<item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
<item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -107,6 +108,7 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+ <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
<item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
<item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -138,7 +140,7 @@
<item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_light</item>
<item name="android:textColorHint">@color/hint_foreground_material_dark</item>
<item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
- <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>m>
+ <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
<item name="android:textColorLink">@color/link_text_material_dark</item>
<!-- Text styles -->
@@ -152,6 +154,7 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+ <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
<item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
<item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -260,6 +263,7 @@
<item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
<item name="editTextBackground">@drawable/abc_edit_text_material</item>
<item name="editTextColor">?android:attr/textColorPrimary</item>
+ <item name="android:autoCompleteTextViewStyle">@style/Widget.AppCompat.AutoCompleteTextView</item>
<!-- Color palette -->
<item name="colorPrimaryDark">@color/primary_dark_material_dark</item>
@@ -269,11 +273,19 @@
<item name="colorControlNormal">?android:attr/textColorSecondary</item>
<item name="colorControlActivated">?attr/colorAccent</item>
<item name="colorControlHighlight">@color/ripple_material_dark</item>
+ <item name="colorButtonNormal">@color/button_material_dark</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_dark</item>
<item name="drawerArrowStyle">@style/Widget.AppCompat.DrawerArrowToggle</item>
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
+
+ <item name="android:ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+
+ <!-- Button styles -->
+ <item name="android:buttonStyle">@style/Widget.AppCompat.Button</item>
+ <item name="android:buttonStyleSmall">@style/Widget.AppCompat.Button.Small</item>
+ <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item>
</style>
<!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
@@ -359,7 +371,7 @@
<item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
<!-- ShareActionProvider attributes -->
- <item name="activityChooserViewStyle">@style/Widget.AppCompat.Light.ActivityChooserView</item>
+ <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView</item>
<!-- Toolbar styles -->
<item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
@@ -368,6 +380,7 @@
<item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
<item name="editTextBackground">@drawable/abc_edit_text_material</item>
<item name="editTextColor">?android:attr/textColorPrimary</item>
+ <item name="android:autoCompleteTextViewStyle">@style/Widget.AppCompat.AutoCompleteTextView</item>
<!-- Color palette -->
<item name="colorPrimaryDark">@color/primary_dark_material_light</item>
@@ -377,11 +390,19 @@
<item name="colorControlNormal">?android:attr/textColorSecondary</item>
<item name="colorControlActivated">?attr/colorAccent</item>
<item name="colorControlHighlight">@color/ripple_material_light</item>
+ <item name="colorButtonNormal">@color/button_material_light</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_light</item>
<item name="drawerArrowStyle">@style/Widget.AppCompat.DrawerArrowToggle</item>
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
+
+ <item name="android:ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+
+ <!-- Button styles -->
+ <item name="android:buttonStyle">@style/Widget.AppCompat.Button</item>
+ <item name="android:buttonStyleSmall">@style/Widget.AppCompat.Button.Small</item>
+ <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item>
</style>
<style name="Base.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
@@ -406,6 +427,12 @@
<style name="Base.Theme.AppCompat.CompactMenu" parent="">
<item name="android:itemTextAppearance">?android:attr/textAppearanceMedium</item>
<item name="android:listViewStyle">@style/Widget.AppCompat.ListView.Menu</item>
+ <item name="android:windowAnimationStyle">@style/Animation.AppCompat.DropDownUp</item>
+ </style>
+
+ <style name="Base.Animation.AppCompat.DropDownUp" parent="android:Animation">
+ <item name="android:windowEnterAnimation">@anim/abc_grow_fade_in_from_bottom</item>
+ <item name="android:windowExitAnimation">@anim/abc_shrink_fade_out_from_bottom</item>
</style>
<style name="Base.V7.Theme.AppCompat.Dialog" parent="Platform.AppCompat.Dialog">
@@ -497,6 +524,7 @@
<item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
<item name="editTextBackground">@drawable/abc_edit_text_material</item>
<item name="editTextColor">?android:attr/textColorPrimary</item>
+ <item name="android:autoCompleteTextViewStyle">@style/Widget.AppCompat.AutoCompleteTextView</item>
<!-- Color palette -->
<item name="colorPrimaryDark">@color/primary_dark_material_dark</item>
@@ -506,9 +534,17 @@
<item name="colorControlNormal">?android:attr/textColorSecondary</item>
<item name="colorControlActivated">?attr/colorAccent</item>
<item name="colorControlHighlight">@color/ripple_material_dark</item>
+ <item name="colorButtonNormal">@color/button_material_dark</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_dark</item>
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
+
+ <item name="android:ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+
+ <!-- Button styles -->
+ <item name="android:buttonStyle">@style/Widget.AppCompat.Button</item>
+ <item name="android:buttonStyleSmall">@style/Widget.AppCompat.Button.Small</item>
+ <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item>
</style>
<style name="Base.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog" />
@@ -589,7 +625,7 @@
<item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_light</item>
<item name="android:textColorHint">@color/hint_foreground_material_dark</item>
<item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
- <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>m>
+ <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
<item name="android:textColorLink">@color/link_text_material_dark</item>
<!-- Action Bar styles -->
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBar.java b/v7/appcompat/src/android/support/v7/app/ActionBar.java
index c174cb4..785b7b8 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBar.java
@@ -1059,6 +1059,11 @@
return false;
}
+ /** @hide **/
+ public boolean onKeyShortcut(int keyCode, KeyEvent ev) {
+ return false;
+ }
+
/** @hide */
public boolean collapseActionView() {
return false;
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
index 5fa8cfb..2168d5e 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
@@ -124,6 +124,12 @@
}
@Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
@@ -142,15 +148,6 @@
}
@Override
- public View onCreatePanelView(int featureId) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- return getDelegate().onCreatePanelView(featureId);
- } else {
- return super.onCreatePanelView(featureId);
- }
- }
-
- @Override
public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
if (super.onMenuItemSelected(featureId, item)) {
return true;
@@ -519,12 +516,11 @@
}
@Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // First let the Activity try and handle it (for back, etc)
- if (super.onKeyDown(keyCode, event)) {
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (getDelegate().dispatchKeyEvent(event)) {
return true;
}
- return getDelegate().onKeyDown(keyCode, event);
+ return super.dispatchKeyEvent(event);
}
/**
@@ -549,7 +545,7 @@
return result;
}
// If we reach here super didn't create a View, so let our delegate attempt it
- return getDelegate().createView(name, attrs);
+ return getDelegate().createView(name, context, attrs);
}
private ActionBarActivityDelegate getDelegate() {
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
index 855f9e6..9599adc 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
@@ -29,6 +29,7 @@
import android.support.v7.appcompat.R;
import android.support.v7.internal.app.WindowCallback;
import android.support.v7.internal.view.SupportMenuInflater;
+import android.support.v7.internal.widget.TintTypedArray;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
@@ -101,7 +102,7 @@
@Override
public View onCreatePanelView(int featureId) {
- return null;
+ return mActivity.onCreatePanelView(featureId);
}
};
// The fake window callback we're currently using
@@ -126,6 +127,10 @@
return mActionBar;
}
+ final ActionBar peekSupportActionBar() {
+ return mActionBar;
+ }
+
protected final void setSupportActionBar(ActionBar actionBar) {
mActionBar = actionBar;
}
@@ -148,13 +153,21 @@
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
- mHasActionBar = a.getBoolean(R.styleable.Theme_windowActionBar, false);
- mOverlayActionBar = a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false);
- mOverlayActionMode = a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false);
+ if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) {
+ mHasActionBar = true;
+ }
+ if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) {
+ mOverlayActionBar = true;
+ }
+ if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) {
+ mOverlayActionMode = true;
+ }
mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false);
a.recycle();
}
+ abstract void onPostCreate(Bundle savedInstanceState);
+
abstract void onConfigurationChanged(Configuration newConfig);
abstract void onStop();
@@ -176,8 +189,6 @@
abstract boolean supportRequestWindowFeature(int featureId);
// Methods used to create and respond to options menu
- abstract View onCreatePanelView(int featureId);
-
abstract boolean onPreparePanel(int featureId, View view, Menu menu);
abstract void onPanelClosed(int featureId, Menu menu);
@@ -208,9 +219,7 @@
abstract void setSupportProgress(int progress);
- boolean onKeyDown(int keyCode, KeyEvent event) {
- return false;
- }
+ abstract boolean dispatchKeyEvent(KeyEvent event);
abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
@@ -259,16 +268,15 @@
return context;
}
- abstract View createView(String name, @NonNull AttributeSet attrs);
-
+ abstract View createView(String name, @NonNull Context context, @NonNull AttributeSet attrs);
private class ActionBarDrawableToggleImpl implements
android.support.v7.app.ActionBarDrawerToggle.Delegate,
ActionBarDrawerToggle.Delegate {
@Override
public Drawable getThemeUpIndicator() {
- final TypedArray a = ActionBarActivityDelegate.this.getActionBarThemedContext()
- .obtainStyledAttributes(new int[]{ getHomeAsUpIndicatorAttrId() });
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
+ getActionBarThemedContext(), null, new int[]{ getHomeAsUpIndicatorAttrId() });
final Drawable result = a.getDrawable(0);
a.recycle();
return result;
@@ -280,6 +288,12 @@
}
@Override
+ public boolean isNavigationVisible() {
+ final ActionBar ab = getSupportActionBar();
+ return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+ }
+
+ @Override
public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
ActionBar ab = getSupportActionBar();
if (ab != null) {
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
index 6a4f9ba..0c7d7c9 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
@@ -20,7 +20,9 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -32,7 +34,6 @@
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.support.v7.appcompat.R;
-import android.support.v7.internal.VersionUtils;
import android.support.v7.internal.app.ToolbarActionBar;
import android.support.v7.internal.app.WindowCallback;
import android.support.v7.internal.app.WindowDecorActionBar;
@@ -44,18 +45,24 @@
import android.support.v7.internal.widget.ActionBarContextView;
import android.support.v7.internal.widget.DecorContentParent;
import android.support.v7.internal.widget.FitWindowsViewGroup;
-import android.support.v7.internal.widget.ProgressBarCompat;
+import android.support.v7.internal.widget.TintAutoCompleteTextView;
+import android.support.v7.internal.widget.TintButton;
import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
+import android.support.v7.internal.widget.TintManager;
+import android.support.v7.internal.widget.TintMultiAutoCompleteTextView;
import android.support.v7.internal.widget.TintRadioButton;
+import android.support.v7.internal.widget.TintRatingBar;
import android.support.v7.internal.widget.TintSpinner;
import android.support.v7.internal.widget.ViewStubCompat;
import android.support.v7.internal.widget.ViewUtils;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
+import android.util.AndroidRuntimeException;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
@@ -64,10 +71,13 @@
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.Window;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import android.widget.PopupWindow;
@@ -75,6 +85,8 @@
import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR;
import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR_OVERLAY;
import static android.support.v4.view.WindowCompat.FEATURE_ACTION_MODE_OVERLAY;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.Window.FEATURE_OPTIONS_PANEL;
class ActionBarActivityDelegateBase extends ActionBarActivityDelegate
@@ -125,8 +137,6 @@
private boolean mEnableDefaultActionBarUp;
- private ListMenuPresenter mToolbarListMenuPresenter;
-
private Rect mTempRect1;
private Rect mTempRect2;
@@ -141,7 +151,8 @@
mWindowDecor = (ViewGroup) mActivity.getWindow().getDecorView();
if (NavUtils.getParentActivityName(mActivity) != null) {
- ActionBar ab = getSupportActionBar();
+ // Peek at the Action Bar and update it if it already exists
+ ActionBar ab = peekSupportActionBar();
if (ab == null) {
mEnableDefaultActionBarUp = true;
} else {
@@ -151,6 +162,12 @@
}
@Override
+ void onPostCreate(Bundle savedInstanceState) {
+ // Make sure that the sub decor is installed
+ ensureSubDecor();
+ }
+
+ @Override
public ActionBar createSupportActionBar() {
ensureSubDecor();
ActionBar ab = new WindowDecorActionBar(mActivity, mOverlayActionBar);
@@ -165,17 +182,12 @@
throw new IllegalStateException("This Activity already has an action bar supplied " +
"by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
"windowActionBar to false in your theme to use a Toolbar instead.");
- } else if (ab instanceof ToolbarActionBar) {
- // Make sure we reset the old toolbar AB's list menu presenter
- ((ToolbarActionBar) ab).setListMenuPresenter(null);
}
// Need to make sure we give the action bar the default window callback. Otherwise multiple
// setSupportActionBar() calls lead to memory leaks
ToolbarActionBar tbab = new ToolbarActionBar(toolbar, mActivity.getTitle(),
mActivity.getWindow(), mDefaultWindowCallback);
- ensureToolbarListMenuPresenter();
- tbab.setListMenuPresenter(mToolbarListMenuPresenter);
setSupportActionBar(tbab);
setWindowCallback(tbab.getWrappedWindowCallback());
tbab.invalidateOptionsMenu();
@@ -335,14 +347,24 @@
// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(mSubDecor);
+ final ViewGroup decorContent = (ViewGroup) mActivity.findViewById(android.R.id.content);
+ final ViewGroup abcContent = (ViewGroup) mSubDecor.findViewById(
+ R.id.action_bar_activity_content);
+
+ // There might be Views already added to the Window's content view so we need to
+ // migrate them to our content view
+ while (decorContent.getChildCount() > 0) {
+ final View child = decorContent.getChildAt(0);
+ decorContent.removeViewAt(0);
+ abcContent.addView(child);
+ }
+
// Now set the Activity's content view with the decor
mActivity.superSetContentView(mSubDecor);
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
- final View decorContent = mActivity.findViewById(android.R.id.content);
decorContent.setId(View.NO_ID);
- View abcContent = mActivity.findViewById(R.id.action_bar_activity_content);
abcContent.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
@@ -436,23 +458,28 @@
public boolean supportRequestWindowFeature(int featureId) {
switch (featureId) {
case FEATURE_ACTION_BAR:
+ throwFeatureRequestIfSubDecorInstalled();
mHasActionBar = true;
return true;
case FEATURE_ACTION_BAR_OVERLAY:
+ throwFeatureRequestIfSubDecorInstalled();
mOverlayActionBar = true;
return true;
case FEATURE_ACTION_MODE_OVERLAY:
+ throwFeatureRequestIfSubDecorInstalled();
mOverlayActionMode = true;
return true;
case Window.FEATURE_PROGRESS:
+ throwFeatureRequestIfSubDecorInstalled();
mFeatureProgress = true;
return true;
case Window.FEATURE_INDETERMINATE_PROGRESS:
+ throwFeatureRequestIfSubDecorInstalled();
mFeatureIndeterminateProgress = true;
return true;
- default:
- return mActivity.requestWindowFeature(featureId);
}
+
+ return mActivity.requestWindowFeature(featureId);
}
@Override
@@ -467,33 +494,6 @@
}
@Override
- public View onCreatePanelView(int featureId) {
- View panelView = null;
-
- // If there isn't an action mode currently being displayed
- if (mActionMode == null) {
- // Let our window callback try first
- WindowCallback callback = getWindowCallback();
- if (callback != null) {
- panelView = callback.onCreatePanelView(featureId);
- }
-
- if (panelView == null && mToolbarListMenuPresenter == null) {
- // Only check our panels if the callback didn't return a view and we do not have
- // a ListMenuPresenter for Toolbars. We check for the ListMenuPresenter because
- // once created, Toolbar needs to control the panel view regardless of whether it
- // has any non-action items to display.
- PanelFeatureState st = getPanelState(featureId, true);
- openPanel(st, null);
- if (st.isOpen) {
- panelView = st.shownPanelView;
- }
- }
- }
- return panelView;
- }
-
- @Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
if (featureId != Window.FEATURE_OPTIONS_PANEL) {
return getWindowCallback().onCreatePanelMenu(featureId, menu);
@@ -511,12 +511,6 @@
@Override
public void onPanelClosed(final int featureId, Menu menu) {
- PanelFeatureState st = getPanelState(featureId, false);
- if (st != null) {
- // If we know about the feature id, update it's state
- closePanel(st, false);
- }
-
if (featureId == FEATURE_ACTION_BAR) {
ActionBar ab = getSupportActionBar();
if (ab != null) {
@@ -683,25 +677,22 @@
@Override
void setSupportProgressBarVisibility(boolean visible) {
- updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
- Window.PROGRESS_VISIBILITY_OFF);
+ // noop
}
@Override
void setSupportProgressBarIndeterminateVisibility(boolean visible) {
- updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
- Window.PROGRESS_VISIBILITY_OFF);
+ // noop
}
@Override
void setSupportProgressBarIndeterminate(boolean indeterminate) {
- updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON
- : Window.PROGRESS_INDETERMINATE_OFF);
+ // noop
}
@Override
void setSupportProgress(int progress) {
- updateProgressBars(Window.PROGRESS_START + progress);
+ // noop
}
@Override
@@ -711,6 +702,12 @@
@Override
boolean onKeyShortcut(int keyCode, KeyEvent ev) {
+ // Let the Action Bar have a chance at handling the shortcut
+ ActionBar ab = getSupportActionBar();
+ if (ab != null && ab.onKeyShortcut(keyCode, ev)) {
+ return true;
+ }
+
// If the panel is already prepared, then perform the shortcut using it.
boolean handled;
if (mPreparedPanel != null) {
@@ -741,85 +738,73 @@
}
@Override
- boolean onKeyDown(int keyCode, KeyEvent event) {
+ boolean dispatchKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ final int action = event.getAction();
+ final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+ return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event);
+ }
+
+ protected boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event);
+ return true;
+ case KeyEvent.KEYCODE_BACK:
+ PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+ if (st != null && st.isOpen) {
+ closePanel(st, true);
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ protected boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
+ onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event);
+ return true;
+ }
+
// On API v7-10 we need to manually call onKeyShortcut() as this is not called
// from the Activity
- return onKeyShortcut(keyCode, event);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ return onKeyShortcut(keyCode, event);
+ }
+ return false;
}
@Override
- View createView(final String name, @NonNull AttributeSet attrs) {
+ View createView(final String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (Build.VERSION.SDK_INT < 21) {
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
// standard framework versions
switch (name) {
case "EditText":
- return new TintEditText(mActivity, attrs);
+ return new TintEditText(context, attrs);
case "Spinner":
- return new TintSpinner(mActivity, attrs);
+ return new TintSpinner(context, attrs);
case "CheckBox":
- return new TintCheckBox(mActivity, attrs);
+ return new TintCheckBox(context, attrs);
case "RadioButton":
- return new TintRadioButton(mActivity, attrs);
+ return new TintRadioButton(context, attrs);
case "CheckedTextView":
- return new TintCheckedTextView(mActivity, attrs);
+ return new TintCheckedTextView(context, attrs);
+ case "AutoCompleteTextView":
+ return new TintAutoCompleteTextView(context, attrs);
+ case "MultiAutoCompleteTextView":
+ return new TintMultiAutoCompleteTextView(context, attrs);
+ case "RatingBar":
+ return new TintRatingBar(context, attrs);
+ case "Button":
+ return new TintButton(context, attrs);
}
}
return null;
}
- /**
- * Progress Bar function. Mostly extracted from PhoneWindow.java
- */
- private void updateProgressBars(int value) {
- ProgressBarCompat circularProgressBar = getCircularProgressBar();
- ProgressBarCompat horizontalProgressBar = getHorizontalProgressBar();
-
- if (value == Window.PROGRESS_VISIBILITY_ON) {
- if (mFeatureProgress) {
- int level = horizontalProgressBar.getProgress();
- int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
- View.VISIBLE : View.INVISIBLE;
- horizontalProgressBar.setVisibility(visibility);
- }
- if (mFeatureIndeterminateProgress) {
- circularProgressBar.setVisibility(View.VISIBLE);
- }
- } else if (value == Window.PROGRESS_VISIBILITY_OFF) {
- if (mFeatureProgress) {
- horizontalProgressBar.setVisibility(View.GONE);
- }
- if (mFeatureIndeterminateProgress) {
- circularProgressBar.setVisibility(View.GONE);
- }
- } else if (value == Window.PROGRESS_INDETERMINATE_ON) {
- horizontalProgressBar.setIndeterminate(true);
- } else if (value == Window.PROGRESS_INDETERMINATE_OFF) {
- horizontalProgressBar.setIndeterminate(false);
- } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) {
- // We want to set the progress value before testing for visibility
- // so that when the progress bar becomes visible again, it has the
- // correct level.
- horizontalProgressBar.setProgress(value - Window.PROGRESS_START);
-
- if (value < Window.PROGRESS_END) {
- showProgressBars(horizontalProgressBar, circularProgressBar);
- } else {
- hideProgressBars(horizontalProgressBar, circularProgressBar);
- }
- }
- }
-
- private void openPanel(int featureId, KeyEvent event) {
- if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
- mDecorContentParent.canShowOverflowMenu() &&
- !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity))) {
- mDecorContentParent.showOverflowMenu();
- } else {
- openPanel(getPanelState(featureId, true), event);
- }
- }
-
private void openPanel(final PanelFeatureState st, KeyEvent event) {
// Already open, return
if (st.isOpen || isDestroyed()) {
@@ -848,27 +833,83 @@
return;
}
+ final WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
+ if (wm == null) {
+ return;
+ }
+
// Prepare panel (should have been done before, but just in case)
if (!preparePanel(st, event)) {
return;
}
+ int width = WRAP_CONTENT;
if (st.decorView == null || st.refreshDecorView) {
- initializePanelDecor(st);
- }
+ if (st.decorView == null) {
+ // Initialize the panel decor, this will populate st.decorView
+ if (!initializePanelDecor(st) || (st.decorView == null))
+ return;
+ } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
+ // Decor needs refreshing, so remove its views
+ st.decorView.removeAllViews();
+ }
- // This will populate st.shownPanelView
- if (!initializePanelContent(st) || !st.hasPanelItems()) {
- return;
+ // This will populate st.shownPanelView
+ if (!initializePanelContent(st) || !st.hasPanelItems()) {
+ return;
+ }
+
+ ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
+ if (lp == null) {
+ lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ }
+
+ int backgroundResId = st.background;
+ st.decorView.setBackgroundResource(backgroundResId);
+
+ ViewParent shownPanelParent = st.shownPanelView.getParent();
+ if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
+ ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
+ }
+ st.decorView.addView(st.shownPanelView, lp);
+
+ /*
+ * Give focus to the view, if it or one of its children does not
+ * already have it.
+ */
+ if (!st.shownPanelView.hasFocus()) {
+ st.shownPanelView.requestFocus();
+ }
+ } else if (st.createdPanelView != null) {
+ // If we already had a panel view, carry width=MATCH_PARENT through
+ // as we did above when it was created.
+ ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
+ if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ width = MATCH_PARENT;
+ }
}
st.isHandled = false;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ width, WRAP_CONTENT,
+ st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+
+ lp.gravity = st.gravity;
+ lp.windowAnimations = st.windowAnimations;
+
+ wm.addView(st.decorView, lp);
st.isOpen = true;
}
- private void initializePanelDecor(PanelFeatureState st) {
- st.decorView = mWindowDecor;
+ private boolean initializePanelDecor(PanelFeatureState st) {
st.setStyle(getActionBarThemedContext());
+ st.decorView = new ListMenuDecorView(st.listPresenterContext);
+ st.gravity = Gravity.CENTER | Gravity.BOTTOM;
+ return true;
}
private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) {
@@ -876,7 +917,7 @@
(!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity)) ||
mDecorContentParent.isOverflowMenuShowPending())) {
- WindowCallback cb = getWindowCallback();
+ final WindowCallback cb = getWindowCallback();
if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
if (cb != null && !isDestroyed()) {
@@ -892,7 +933,7 @@
// If we don't have a menu or we're waiting for a full content refresh,
// forget it. This is a lingering event that no longer matters.
if (st.menu != null && !st.refreshMenuContent &&
- cb.onPreparePanel(FEATURE_OPTIONS_PANEL, null, st.menu)) {
+ cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu);
mDecorContentParent.showOverflowMenu();
}
@@ -901,7 +942,7 @@
mDecorContentParent.hideOverflowMenu();
if (!isDestroyed()) {
final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
- mActivity.onPanelClosed(FEATURE_ACTION_BAR, st.menu);
+ cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu);
}
}
return;
@@ -915,43 +956,6 @@
openPanel(st, null);
}
- private void showProgressBars(ProgressBarCompat horizontalProgressBar,
- ProgressBarCompat spinnyProgressBar) {
- if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) {
- spinnyProgressBar.setVisibility(View.VISIBLE);
- }
- // Only show the progress bars if the primary progress is not complete
- if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) {
- horizontalProgressBar.setVisibility(View.VISIBLE);
- }
- }
-
- private void hideProgressBars(ProgressBarCompat horizontalProgressBar,
- ProgressBarCompat spinnyProgressBar) {
- if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) {
- spinnyProgressBar.setVisibility(View.INVISIBLE);
- }
- if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) {
- horizontalProgressBar.setVisibility(View.INVISIBLE);
- }
- }
-
- private ProgressBarCompat getCircularProgressBar() {
- ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_circular);
- if (pb != null) {
- pb.setVisibility(View.INVISIBLE);
- }
- return pb;
- }
-
- private ProgressBarCompat getHorizontalProgressBar() {
- ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_horizontal);
- if (pb != null) {
- pb.setVisibility(View.INVISIBLE);
- }
- return pb;
- }
-
private boolean initializePanelMenu(final PanelFeatureState st) {
Context context = mActivity;
@@ -996,6 +1000,11 @@
}
private boolean initializePanelContent(PanelFeatureState st) {
+ if (st.createdPanelView != null) {
+ st.shownPanelView = st.createdPanelView;
+ return true;
+ }
+
if (st.menu == null) {
return false;
}
@@ -1026,6 +1035,12 @@
closePanel(mPreparedPanel, false);
}
+ final WindowCallback cb = getWindowCallback();
+
+ if (cb != null) {
+ st.createdPanelView = cb.onCreatePanelView(st.featureId);
+ }
+
final boolean isActionBarMenu =
(st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR);
@@ -1035,68 +1050,70 @@
mDecorContentParent.setMenuPrepared();
}
- // Init the panel state's menu--return false if init failed
- if (st.menu == null || st.refreshMenuContent) {
- if (st.menu == null) {
- if (!initializePanelMenu(st) || (st.menu == null)) {
- return false;
+ if (st.createdPanelView == null) {
+ // Init the panel state's menu--return false if init failed
+ if (st.menu == null || st.refreshMenuContent) {
+ if (st.menu == null) {
+ if (!initializePanelMenu(st) || (st.menu == null)) {
+ return false;
+ }
}
- }
-
- if (isActionBarMenu && mDecorContentParent != null) {
- if (mActionMenuPresenterCallback == null) {
- mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
- }
- mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
- }
-
- // Creating the panel menu will involve a lot of manipulation;
- // don't dispatch change events to presenters until we're done.
- st.menu.stopDispatchingItemsChanged();
- if (!getWindowCallback().onCreatePanelMenu(st.featureId, st.menu)) {
- // Ditch the menu created above
- st.setMenu(null);
if (isActionBarMenu && mDecorContentParent != null) {
- // Don't show it in the action bar either
- mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+ if (mActionMenuPresenterCallback == null) {
+ mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+ }
+ mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
}
+ // Creating the panel menu will involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
+ if (!getWindowCallback().onCreatePanelMenu(st.featureId, st.menu)) {
+ // Ditch the menu created above
+ st.setMenu(null);
+
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // Don't show it in the action bar either
+ mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+ }
+
+ return false;
+ }
+
+ st.refreshMenuContent = false;
+ }
+
+ // Preparing the panel menu can involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
+
+ // Restore action view state before we prepare. This gives apps
+ // an opportunity to override frozen/restored state in onPrepare.
+ if (st.frozenActionViewState != null) {
+ st.menu.restoreActionViewStates(st.frozenActionViewState);
+ st.frozenActionViewState = null;
+ }
+
+ // Callback and return if the callback does not want to show the menu
+ if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // The app didn't want to show the menu for now but it still exists.
+ // Clear it out of the action bar.
+ mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+ }
+ st.menu.startDispatchingItemsChanged();
return false;
}
- st.refreshMenuContent = false;
- }
-
- // Preparing the panel menu can involve a lot of manipulation;
- // don't dispatch change events to presenters until we're done.
- st.menu.stopDispatchingItemsChanged();
-
- // Restore action view state before we prepare. This gives apps
- // an opportunity to override frozen/restored state in onPrepare.
- if (st.frozenActionViewState != null) {
- st.menu.restoreActionViewStates(st.frozenActionViewState);
- st.frozenActionViewState = null;
- }
-
- // Callback and return if the callback does not want to show the menu
- if (!getWindowCallback().onPreparePanel(FEATURE_OPTIONS_PANEL, null, st.menu)) {
- if (isActionBarMenu && mDecorContentParent != null) {
- // The app didn't want to show the menu for now but it still exists.
- // Clear it out of the action bar.
- mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
- }
+ // Set the proper keymap
+ KeyCharacterMap kmap = KeyCharacterMap.load(
+ event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
+ st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
+ st.menu.setQwertyMode(st.qwertyMode);
st.menu.startDispatchingItemsChanged();
- return false;
}
- // Set the proper keymap
- KeyCharacterMap kmap = KeyCharacterMap.load(
- event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
- st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
- st.menu.setQwertyMode(st.qwertyMode);
- st.menu.startDispatchingItemsChanged();
-
// Set other state
st.isPrepared = true;
st.isHandled = false;
@@ -1119,6 +1136,10 @@
mClosingActionMenu = false;
}
+ private void closePanel(int featureId) {
+ closePanel(getPanelState(featureId, true), true);
+ }
+
private void closePanel(PanelFeatureState st, boolean doCallback) {
if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
@@ -1126,7 +1147,12 @@
return;
}
- if (st.isOpen) {
+ final WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
+ if ((wm != null) && st.isOpen) {
+ if (st.decorView != null) {
+ wm.removeView(st.decorView);
+ }
+
if (doCallback) {
callOnPanelClosed(st.featureId, st, null);
}
@@ -1148,6 +1174,73 @@
}
}
+ private boolean onKeyDownPanel(int featureId, KeyEvent event) {
+ if (event.getRepeatCount() == 0) {
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (!st.isOpen) {
+ return preparePanel(st, event);
+ }
+ }
+
+ return false;
+ }
+
+ private void onKeyUpPanel(int featureId, KeyEvent event) {
+ if (mActionMode != null) {
+ return;
+ }
+
+ boolean playSoundEffect = false;
+ final PanelFeatureState st = getPanelState(featureId, true);
+ if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+ mDecorContentParent.canShowOverflowMenu() &&
+ !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity))) {
+ if (!mDecorContentParent.isOverflowMenuShowing()) {
+ if (!isDestroyed() && preparePanel(st, event)) {
+ playSoundEffect = mDecorContentParent.showOverflowMenu();
+ }
+ } else {
+ playSoundEffect = mDecorContentParent.hideOverflowMenu();
+ }
+ } else {
+ if (st.isOpen || st.isHandled) {
+
+ // Play the sound effect if the user closed an open menu (and not if
+ // they just released a menu shortcut)
+ playSoundEffect = st.isOpen;
+
+ // Close menu
+ closePanel(st, true);
+
+ } else if (st.isPrepared) {
+ boolean show = true;
+ if (st.refreshMenuContent) {
+ // Something may have invalidated the menu since we prepared it.
+ // Re-prepare it to refresh.
+ st.isPrepared = false;
+ show = preparePanel(st, event);
+ }
+
+ if (show) {
+ // Show menu
+ openPanel(st, event);
+
+ playSoundEffect = true;
+ }
+ }
+ }
+
+ if (playSoundEffect) {
+ AudioManager audioManager = (AudioManager) mActivity.getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+ } else {
+ Log.w(TAG, "Couldn't get audio manager");
+ }
+ }
+ }
+
private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
// Try to get a menu
if (menu == null) {
@@ -1337,19 +1430,10 @@
return insetTop;
}
- private void ensureToolbarListMenuPresenter() {
- if (mToolbarListMenuPresenter == null) {
- // First resolve panelMenuListTheme
- TypedValue outValue = new TypedValue();
- mActivity.getTheme().resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
-
- Context context = new ContextThemeWrapper(mActivity,
- outValue.resourceId != 0
- ? outValue.resourceId
- : R.style.Theme_AppCompat_CompactMenu);
-
- mToolbarListMenuPresenter = new ListMenuPresenter(context,
- R.layout.abc_list_menu_item_layout);
+ private void throwFeatureRequestIfSubDecorInstalled() {
+ if (mSubDecorInstalled) {
+ throw new AndroidRuntimeException(
+ "supportRequestWindowFeature() must be called before adding content");
}
}
@@ -1452,12 +1536,25 @@
/** Feature ID for this panel. */
int featureId;
+ int background;
+
+ int gravity;
+
+ int x;
+
+ int y;
+
+ int windowAnimations;
+
/** Dynamic state of the panel. */
ViewGroup decorView;
/** The panel that we are actually showing. */
View shownPanelView;
+ /** The panel that was returned by onCreatePanelView(). */
+ View createdPanelView;
+
/** Use {@link #setMenu} to set this. */
MenuBuilder menu;
@@ -1507,6 +1604,7 @@
public boolean hasPanelItems() {
if (shownPanelView == null) return false;
+ if (createdPanelView != null) return true;
return listMenuPresenter.getAdapter().getCount() > 0;
}
@@ -1544,6 +1642,13 @@
context.getTheme().setTo(widgetTheme);
listPresenterContext = context;
+
+ TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
+ background = a.getResourceId(
+ R.styleable.Theme_panelBackground, 0);
+ windowAnimations = a.getResourceId(
+ R.styleable.Theme_android_windowAnimationStyle, 0);
+ a.recycle();
}
void setMenu(MenuBuilder menu) {
@@ -1646,4 +1751,38 @@
}
}
+ private class ListMenuDecorView extends FrameLayout {
+ public ListMenuDecorView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return ActionBarActivityDelegateBase.this.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ if (isOutOfBounds(x, y)) {
+ closePanel(Window.FEATURE_OPTIONS_PANEL);
+ return true;
+ }
+ }
+ return super.onInterceptTouchEvent(event);
+ }
+
+ @Override
+ public void setBackgroundResource(int resid) {
+ setBackgroundDrawable(TintManager.getDrawable(getContext(), resid));
+ }
+
+ private boolean isOutOfBounds(int x, int y) {
+ return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5);
+ }
+ }
+
}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
index 0adda0d..97f2b22 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
@@ -47,12 +47,6 @@
}
}
- @Override
- boolean onKeyDown(int keyCode, KeyEvent event) {
- // On HC+ we do not need to do anything from onKeyDown
- return false;
- }
-
// From NativeActionModeAwareLayout.OnActionModeForChildListener
@Override
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java b/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java
index 7edab96..93b6a66 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java
@@ -28,6 +28,7 @@
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.Toolbar;
+import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.support.v7.appcompat.R;
@@ -112,6 +113,12 @@
* Returns the context of ActionBar
*/
Context getActionBarThemedContext();
+
+ /**
+ * Returns whether navigation icon is visible or not.
+ * Used to print warning messages in case developer forgets to set displayHomeAsUp to true
+ */
+ boolean isNavigationVisible();
}
private final Delegate mActivityImpl;
@@ -125,6 +132,9 @@
private final int mCloseDrawerContentDescRes;
// used in toolbar mode when DrawerToggle is disabled
private View.OnClickListener mToolbarNavigationClickListener;
+ // If developer does not set displayHomeAsUp, DrawerToggle won't show up.
+ // DrawerToggle logs a warning if this case is detected
+ private boolean mWarnedForDisplayHomeAsUp = false;
/**
* Construct a new ActionBarDrawerToggle.
@@ -449,6 +459,12 @@
}
void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
+ if (!mWarnedForDisplayHomeAsUp && !mActivityImpl.isNavigationVisible()) {
+ Log.w("ActionBarDrawerToggle", "DrawerToggle may not show up because NavigationIcon"
+ + " is not visible. You may need to call "
+ + "actionbar.setDisplayHomeAsUpEnabled(true);");
+ mWarnedForDisplayHomeAsUp = true;
+ }
mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes);
}
@@ -530,6 +546,13 @@
}
@Override
+ public boolean isNavigationVisible() {
+ final ActionBar actionBar = mActivity.getActionBar();
+ return actionBar != null
+ && (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+ }
+
+ @Override
public void setActionBarUpIndicator(Drawable themeImage, int contentDescRes) {
mActivity.getActionBar().setDisplayShowHomeEnabled(true);
mSetIndicatorInfo = ActionBarDrawerToggleHoneycomb.setActionBarUpIndicator(
@@ -577,6 +600,13 @@
}
@Override
+ public boolean isNavigationVisible() {
+ final ActionBar actionBar = mActivity.getActionBar();
+ return actionBar != null &&
+ (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+ }
+
+ @Override
public void setActionBarUpIndicator(Drawable drawable, int contentDescRes) {
final ActionBar actionBar = mActivity.getActionBar();
if (actionBar != null) {
@@ -600,35 +630,44 @@
static class ToolbarCompatDelegate implements Delegate {
final Toolbar mToolbar;
+ final Drawable mDefaultUpIndicator;
+ final CharSequence mDefaultContentDescription;
ToolbarCompatDelegate(Toolbar toolbar) {
mToolbar = toolbar;
+ mDefaultUpIndicator = toolbar.getNavigationIcon();
+ mDefaultContentDescription = toolbar.getNavigationContentDescription();
}
@Override
public void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes) {
mToolbar.setNavigationIcon(upDrawable);
- mToolbar.setNavigationContentDescription(contentDescRes);
+ setActionBarDescription(contentDescRes);
}
@Override
public void setActionBarDescription(@StringRes int contentDescRes) {
- mToolbar.setNavigationContentDescription(contentDescRes);
+ if (contentDescRes == 0) {
+ mToolbar.setNavigationContentDescription(mDefaultContentDescription);
+ } else {
+ mToolbar.setNavigationContentDescription(contentDescRes);
+ }
}
@Override
public Drawable getThemeUpIndicator() {
- final TypedArray a = mToolbar.getContext()
- .obtainStyledAttributes(new int[]{android.R.id.home});
- final Drawable result = a.getDrawable(0);
- a.recycle();
- return result;
+ return mDefaultUpIndicator;
}
@Override
public Context getActionBarThemedContext() {
return mToolbar.getContext();
}
+
+ @Override
+ public boolean isNavigationVisible() {
+ return true;
+ }
}
/**
@@ -660,5 +699,10 @@
public Context getActionBarThemedContext() {
return mActivity;
}
+
+ @Override
+ public boolean isNavigationVisible() {
+ return true;
+ }
}
}
diff --git a/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java b/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java
index f95e972..06bb360 100644
--- a/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java
+++ b/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
@@ -56,6 +57,10 @@
private boolean mVerticalMirror = false;
// The interpolated version of the original progress
private float mProgress;
+ // the amount that overlaps w/ bar size when rotation is max
+ private float mMaxCutForBarSize;
+ // The distance of arrow's center from top when horizontal
+ private float mCenterOffset;
/**
* @param context used to get the configuration for the drawable from
@@ -68,20 +73,29 @@
mPaint.setAntiAlias(true);
mPaint.setColor(typedArray.getColor(R.styleable.DrawerArrowToggle_color, 0));
mSize = typedArray.getDimensionPixelSize(R.styleable.DrawerArrowToggle_drawableSize, 0);
- mBarSize = typedArray.getDimension(R.styleable.DrawerArrowToggle_barSize, 0);
- mTopBottomArrowSize = typedArray
- .getDimension(R.styleable.DrawerArrowToggle_topBottomBarArrowSize, 0);
+ // round this because having this floating may cause bad measurements
+ mBarSize = Math.round(typedArray.getDimension(R.styleable.DrawerArrowToggle_barSize, 0));
+ // round this because having this floating may cause bad measurements
+ mTopBottomArrowSize = Math.round(typedArray.getDimension(
+ R.styleable.DrawerArrowToggle_topBottomBarArrowSize, 0));
mBarThickness = typedArray.getDimension(R.styleable.DrawerArrowToggle_thickness, 0);
- mBarGap = typedArray.getDimension(R.styleable.DrawerArrowToggle_gapBetweenBars, 0);
+ // round this because having this floating may cause bad measurements
+ mBarGap = Math.round(typedArray.getDimension(
+ R.styleable.DrawerArrowToggle_gapBetweenBars, 0));
mSpin = typedArray.getBoolean(R.styleable.DrawerArrowToggle_spinBars, true);
mMiddleArrowSize = typedArray
.getDimension(R.styleable.DrawerArrowToggle_middleBarArrowSize, 0);
+ final int remainingSpace = (int) (mSize - mBarThickness * 3 - mBarGap * 2);
+ mCenterOffset = (remainingSpace / 4) * 2; //making sure it is a multiple of 2.
+ mCenterOffset += mBarThickness * 1.5 + mBarGap;
typedArray.recycle();
mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeJoin(Paint.Join.ROUND);
- mPaint.setStrokeCap(Paint.Cap.SQUARE);
+ mPaint.setStrokeJoin(Paint.Join.MITER);
+ mPaint.setStrokeCap(Paint.Cap.BUTT);
mPaint.setStrokeWidth(mBarThickness);
+
+ mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
}
abstract boolean isLayoutRtl();
@@ -101,43 +115,44 @@
final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mProgress);
final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mProgress);
// Interpolated size of middle bar
- final float middleBarCut = lerp(0, mBarThickness / 2, mProgress);
+ final float middleBarCut = Math.round(lerp(0, mMaxCutForBarSize, mProgress));
// The rotation of the top and bottom bars (that make the arrow head)
final float rotation = lerp(0, ARROW_HEAD_ANGLE, mProgress);
// The whole canvas rotates as the transition happens
final float canvasRotate = lerp(isRtl ? 0 : -180, isRtl ? 180 : 0, mProgress);
- final float topBottomBarOffset = lerp(mBarGap + mBarThickness, 0, mProgress);
+ final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
+ final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
+
+
mPath.rewind();
+ final float topBottomBarOffset = lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
+ mProgress);
final float arrowEdge = -middleBarSize / 2;
// draw middle bar
mPath.moveTo(arrowEdge + middleBarCut, 0);
- mPath.rLineTo(middleBarSize - middleBarCut, 0);
+ mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
- final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
- final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
-
- // top bar
+ // bottom bar
mPath.moveTo(arrowEdge, topBottomBarOffset);
mPath.rLineTo(arrowWidth, arrowHeight);
- // bottom bar
+ // top bar
mPath.moveTo(arrowEdge, -topBottomBarOffset);
mPath.rLineTo(arrowWidth, -arrowHeight);
- mPath.moveTo(0, 0);
+
mPath.close();
canvas.save();
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
// the arrow pointing the other way for RTL.
+ canvas.translate(bounds.centerX(), mCenterOffset);
if (mSpin) {
- canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1),
- bounds.centerX(), bounds.centerY());
+ canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1));
} else if (isRtl) {
- canvas.rotate(180, bounds.centerX(), bounds.centerY());
+ canvas.rotate(180);
}
- canvas.translate(bounds.centerX(), bounds.centerY());
canvas.drawPath(mPath, mPaint);
canvas.restore();
diff --git a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
index 08f8552..5c63889 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
@@ -50,7 +50,6 @@
* @hide
*/
public class ToolbarActionBar extends ActionBar {
- private Toolbar mToolbar;
private DecorToolbar mDecorToolbar;
private boolean mToolbarMenuPrepared;
private WindowCallback mWindowCallback;
@@ -80,7 +79,6 @@
public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window,
WindowCallback windowCallback) {
- mToolbar = toolbar;
mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
mDecorToolbar.setWindowCallback(mWindowCallback);
@@ -107,8 +105,8 @@
@Override
public void setCustomView(int resId) {
- final LayoutInflater inflater = LayoutInflater.from(mToolbar.getContext());
- setCustomView(inflater.inflate(resId, mToolbar, false));
+ final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext());
+ setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false));
}
@Override
@@ -148,17 +146,17 @@
@Override
public void setElevation(float elevation) {
- ViewCompat.setElevation(mToolbar, elevation);
+ ViewCompat.setElevation(mDecorToolbar.getViewGroup(), elevation);
}
@Override
public float getElevation() {
- return ViewCompat.getElevation(mToolbar);
+ return ViewCompat.getElevation(mDecorToolbar.getViewGroup());
}
@Override
public Context getThemedContext() {
- return mToolbar.getContext();
+ return mDecorToolbar.getContext();
}
@Override
@@ -168,12 +166,12 @@
@Override
public void setHomeAsUpIndicator(Drawable indicator) {
- mToolbar.setNavigationIcon(indicator);
+ mDecorToolbar.setNavigationIcon(indicator);
}
@Override
public void setHomeAsUpIndicator(int resId) {
- mToolbar.setNavigationIcon(resId);
+ mDecorToolbar.setNavigationIcon(resId);
}
@Override
@@ -296,7 +294,7 @@
@Override
public void setBackgroundDrawable(@Nullable Drawable d) {
- mToolbar.setBackgroundDrawable(d);
+ mDecorToolbar.setBackgroundDrawable(d);
}
@Override
@@ -306,12 +304,12 @@
@Override
public CharSequence getTitle() {
- return mToolbar.getTitle();
+ return mDecorToolbar.getTitle();
}
@Override
public CharSequence getSubtitle() {
- return mToolbar.getSubtitle();
+ return mDecorToolbar.getSubtitle();
}
@Override
@@ -405,44 +403,44 @@
@Override
public int getHeight() {
- return mToolbar.getHeight();
+ return mDecorToolbar.getHeight();
}
@Override
public void show() {
// TODO: Consider a better transition for this.
// Right now use no automatic transition so that the app can supply one if desired.
- mToolbar.setVisibility(View.VISIBLE);
+ mDecorToolbar.setVisibility(View.VISIBLE);
}
@Override
public void hide() {
// TODO: Consider a better transition for this.
// Right now use no automatic transition so that the app can supply one if desired.
- mToolbar.setVisibility(View.GONE);
+ mDecorToolbar.setVisibility(View.GONE);
}
@Override
public boolean isShowing() {
- return mToolbar.getVisibility() == View.VISIBLE;
+ return mDecorToolbar.getVisibility() == View.VISIBLE;
}
@Override
public boolean openOptionsMenu() {
- return mToolbar.showOverflowMenu();
+ return mDecorToolbar.showOverflowMenu();
}
@Override
public boolean invalidateOptionsMenu() {
- mToolbar.removeCallbacks(mMenuInvalidator);
- ViewCompat.postOnAnimation(mToolbar, mMenuInvalidator);
+ mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
+ ViewCompat.postOnAnimation(mDecorToolbar.getViewGroup(), mMenuInvalidator);
return true;
}
@Override
public boolean collapseActionView() {
- if (mToolbar.hasExpandedActionView()) {
- mToolbar.collapseActionView();
+ if (mDecorToolbar.hasExpandedActionView()) {
+ mDecorToolbar.collapseActionView();
return true;
}
return false;
@@ -475,6 +473,12 @@
return true;
}
+ @Override
+ public boolean onKeyShortcut(int keyCode, KeyEvent ev) {
+ Menu menu = getMenu();
+ return menu != null ? menu.performShortcut(keyCode, ev, 0) : false;
+ }
+
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
mMenuVisibilityListeners.add(listener);
}
@@ -496,16 +500,45 @@
}
private View getListMenuView(Menu menu) {
+ ensureListMenuPresenter(menu);
+
if (menu == null || mListMenuPresenter == null) {
return null;
}
if (mListMenuPresenter.getAdapter().getCount() > 0) {
- return (View) mListMenuPresenter.getMenuView(mToolbar);
+ return (View) mListMenuPresenter.getMenuView(mDecorToolbar.getViewGroup());
}
return null;
}
+ private void ensureListMenuPresenter(Menu menu) {
+ if (mListMenuPresenter == null && (menu instanceof MenuBuilder)) {
+ MenuBuilder mb = (MenuBuilder) menu;
+
+ Context context = mDecorToolbar.getContext();
+ final TypedValue outValue = new TypedValue();
+ final Resources.Theme widgetTheme = context.getResources().newTheme();
+ widgetTheme.setTo(context.getTheme());
+
+ // Apply the panelMenuListTheme
+ widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
+ if (outValue.resourceId != 0) {
+ widgetTheme.applyStyle(outValue.resourceId, true);
+ } else {
+ widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true);
+ }
+
+ context = new ContextThemeWrapper(context, 0);
+ context.getTheme().setTo(widgetTheme);
+
+ // Finally create the list menu presenter
+ mListMenuPresenter = new ListMenuPresenter(context, R.layout.abc_list_menu_item_layout);
+ mListMenuPresenter.setCallback(new PanelMenuPresenterCallback());
+ mb.addMenuPresenter(mListMenuPresenter);
+ }
+ }
+
private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
public ToolbarCallbackWrapper(WindowCallback wrapped) {
super(wrapped);
@@ -525,20 +558,11 @@
public View onCreatePanelView(int featureId) {
switch (featureId) {
case Window.FEATURE_OPTIONS_PANEL:
- if (!mToolbarMenuPrepared) {
- // If the options menu isn't populated yet, do it now
- populateOptionsMenu();
- mToolbar.removeCallbacks(mMenuInvalidator);
- }
-
- if (mToolbarMenuPrepared && mWindowCallback != null) {
- // If we are prepared, check to see if the callback wants a menu opened
- final Menu menu = getMenu();
-
- if (mWindowCallback.onPreparePanel(featureId, null, menu) &&
- mWindowCallback.onMenuOpened(featureId, menu)) {
- return getListMenuView(menu);
- }
+ final Menu menu = mDecorToolbar.getMenu();
+ if (mWindowCallback != null &&
+ mWindowCallback.onPreparePanel(featureId, null, menu) &&
+ mWindowCallback.onMenuOpened(featureId, menu)) {
+ return getListMenuView(menu);
}
break;
}
@@ -548,31 +572,11 @@
private Menu getMenu() {
if (!mMenuCallbackSet) {
- mToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), new MenuBuilderCallback());
+ mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(),
+ new MenuBuilderCallback());
mMenuCallbackSet = true;
}
- return mToolbar.getMenu();
- }
-
- public void setListMenuPresenter(ListMenuPresenter listMenuPresenter) {
- final Menu menu = getMenu();
-
- if (menu instanceof MenuBuilder) {
- MenuBuilder mb = (MenuBuilder) menu;
-
- if (mListMenuPresenter != null) {
- // We currently have a list menu presenter, remove it as our menu's presenter
- mListMenuPresenter.setCallback(null);
- mb.removeMenuPresenter(mListMenuPresenter);
- }
-
- mListMenuPresenter = listMenuPresenter;
-
- if (listMenuPresenter != null) {
- listMenuPresenter.setCallback(new PanelMenuPresenterCallback());
- mb.addMenuPresenter(listMenuPresenter);
- }
- }
+ return mDecorToolbar.getMenu();
}
private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
@@ -594,7 +598,7 @@
}
mClosingActionMenu = true;
- mToolbar.dismissPopupMenus();
+ mDecorToolbar.dismissPopupMenus();
if (mWindowCallback != null) {
mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu);
}
@@ -632,7 +636,7 @@
@Override
public void onMenuModeChange(MenuBuilder menu) {
if (mWindowCallback != null) {
- if (mToolbar.isOverflowMenuShowing()) {
+ if (mDecorToolbar.isOverflowMenuShowing()) {
mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu);
} else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
null, menu)) {
diff --git a/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
index 96ed48f..6eccb1d 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
@@ -506,7 +506,7 @@
mOverlayLayout.setHideOnContentScrollEnabled(false);
mContextView.killMode();
- ActionModeImpl mode = new ActionModeImpl(callback);
+ ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback);
if (mode.dispatchOnCreate()) {
mode.invalidate();
mContextView.initForMode(mode);
@@ -944,20 +944,23 @@
* @hide
*/
public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
+ private final Context mActionModeContext;
+ private final MenuBuilder mMenu;
+
private ActionMode.Callback mCallback;
- private MenuBuilder mMenu;
private WeakReference<View> mCustomView;
- public ActionModeImpl(ActionMode.Callback callback) {
+ public ActionModeImpl(Context context, ActionMode.Callback callback) {
+ mActionModeContext = context;
mCallback = callback;
- mMenu = new MenuBuilder(getThemedContext())
+ mMenu = new MenuBuilder(context)
.setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
mMenu.setCallback(this);
}
@Override
public MenuInflater getMenuInflater() {
- return new SupportMenuInflater(getThemedContext());
+ return new SupportMenuInflater(mActionModeContext);
}
@Override
diff --git a/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
index ecd499c..029ad97 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
@@ -19,6 +19,8 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
+import android.support.v4.internal.view.SupportMenu;
+import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.util.SimpleArrayMap;
import android.support.v7.internal.view.menu.MenuWrapperFactory;
import android.view.ActionMode;
@@ -35,14 +37,13 @@
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class SupportActionModeWrapper extends ActionMode {
- final MenuInflater mInflater;
-
+ final Context mContext;
final android.support.v7.view.ActionMode mWrappedObject;
public SupportActionModeWrapper(Context context,
android.support.v7.view.ActionMode supportActionMode) {
+ mContext = context;
mWrappedObject = supportActionMode;
- mInflater = new SupportMenuInflater(context);
}
@Override
@@ -77,7 +78,7 @@
@Override
public Menu getMenu() {
- return MenuWrapperFactory.createMenuWrapper(mWrappedObject.getMenu());
+ return MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) mWrappedObject.getMenu());
}
@Override
@@ -112,7 +113,7 @@
@Override
public MenuInflater getMenuInflater() {
- return mInflater;
+ return mWrappedObject.getMenuInflater();
}
@Override
@@ -139,30 +140,32 @@
final SimpleArrayMap<android.support.v7.view.ActionMode, SupportActionModeWrapper>
mActionModes;
+ final SimpleArrayMap<Menu, Menu> mMenus;
public CallbackWrapper(Context context, Callback supportCallback) {
mContext = context;
mWrappedCallback = supportCallback;
mActionModes = new SimpleArrayMap<>();
+ mMenus = new SimpleArrayMap<>();
}
@Override
public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrappedCallback.onCreateActionMode(getActionModeWrapper(mode),
- MenuWrapperFactory.createMenuWrapper(menu));
+ getMenuWrapper(menu));
}
@Override
public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrappedCallback.onPrepareActionMode(getActionModeWrapper(mode),
- MenuWrapperFactory.createMenuWrapper(menu));
+ getMenuWrapper(menu));
}
@Override
public boolean onActionItemClicked(android.support.v7.view.ActionMode mode,
android.view.MenuItem item) {
return mWrappedCallback.onActionItemClicked(getActionModeWrapper(mode),
- MenuWrapperFactory.createMenuItemWrapper(item));
+ MenuWrapperFactory.wrapSupportMenuItem(mContext, (SupportMenuItem) item));
}
@Override
@@ -170,6 +173,15 @@
mWrappedCallback.onDestroyActionMode(getActionModeWrapper(mode));
}
+ private Menu getMenuWrapper(Menu menu) {
+ Menu wrapper = mMenus.get(menu);
+ if (wrapper == null) {
+ wrapper = MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) menu);
+ mMenus.put(menu, wrapper);
+ }
+ return wrapper;
+ }
+
private ActionMode getActionModeWrapper(android.support.v7.view.ActionMode mode) {
// First see if we already have a wrapper for this mode
SupportActionModeWrapper wrapper = mActionModes.get(mode);
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java
index cfd5b85..b8f7793 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java
@@ -16,58 +16,69 @@
package android.support.v7.internal.view.menu;
+import android.content.Context;
import android.support.v4.internal.view.SupportMenuItem;
+import android.support.v4.internal.view.SupportSubMenu;
+import android.support.v4.util.ArrayMap;
import android.view.MenuItem;
import android.view.SubMenu;
-import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
abstract class BaseMenuWrapper<T> extends BaseWrapper<T> {
- private HashMap<MenuItem, SupportMenuItem> mMenuItems;
+ final Context mContext;
- private HashMap<SubMenu, SubMenu> mSubMenus;
+ private Map<SupportMenuItem, MenuItem> mMenuItems;
+ private Map<SupportSubMenu, SubMenu> mSubMenus;
- BaseMenuWrapper(T object) {
+ BaseMenuWrapper(Context context, T object) {
super(object);
+ mContext = context;
}
- final SupportMenuItem getMenuItemWrapper(android.view.MenuItem frameworkItem) {
- if (frameworkItem != null) {
- // Instantiate HashMap if null
+ final MenuItem getMenuItemWrapper(final MenuItem menuItem) {
+ if (menuItem instanceof SupportMenuItem) {
+ final SupportMenuItem supportMenuItem = (SupportMenuItem) menuItem;
+
+ // Instantiate Map if null
if (mMenuItems == null) {
- mMenuItems = new HashMap<MenuItem, SupportMenuItem>();
+ mMenuItems = new ArrayMap<>();
}
- SupportMenuItem compatItem = mMenuItems.get(frameworkItem);
+ // First check if we already have a wrapper for this item
+ MenuItem wrappedItem = mMenuItems.get(menuItem);
- if (null == compatItem) {
- compatItem = MenuWrapperFactory.createSupportMenuItemWrapper(frameworkItem);
- mMenuItems.put(frameworkItem, compatItem);
+ if (null == wrappedItem) {
+ // ... if not, create one and add it to our map
+ wrappedItem = MenuWrapperFactory.wrapSupportMenuItem(mContext, supportMenuItem);
+ mMenuItems.put(supportMenuItem, wrappedItem);
}
- return compatItem;
+ return wrappedItem;
}
- return null;
+ return menuItem;
}
- final SubMenu getSubMenuWrapper(android.view.SubMenu frameworkSubMenu) {
- if (frameworkSubMenu != null) {
- // Instantiate HashMap if null
+ final SubMenu getSubMenuWrapper(final SubMenu subMenu) {
+ if (subMenu instanceof SupportSubMenu) {
+ final SupportSubMenu supportSubMenu = (SupportSubMenu) subMenu;
+
+ // Instantiate Map if null
if (mSubMenus == null) {
- mSubMenus = new HashMap<android.view.SubMenu, SubMenu>();
+ mSubMenus = new ArrayMap<>();
}
- SubMenu compatSubMenu = mSubMenus.get(frameworkSubMenu);
+ SubMenu wrappedMenu = mSubMenus.get(supportSubMenu);
- if (null == compatSubMenu) {
- compatSubMenu = MenuWrapperFactory.createSupportSubMenuWrapper(frameworkSubMenu);
- mSubMenus.put(frameworkSubMenu, compatSubMenu);
+ if (null == wrappedMenu) {
+ wrappedMenu = MenuWrapperFactory.wrapSupportSubMenu(mContext, supportSubMenu);
+ mSubMenus.put(supportSubMenu, wrappedMenu);
}
- return compatSubMenu;
+ return wrappedMenu;
}
- return null;
+ return subMenu;
}
@@ -85,7 +96,7 @@
return;
}
- Iterator<android.view.MenuItem> iterator = mMenuItems.keySet().iterator();
+ Iterator<SupportMenuItem> iterator = mMenuItems.keySet().iterator();
android.view.MenuItem menuItem;
while (iterator.hasNext()) {
@@ -101,7 +112,7 @@
return;
}
- Iterator<android.view.MenuItem> iterator = mMenuItems.keySet().iterator();
+ Iterator<SupportMenuItem> iterator = mMenuItems.keySet().iterator();
android.view.MenuItem menuItem;
while (iterator.hasNext()) {
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
index 09029d7..a2e9783 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ActionProvider;
import android.support.v4.internal.view.SupportMenuItem;
@@ -384,7 +385,17 @@
@Override
public CharSequence getTitleCondensed() {
- return mTitleCondensed != null ? mTitleCondensed : mTitle;
+ final CharSequence ctitle = mTitleCondensed != null ? mTitleCondensed : mTitle;
+
+ if (Build.VERSION.SDK_INT < 18 && ctitle != null && !(ctitle instanceof String)) {
+ // For devices pre-JB-MR2, where we have a non-String CharSequence, we need to
+ // convert this to a String so that EventLog.writeEvent() does not throw an exception
+ // in Activity.onMenuItemSelected()
+ return ctitle.toString();
+ } else {
+ // Else, we just return the condensed title
+ return ctitle;
+ }
}
@Override
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java
index a84e34c..3e6a99a 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java
@@ -16,8 +16,11 @@
package android.support.v7.internal.view.menu;
+import android.annotation.TargetApi;
+import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.MenuItemCompat;
@@ -32,26 +35,18 @@
import java.lang.reflect.Method;
/**
+ * Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem}
* @hide
*/
-public class MenuItemWrapperICS extends BaseMenuWrapper<android.view.MenuItem> implements SupportMenuItem {
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class MenuItemWrapperICS extends BaseMenuWrapper<SupportMenuItem> implements MenuItem {
static final String LOG_TAG = "MenuItemWrapper";
- private final boolean mEmulateProviderVisibilityOverride;
- // Tracks the last requested visibility
- private boolean mLastRequestVisible;
-
// Reflection Method to call setExclusiveCheckable
private Method mSetExclusiveCheckableMethod;
- MenuItemWrapperICS(android.view.MenuItem object, boolean emulateProviderVisibilityOverride) {
- super(object);
- mLastRequestVisible = object.isVisible();
- mEmulateProviderVisibilityOverride = emulateProviderVisibilityOverride;
- }
-
- MenuItemWrapperICS(android.view.MenuItem object) {
- this(object, true);
+ MenuItemWrapperICS(Context context, SupportMenuItem object) {
+ super(context, object);
}
@Override
@@ -177,14 +172,7 @@
@Override
public MenuItem setVisible(boolean visible) {
- if (mEmulateProviderVisibilityOverride) {
- mLastRequestVisible = visible;
- // If we need to be visible, we need to check whether the ActionProvider overrides it
- if (checkActionProviderOverrideVisibility()) {
- return this;
- }
- }
- return wrappedSetVisible(visible);
+ return mWrappedObject.setVisible(visible);
}
@Override
@@ -238,7 +226,7 @@
@Override
public MenuItem setActionView(View view) {
- if (view instanceof CollapsibleActionView) {
+ if (view instanceof android.view.CollapsibleActionView) {
view = new CollapsibleActionViewWrapper(view);
}
mWrappedObject.setActionView(view);
@@ -251,7 +239,7 @@
mWrappedObject.setActionView(resId);
View actionView = mWrappedObject.getActionView();
- if (actionView instanceof CollapsibleActionView) {
+ if (actionView instanceof android.view.CollapsibleActionView) {
// If the inflated Action View is support-collapsible, wrap it
mWrappedObject.setActionView(new CollapsibleActionViewWrapper(actionView));
}
@@ -269,16 +257,18 @@
@Override
public MenuItem setActionProvider(android.view.ActionProvider provider) {
- mWrappedObject.setActionProvider(provider);
- if (provider != null && mEmulateProviderVisibilityOverride) {
- checkActionProviderOverrideVisibility();
- }
+ mWrappedObject.setSupportActionProvider(
+ provider != null ? createActionProviderWrapper(provider) : null);
return this;
}
@Override
public android.view.ActionProvider getActionProvider() {
- return mWrappedObject.getActionProvider();
+ ActionProvider provider = mWrappedObject.getSupportActionProvider();
+ if (provider instanceof ActionProviderWrapper) {
+ return ((ActionProviderWrapper) provider).mInner;
+ }
+ return null;
}
@Override
@@ -298,32 +288,11 @@
@Override
public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
- mWrappedObject.setOnActionExpandListener(listener);
- return this;
- }
-
- @Override
- public SupportMenuItem setSupportOnActionExpandListener(
- MenuItemCompat.OnActionExpandListener listener) {
- mWrappedObject.setOnActionExpandListener(listener != null ?
+ mWrappedObject.setSupportOnActionExpandListener(listener != null ?
new OnActionExpandListenerWrapper(listener) : null);
- return null;
- }
-
- @Override
- public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) {
- mWrappedObject.setActionProvider(actionProvider != null ?
- createActionProviderWrapper(actionProvider) : null);
return this;
}
- @Override
- public ActionProvider getSupportActionProvider() {
- ActionProviderWrapper providerWrapper =
- (ActionProviderWrapper) mWrappedObject.getActionProvider();
- return providerWrapper != null ? providerWrapper.mInner : null;
- }
-
public void setExclusiveCheckable(boolean checkable) {
try {
if (mSetExclusiveCheckableMethod == null) {
@@ -336,26 +305,8 @@
}
}
- ActionProviderWrapper createActionProviderWrapper(ActionProvider provider) {
- return new ActionProviderWrapper(provider);
- }
-
- /**
- * @return true if the ActionProvider has overriden the visibility
- */
- final boolean checkActionProviderOverrideVisibility() {
- if (mLastRequestVisible) {
- ActionProvider provider = getSupportActionProvider();
- if (provider != null && provider.overridesItemVisibility() && !provider.isVisible()) {
- wrappedSetVisible(false);
- return true;
- }
- }
- return false;
- }
-
- final MenuItem wrappedSetVisible(boolean visible) {
- return mWrappedObject.setVisible(visible);
+ ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) {
+ return new ActionProviderWrapper(mContext, provider);
}
private class OnMenuItemClickListenerWrapper extends BaseWrapper<OnMenuItemClickListener>
@@ -371,10 +322,10 @@
}
}
- private class OnActionExpandListenerWrapper extends BaseWrapper<MenuItemCompat.OnActionExpandListener>
- implements android.view.MenuItem.OnActionExpandListener {
+ private class OnActionExpandListenerWrapper extends BaseWrapper<MenuItem.OnActionExpandListener>
+ implements MenuItemCompat.OnActionExpandListener {
- OnActionExpandListenerWrapper(MenuItemCompat.OnActionExpandListener object) {
+ OnActionExpandListenerWrapper(MenuItem.OnActionExpandListener object) {
super(object);
}
@@ -389,32 +340,16 @@
}
}
- class ActionProviderWrapper extends android.view.ActionProvider {
- final ActionProvider mInner;
+ class ActionProviderWrapper extends android.support.v4.view.ActionProvider {
+ final android.view.ActionProvider mInner;
- public ActionProviderWrapper(ActionProvider inner) {
- super(inner.getContext());
+ public ActionProviderWrapper(Context context, android.view.ActionProvider inner) {
+ super(context);
mInner = inner;
-
- if (mEmulateProviderVisibilityOverride) {
- mInner.setVisibilityListener(new ActionProvider.VisibilityListener() {
- @Override
- public void onActionProviderVisibilityChanged(boolean isVisible) {
- if (mInner.overridesItemVisibility() && mLastRequestVisible) {
- wrappedSetVisible(isVisible);
- }
- }
- });
- }
}
@Override
public View onCreateActionView() {
- if (mEmulateProviderVisibilityOverride) {
- // This is a convenient place to hook in and check if we need to override the
- // visibility after being created.
- checkActionProviderOverrideVisibility();
- }
return mInner.onCreateActionView();
}
@@ -434,13 +369,18 @@
}
}
+ /**
+ * Wrap a support {@link android.support.v7.view.CollapsibleActionView} into a framework
+ * {@link android.view.CollapsibleActionView}.
+ */
static class CollapsibleActionViewWrapper extends FrameLayout
- implements android.view.CollapsibleActionView {
- final CollapsibleActionView mWrappedView;
+ implements CollapsibleActionView {
+
+ final android.view.CollapsibleActionView mWrappedView;
CollapsibleActionViewWrapper(View actionView) {
super(actionView.getContext());
- mWrappedView = (CollapsibleActionView) actionView;
+ mWrappedView = (android.view.CollapsibleActionView) actionView;
addView(actionView);
}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java
index 48fea03..4dbb0e0 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java
@@ -16,27 +16,36 @@
package android.support.v7.internal.view.menu;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.view.ActionProvider;
import android.view.MenuItem;
import android.view.View;
+/**
+ * Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem}
+ * @hide
+ */
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
class MenuItemWrapperJB extends MenuItemWrapperICS {
- MenuItemWrapperJB(android.view.MenuItem object) {
- // We do not want to use the emulation of Action Provider visibility override
- super(object, false);
+
+ MenuItemWrapperJB(Context context, SupportMenuItem object) {
+ super(context, object);
}
@Override
- ActionProviderWrapper createActionProviderWrapper(ActionProvider provider) {
- return new ActionProviderWrapperJB(provider);
+ ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) {
+ return new ActionProviderWrapperJB(mContext, provider);
}
class ActionProviderWrapperJB extends ActionProviderWrapper
- implements ActionProvider.VisibilityListener {
- android.view.ActionProvider.VisibilityListener mListener;
+ implements android.view.ActionProvider.VisibilityListener {
+ ActionProvider.VisibilityListener mListener;
- public ActionProviderWrapperJB(ActionProvider inner) {
- super(inner);
+ public ActionProviderWrapperJB(Context context, android.view.ActionProvider inner) {
+ super(context, inner);
}
@Override
@@ -60,8 +69,7 @@
}
@Override
- public void setVisibilityListener(
- android.view.ActionProvider.VisibilityListener listener) {
+ public void setVisibilityListener(ActionProvider.VisibilityListener listener) {
mListener = listener;
mInner.setVisibilityListener(listener != null ? this : null);
}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
index 10c096b..af7deef 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
@@ -57,6 +57,7 @@
private final boolean mOverflowOnly;
private final int mPopupMaxWidth;
private final int mPopupStyleAttr;
+ private final int mPopupStyleRes;
private View mAnchorView;
private ListPopupWindow mPopup;
@@ -85,12 +86,18 @@
public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly, int popupStyleAttr) {
+ this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
+ }
+
+ public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
+ boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
mContext = context;
mInflater = LayoutInflater.from(context);
mMenu = menu;
mAdapter = new MenuAdapter(mMenu);
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
+ mPopupStyleRes = popupStyleRes;
final Resources res = context.getResources();
mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
@@ -125,7 +132,7 @@
}
public boolean tryShow() {
- mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr);
+ mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
mPopup.setOnDismissListener(this);
mPopup.setOnItemClickListener(this);
mPopup.setAdapter(mAdapter);
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java
index 10e583e..7435874 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java
@@ -16,12 +16,14 @@
package android.support.v7.internal.view.menu;
+import android.content.Context;
import android.os.Build;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.internal.view.SupportSubMenu;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.SubMenu;
/**
* @hide
@@ -30,43 +32,25 @@
private MenuWrapperFactory() {
}
- public static Menu createMenuWrapper(android.view.Menu frameworkMenu) {
+ public static Menu wrapSupportMenu(Context context, SupportMenu supportMenu) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return new MenuWrapperICS(frameworkMenu);
- }
- return frameworkMenu;
- }
-
- public static MenuItem createMenuItemWrapper(android.view.MenuItem frameworkMenuItem) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- return new MenuItemWrapperJB(frameworkMenuItem);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return new MenuItemWrapperICS(frameworkMenuItem);
- }
- return frameworkMenuItem;
- }
-
- public static SupportMenu createSupportMenuWrapper(android.view.Menu frameworkMenu) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return new MenuWrapperICS(frameworkMenu);
+ return new MenuWrapperICS(context, supportMenu);
}
throw new UnsupportedOperationException();
}
- public static SupportSubMenu createSupportSubMenuWrapper(
- android.view.SubMenu frameworkSubMenu) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return new SubMenuWrapperICS(frameworkSubMenu);
+ public static MenuItem wrapSupportMenuItem(Context context, SupportMenuItem supportMenuItem) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ return new MenuItemWrapperJB(context, supportMenuItem);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return new MenuItemWrapperICS(context, supportMenuItem);
}
throw new UnsupportedOperationException();
}
- public static SupportMenuItem createSupportMenuItemWrapper(
- android.view.MenuItem frameworkMenuItem) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- return new MenuItemWrapperJB(frameworkMenuItem);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return new MenuItemWrapperICS(frameworkMenuItem);
+ public static SubMenu wrapSupportSubMenu(Context context, SupportSubMenu supportSubMenu) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return new SubMenuWrapperICS(context, supportSubMenu);
}
throw new UnsupportedOperationException();
}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java
index 8d0e30f..79833f5 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java
@@ -17,17 +17,23 @@
package android.support.v7.internal.view.menu;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.internal.view.SupportMenuItem;
import android.view.KeyEvent;
+import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
-class MenuWrapperICS extends BaseMenuWrapper<android.view.Menu> implements SupportMenu {
+/**
+ * Wraps a support {@link SupportMenu} as a framework {@link android.view.Menu}
+ * @hide
+ */
+class MenuWrapperICS extends BaseMenuWrapper<SupportMenu> implements Menu {
- MenuWrapperICS(android.view.Menu object) {
- super(object);
+ MenuWrapperICS(Context context, SupportMenu object) {
+ super(context, object);
}
@Override
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java b/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java
index 3e70e49..a4306e9 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java
@@ -16,71 +16,77 @@
package android.support.v7.internal.view.menu;
+import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.internal.view.SupportSubMenu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
-class SubMenuWrapperICS extends MenuWrapperICS implements SupportSubMenu {
- SubMenuWrapperICS(android.view.SubMenu subMenu) {
- super(subMenu);
+/**
+ * Wraps a support {@link SupportSubMenu} as a framework {@link android.view.SubMenu}
+ * @hide
+ */
+class SubMenuWrapperICS extends MenuWrapperICS implements SubMenu {
+
+ SubMenuWrapperICS(Context context, SupportSubMenu subMenu) {
+ super(context, subMenu);
}
@Override
- public android.view.SubMenu getWrappedObject() {
- return (android.view.SubMenu) mWrappedObject;
+ public SupportSubMenu getWrappedObject() {
+ return (SupportSubMenu) mWrappedObject;
}
@Override
public SubMenu setHeaderTitle(int titleRes) {
- ((android.view.SubMenu) mWrappedObject).setHeaderTitle(titleRes);
+ getWrappedObject().setHeaderTitle(titleRes);
return this;
}
@Override
public SubMenu setHeaderTitle(CharSequence title) {
- ((android.view.SubMenu) mWrappedObject).setHeaderTitle(title);
+ getWrappedObject().setHeaderTitle(title);
return this;
}
@Override
public SubMenu setHeaderIcon(int iconRes) {
- ((android.view.SubMenu) mWrappedObject).setHeaderIcon(iconRes);
+ getWrappedObject().setHeaderIcon(iconRes);
return this;
}
@Override
public SubMenu setHeaderIcon(Drawable icon) {
- ((android.view.SubMenu) mWrappedObject).setHeaderIcon(icon);
+ getWrappedObject().setHeaderIcon(icon);
return this;
}
@Override
public SubMenu setHeaderView(View view) {
- ((android.view.SubMenu) mWrappedObject).setHeaderView(view);
+ getWrappedObject().setHeaderView(view);
return this;
}
@Override
public void clearHeader() {
- ((android.view.SubMenu) mWrappedObject).clearHeader();
+ getWrappedObject().clearHeader();
}
@Override
public SubMenu setIcon(int iconRes) {
- ((android.view.SubMenu) mWrappedObject).setIcon(iconRes);
+ getWrappedObject().setIcon(iconRes);
return this;
}
@Override
public SubMenu setIcon(Drawable icon) {
- ((android.view.SubMenu) mWrappedObject).setIcon(icon);
+ getWrappedObject().setIcon(icon);
return this;
}
@Override
public MenuItem getItem() {
- return getMenuItemWrapper(((android.view.SubMenu) mWrappedObject).getItem());
+ return getMenuItemWrapper(getWrappedObject().getItem());
}
}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
index acc30ee..9931afb 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
@@ -25,6 +25,7 @@
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ActionProvider;
+import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.widget.LinearLayoutCompat;
import android.support.v7.widget.ListPopupWindow;
@@ -38,7 +39,6 @@
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
@@ -68,6 +68,8 @@
public class ActivityChooserView extends ViewGroup implements
ActivityChooserModel.ActivityChooserModelClient {
+ private static final String LOG_TAG = "ActivityChooserView";
+
/**
* An adapter for displaying the activities in an {@link android.widget.AdapterView}.
*/
@@ -181,13 +183,13 @@
*/
private int mDefaultActionButtonContentDescription;
- /**
- * Create a new instance.
- *
- * @param context The application environment.
- */
- public ActivityChooserView(Context context) {
- this(context, null);
+ /**
+ * Create a new instance.
+ *
+ * @param context The application environment.
+ */
+ public ActivityChooserView(Context context) {
+ this(context, null);
}
/**
@@ -235,10 +237,29 @@
mDefaultActivityButton.setOnLongClickListener(mCallbacks);
mDefaultActivityButtonImage = (ImageView) mDefaultActivityButton.findViewById(R.id.image);
- mExpandActivityOverflowButton = (FrameLayout) findViewById(R.id.expand_activities_button);
- mExpandActivityOverflowButton.setOnClickListener(mCallbacks);
+ final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
+ expandButton.setOnClickListener(mCallbacks);
+ expandButton.setOnTouchListener(new ListPopupWindow.ForwardingListener(expandButton) {
+ @Override
+ public ListPopupWindow getPopup() {
+ return getListPopupWindow();
+ }
+
+ @Override
+ protected boolean onForwardingStarted() {
+ showPopup();
+ return true;
+ }
+
+ @Override
+ protected boolean onForwardingStopped() {
+ dismissPopup();
+ return true;
+ }
+ });
+ mExpandActivityOverflowButton = expandButton;
mExpandActivityOverflowButtonImage =
- (ImageView) mExpandActivityOverflowButton.findViewById(R.id.image);
+ (ImageView) expandButton.findViewById(R.id.image);
mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
mAdapter = new ActivityChooserViewAdapter();
@@ -723,9 +744,9 @@
titleView.setText(activity.loadLabel(packageManager));
// Highlight the default.
if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
- //TODO convertView.setActivated(true);
+ ViewCompat.setActivated(convertView, true);
} else {
- //TODO convertView.setActivated(false);
+ ViewCompat.setActivated(convertView, false);
}
return convertView;
default:
@@ -783,10 +804,6 @@
return mDataModel.getHistorySize();
}
- public int getMaxActivityCount() {
- return mMaxActivityCount;
- }
-
public ActivityChooserModel getDataModel() {
return mDataModel;
}
@@ -805,4 +822,22 @@
return mShowDefaultActivity;
}
}
+
+ /**
+ * Allows us to set the background using TintTypedArray
+ * @hide
+ */
+ public static class InnerLayout extends LinearLayoutCompat {
+
+ private static final int[] TINT_ATTRS = {
+ android.R.attr.background
+ };
+
+ public InnerLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS);
+ setBackgroundDrawable(a.getDrawable(0));
+ a.recycle();
+ }
+ }
}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
index 67422d5..dfeb34c 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
@@ -21,6 +21,7 @@
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.support.v7.internal.app.WindowCallback;
+import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.util.SparseArray;
import android.view.Menu;
@@ -92,4 +93,12 @@
void setDefaultNavigationIcon(Drawable icon);
void saveHierarchyState(SparseArray<Parcelable> toolbarStates);
void restoreHierarchyState(SparseArray<Parcelable> toolbarStates);
+ void setBackgroundDrawable(Drawable d);
+ int getHeight();
+ void setVisibility(int visible);
+ int getVisibility();
+ void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
+ MenuBuilder.Callback menuBuilderCallback);
+ Menu getMenu();
+ int getPopupTheme();
}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
index 6a1711e..ad15064 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
@@ -35,11 +35,10 @@
*/
class DrawableWrapper extends Drawable implements Drawable.Callback {
- private final Drawable mDrawable;
+ private Drawable mDrawable;
public DrawableWrapper(Drawable drawable) {
- mDrawable = drawable;
- mDrawable.setCallback(this);
+ setWrappedDrawable(drawable);
}
@Override
@@ -207,4 +206,20 @@
public void setHotspotBounds(int left, int top, int right, int bottom) {
DrawableCompat.setHotspotBounds(mDrawable, left, top, right, bottom);
}
+
+ public Drawable getWrappedDrawable() {
+ return mDrawable;
+ }
+
+ public void setWrappedDrawable(Drawable drawable) {
+ if (mDrawable != null) {
+ mDrawable.setCallback(null);
+ }
+
+ mDrawable = drawable;
+
+ if (drawable != null) {
+ drawable.setCallback(this);
+ }
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java
deleted file mode 100644
index 3d83367..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java
+++ /dev/null
@@ -1,923 +0,0 @@
-package android.support.v7.internal.widget;
-
-/*
- * Copyright (C) 2013 Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimationDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ClipDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.graphics.drawable.shapes.Shape;
-import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.Transformation;
-
-/**
- * @hide
- */
-public class ProgressBarCompat extends View {
-
- private static final int MAX_LEVEL = 10000;
- private static final int ANIMATION_RESOLUTION = 200;
-
- /**
- * android.R.styleable.ProgressBar is internalised, so we need to create it ourselves.
- */
- private static final int[] android_R_styleable_ProgressBar = new int[]{
- android.R.attr.max,
- android.R.attr.progress,
- android.R.attr.secondaryProgress,
- android.R.attr.indeterminate,
- android.R.attr.indeterminateOnly,
- android.R.attr.indeterminateDrawable,
- android.R.attr.progressDrawable,
- android.R.attr.indeterminateDuration,
- android.R.attr.indeterminateBehavior,
- android.R.attr.minWidth,
- android.R.attr.maxWidth,
- android.R.attr.minHeight,
- android.R.attr.maxHeight,
- android.R.attr.interpolator,
- };
-
- int mMinWidth;
- int mMaxWidth;
- int mMinHeight;
- int mMaxHeight;
-
- private int mProgress;
- private int mSecondaryProgress;
- private int mMax;
-
- private int mBehavior;
- private int mDuration;
- private boolean mIndeterminate;
- private boolean mOnlyIndeterminate;
- private Transformation mTransformation;
- private AlphaAnimation mAnimation;
- private Drawable mIndeterminateDrawable;
- private Drawable mProgressDrawable;
- private Drawable mCurrentDrawable;
- Bitmap mSampleTile;
- private boolean mNoInvalidate;
- private Interpolator mInterpolator;
- private RefreshProgressRunnable mRefreshProgressRunnable;
- private long mUiThreadId;
- private boolean mShouldStartAnimationDrawable;
- private long mLastDrawTime;
-
- private boolean mInDrawing;
-
- /**
- * @hide
- */
- public ProgressBarCompat(Context context, AttributeSet attrs, int defStyle, int styleRes) {
- super(context, attrs, defStyle);
- mUiThreadId = Thread.currentThread().getId();
- initProgressBar();
-
- TypedArray a = context.obtainStyledAttributes(attrs, android_R_styleable_ProgressBar,
- defStyle, styleRes);
-
- mNoInvalidate = true;
-
- setMax(a.getInt(0, mMax));
- setProgress(a.getInt(1, mProgress));
- setSecondaryProgress(a.getInt(2, mSecondaryProgress));
-
- final boolean indeterminate = a.getBoolean(3, mIndeterminate);
- mOnlyIndeterminate = a.getBoolean(4, mOnlyIndeterminate);
-
- Drawable drawable = a.getDrawable(5);
- if (drawable != null) {
- drawable = tileifyIndeterminate(drawable);
- setIndeterminateDrawable(drawable);
- }
-
- drawable = a.getDrawable(6);
- if (drawable != null) {
- drawable = tileify(drawable, false);
- // Calling this method can set mMaxHeight, make sure the corresponding
- // XML attribute for mMaxHeight is read after calling this method
- setProgressDrawable(drawable);
- }
-
- mDuration = a.getInt(7, mDuration);
- mBehavior = a.getInt(8, mBehavior);
- mMinWidth = a.getDimensionPixelSize(9, mMinWidth);
- mMaxWidth = a.getDimensionPixelSize(10, mMaxWidth);
- mMinHeight = a.getDimensionPixelSize(11, mMinHeight);
- mMaxHeight = a.getDimensionPixelSize(12, mMaxHeight);
-
- final int resID = a.getResourceId(13, android.R.anim.linear_interpolator);
- if (resID > 0) {
- setInterpolator(context, resID);
- }
-
- a.recycle();
-
- mNoInvalidate = false;
- setIndeterminate(mOnlyIndeterminate || indeterminate);
- }
-
- /**
- * Converts a drawable to a tiled version of itself. It will recursively
- * traverse layer and state list drawables.
- */
- private Drawable tileify(Drawable drawable, boolean clip) {
-
- if (drawable instanceof LayerDrawable) {
- LayerDrawable background = (LayerDrawable) drawable;
- final int N = background.getNumberOfLayers();
- Drawable[] outDrawables = new Drawable[N];
-
- for (int i = 0; i < N; i++) {
- int id = background.getId(i);
- outDrawables[i] = tileify(background.getDrawable(i),
- (id == android.R.id.progress || id == android.R.id.secondaryProgress));
- }
-
- LayerDrawable newBg = new LayerDrawable(outDrawables);
-
- for (int i = 0; i < N; i++) {
- newBg.setId(i, background.getId(i));
- }
-
- return newBg;
-
- } else if (drawable instanceof BitmapDrawable) {
- final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
- if (mSampleTile == null) {
- mSampleTile = tileBitmap;
- }
-
- final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
-
- final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
- Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
- shapeDrawable.getPaint().setShader(bitmapShader);
-
- return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
- ClipDrawable.HORIZONTAL) : shapeDrawable;
- }
-
- return drawable;
- }
-
- Shape getDrawableShape() {
- final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
- return new RoundRectShape(roundedCorners, null, null);
- }
-
- /**
- * Convert a AnimationDrawable for use as a barberpole animation.
- * Each frame of the animation is wrapped in a ClipDrawable and
- * given a tiling BitmapShader.
- */
- private Drawable tileifyIndeterminate(Drawable drawable) {
- if (drawable instanceof AnimationDrawable) {
- AnimationDrawable background = (AnimationDrawable) drawable;
- final int N = background.getNumberOfFrames();
- AnimationDrawable newBg = new AnimationDrawable();
- newBg.setOneShot(background.isOneShot());
-
- for (int i = 0; i < N; i++) {
- Drawable frame = tileify(background.getFrame(i), true);
- frame.setLevel(10000);
- newBg.addFrame(frame, background.getDuration(i));
- }
- newBg.setLevel(10000);
- drawable = newBg;
- }
- return drawable;
- }
-
- /**
- * <p>
- * Initialize the progress bar's default values:
- * </p>
- * <ul>
- * <li>progress = 0</li>
- * <li>max = 100</li>
- * <li>animation duration = 4000 ms</li>
- * <li>indeterminate = false</li>
- * <li>behavior = repeat</li>
- * </ul>
- */
- private void initProgressBar() {
- mMax = 100;
- mProgress = 0;
- mSecondaryProgress = 0;
- mIndeterminate = false;
- mOnlyIndeterminate = false;
- mDuration = 4000;
- mBehavior = AlphaAnimation.RESTART;
- mMinWidth = 24;
- mMaxWidth = 48;
- mMinHeight = 24;
- mMaxHeight = 48;
- }
-
- /**
- * <p>Indicate whether this progress bar is in indeterminate mode.</p>
- *
- * @return true if the progress bar is in indeterminate mode
- */
- public synchronized boolean isIndeterminate() {
- return mIndeterminate;
- }
-
- /**
- * <p>Change the indeterminate mode for this progress bar. In indeterminate
- * mode, the progress is ignored and the progress bar shows an infinite
- * animation instead.</p>
- *
- * If this progress bar's style only supports indeterminate mode (such as the circular
- * progress bars), then this will be ignored.
- *
- * @param indeterminate true to enable the indeterminate mode
- */
- public synchronized void setIndeterminate(boolean indeterminate) {
- if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
- mIndeterminate = indeterminate;
-
- if (indeterminate) {
- // swap between indeterminate and regular backgrounds
- mCurrentDrawable = mIndeterminateDrawable;
- startAnimation();
- } else {
- mCurrentDrawable = mProgressDrawable;
- stopAnimation();
- }
- }
- }
-
- /**
- * <p>Get the drawable used to draw the progress bar in
- * indeterminate mode.</p>
- *
- * @return a {@link android.graphics.drawable.Drawable} instance
- *
- * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
- * @see #setIndeterminate(boolean)
- */
- public Drawable getIndeterminateDrawable() {
- return mIndeterminateDrawable;
- }
-
- /**
- * <p>Define the drawable used to draw the progress bar in
- * indeterminate mode.</p>
- *
- * @param d the new drawable
- *
- * @see #getIndeterminateDrawable()
- * @see #setIndeterminate(boolean)
- */
- public void setIndeterminateDrawable(Drawable d) {
- if (d != null) {
- d.setCallback(this);
- }
- mIndeterminateDrawable = d;
- if (mIndeterminate) {
- mCurrentDrawable = d;
- postInvalidate();
- }
- }
-
- /**
- * <p>Get the drawable used to draw the progress bar in
- * progress mode.</p>
- *
- * @return a {@link android.graphics.drawable.Drawable} instance
- *
- * @see #setProgressDrawable(android.graphics.drawable.Drawable)
- * @see #setIndeterminate(boolean)
- */
- public Drawable getProgressDrawable() {
- return mProgressDrawable;
- }
-
- /**
- * <p>Define the drawable used to draw the progress bar in
- * progress mode.</p>
- *
- * @param d the new drawable
- *
- * @see #getProgressDrawable()
- * @see #setIndeterminate(boolean)
- */
- public void setProgressDrawable(Drawable d) {
- boolean needUpdate;
- if (mProgressDrawable != null && d != mProgressDrawable) {
- mProgressDrawable.setCallback(null);
- needUpdate = true;
- } else {
- needUpdate = false;
- }
-
- if (d != null) {
- d.setCallback(this);
-
- // Make sure the android_R_styleable_ProgressBar is always tall enough
- int drawableHeight = d.getMinimumHeight();
- if (mMaxHeight < drawableHeight) {
- mMaxHeight = drawableHeight;
- requestLayout();
- }
- }
- mProgressDrawable = d;
- if (!mIndeterminate) {
- mCurrentDrawable = d;
- postInvalidate();
- }
-
- if (needUpdate) {
- updateDrawableBounds(getWidth(), getHeight());
- updateDrawableState();
- doRefreshProgress(android.R.id.progress, mProgress, false, false);
- doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
- }
- }
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- return who == mProgressDrawable || who == mIndeterminateDrawable
- || super.verifyDrawable(who);
- }
-
- @Override
- public void postInvalidate() {
- if (!mNoInvalidate) {
- super.postInvalidate();
- }
- }
-
- private class RefreshProgressRunnable implements Runnable {
-
- private int mId;
- private int mProgress;
- private boolean mFromUser;
-
- RefreshProgressRunnable(int id, int progress, boolean fromUser) {
- mId = id;
- mProgress = progress;
- mFromUser = fromUser;
- }
-
- public void run() {
- doRefreshProgress(mId, mProgress, mFromUser, true);
- // Put ourselves back in the cache when we are done
- mRefreshProgressRunnable = this;
- }
-
- public void setup(int id, int progress, boolean fromUser) {
- mId = id;
- mProgress = progress;
- mFromUser = fromUser;
- }
-
- }
-
- private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
- boolean callBackToApp) {
- float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
- final Drawable d = mCurrentDrawable;
- if (d != null) {
- Drawable progressDrawable = null;
-
- if (d instanceof LayerDrawable) {
- progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
- }
-
- final int level = (int) (scale * MAX_LEVEL);
- (progressDrawable != null ? progressDrawable : d).setLevel(level);
- } else {
- invalidate();
- }
- }
-
- private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
- if (mUiThreadId == Thread.currentThread().getId()) {
- doRefreshProgress(id, progress, fromUser, true);
- } else {
- RefreshProgressRunnable r;
- if (mRefreshProgressRunnable != null) {
- // Use cached RefreshProgressRunnable if available
- r = mRefreshProgressRunnable;
- // Uncache it
- mRefreshProgressRunnable = null;
- r.setup(id, progress, fromUser);
- } else {
- // Make a new one
- r = new RefreshProgressRunnable(id, progress, fromUser);
- }
- post(r);
- }
- }
-
- /**
- * <p>Set the current progress to the specified value. Does not do anything
- * if the progress bar is in indeterminate mode.</p>
- *
- * @param progress the new progress, between 0 and {@link #getMax()}
- *
- * @see #setIndeterminate(boolean)
- * @see #isIndeterminate()
- * @see #getProgress()
- * @see #incrementProgressBy(int)
- */
- public synchronized void setProgress(int progress) {
- setProgress(progress, false);
- }
-
- synchronized void setProgress(int progress, boolean fromUser) {
- if (mIndeterminate) {
- return;
- }
-
- if (progress < 0) {
- progress = 0;
- }
-
- if (progress > mMax) {
- progress = mMax;
- }
-
- if (progress != mProgress) {
- mProgress = progress;
- refreshProgress(android.R.id.progress, mProgress, fromUser);
- }
- }
-
- /**
- * <p>
- * Set the current secondary progress to the specified value. Does not do
- * anything if the progress bar is in indeterminate mode.
- * </p>
- *
- * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
- * @see #setIndeterminate(boolean)
- * @see #isIndeterminate()
- * @see #getSecondaryProgress()
- * @see #incrementSecondaryProgressBy(int)
- */
- public synchronized void setSecondaryProgress(int secondaryProgress) {
- if (mIndeterminate) {
- return;
- }
-
- if (secondaryProgress < 0) {
- secondaryProgress = 0;
- }
-
- if (secondaryProgress > mMax) {
- secondaryProgress = mMax;
- }
-
- if (secondaryProgress != mSecondaryProgress) {
- mSecondaryProgress = secondaryProgress;
- refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
- }
- }
-
- /**
- * <p>Get the progress bar's current level of progress. Return 0 when the
- * progress bar is in indeterminate mode.</p>
- *
- * @return the current progress, between 0 and {@link #getMax()}
- *
- * @see #setIndeterminate(boolean)
- * @see #isIndeterminate()
- * @see #setProgress(int)
- * @see #setMax(int)
- * @see #getMax()
- */
- public synchronized int getProgress() {
- return mIndeterminate ? 0 : mProgress;
- }
-
- /**
- * <p>Get the progress bar's current level of secondary progress. Return 0 when the
- * progress bar is in indeterminate mode.</p>
- *
- * @return the current secondary progress, between 0 and {@link #getMax()}
- *
- * @see #setIndeterminate(boolean)
- * @see #isIndeterminate()
- * @see #setSecondaryProgress(int)
- * @see #setMax(int)
- * @see #getMax()
- */
- public synchronized int getSecondaryProgress() {
- return mIndeterminate ? 0 : mSecondaryProgress;
- }
-
- /**
- * <p>Return the upper limit of this progress bar's range.</p>
- *
- * @return a positive integer
- *
- * @see #setMax(int)
- * @see #getProgress()
- * @see #getSecondaryProgress()
- */
- public synchronized int getMax() {
- return mMax;
- }
-
- /**
- * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
- *
- * @param max the upper range of this progress bar
- *
- * @see #getMax()
- * @see #setProgress(int)
- * @see #setSecondaryProgress(int)
- */
- public synchronized void setMax(int max) {
- if (max < 0) {
- max = 0;
- }
- if (max != mMax) {
- mMax = max;
- postInvalidate();
-
- if (mProgress > max) {
- mProgress = max;
- }
- refreshProgress(android.R.id.progress, mProgress, false);
- }
- }
-
- /**
- * <p>Increase the progress bar's progress by the specified amount.</p>
- *
- * @param diff the amount by which the progress must be increased
- *
- * @see #setProgress(int)
- */
- public synchronized final void incrementProgressBy(int diff) {
- setProgress(mProgress + diff);
- }
-
- /**
- * <p>Increase the progress bar's secondary progress by the specified amount.</p>
- *
- * @param diff the amount by which the secondary progress must be increased
- *
- * @see #setSecondaryProgress(int)
- */
- public synchronized final void incrementSecondaryProgressBy(int diff) {
- setSecondaryProgress(mSecondaryProgress + diff);
- }
-
- /**
- * <p>Start the indeterminate progress animation.</p>
- */
- void startAnimation() {
- if (getVisibility() != VISIBLE) {
- return;
- }
-
- if (mIndeterminateDrawable instanceof Animatable) {
- mShouldStartAnimationDrawable = true;
- mAnimation = null;
- } else {
- if (mInterpolator == null) {
- mInterpolator = new LinearInterpolator();
- }
-
- mTransformation = new Transformation();
- mAnimation = new AlphaAnimation(0.0f, 1.0f);
- mAnimation.setRepeatMode(mBehavior);
- mAnimation.setRepeatCount(Animation.INFINITE);
- mAnimation.setDuration(mDuration);
- mAnimation.setInterpolator(mInterpolator);
- mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
- }
- postInvalidate();
- }
-
- /**
- * <p>Stop the indeterminate progress animation.</p>
- */
- void stopAnimation() {
- mAnimation = null;
- mTransformation = null;
- if (mIndeterminateDrawable instanceof Animatable) {
- ((Animatable) mIndeterminateDrawable).stop();
- mShouldStartAnimationDrawable = false;
- }
- postInvalidate();
- }
-
- /**
- * Sets the acceleration curve for the indeterminate animation.
- * The interpolator is loaded as a resource from the specified context.
- *
- * @param context The application environment
- * @param resID The resource identifier of the interpolator to load
- */
- public void setInterpolator(Context context, int resID) {
- setInterpolator(AnimationUtils.loadInterpolator(context, resID));
- }
-
- /**
- * Sets the acceleration curve for the indeterminate animation.
- * Defaults to a linear interpolation.
- *
- * @param interpolator The interpolator which defines the acceleration curve
- */
- public void setInterpolator(Interpolator interpolator) {
- mInterpolator = interpolator;
- }
-
- /**
- * Gets the acceleration curve type for the indeterminate animation.
- *
- * @return the {@link Interpolator} associated to this animation
- */
- public Interpolator getInterpolator() {
- return mInterpolator;
- }
-
- @Override
- public void setVisibility(int v) {
- if (getVisibility() != v) {
- super.setVisibility(v);
-
- if (mIndeterminate) {
- // let's be nice with the UI thread
- if (v == GONE || v == INVISIBLE) {
- stopAnimation();
- } else {
- startAnimation();
- }
- }
- }
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- if (Build.VERSION.SDK_INT >= 8) {
- super.onVisibilityChanged(changedView, visibility);
- }
-
- if (mIndeterminate) {
- // let's be nice with the UI thread
- if (visibility == GONE || visibility == INVISIBLE) {
- stopAnimation();
- } else {
- startAnimation();
- }
- }
- }
-
- @Override
- public void invalidateDrawable(Drawable dr) {
- if (!mInDrawing) {
- if (verifyDrawable(dr)) {
- final Rect dirty = dr.getBounds();
- final int scrollX = getScrollX() + getPaddingLeft();
- final int scrollY = getScrollY() + getPaddingTop();
-
- invalidate(dirty.left + scrollX, dirty.top + scrollY,
- dirty.right + scrollX, dirty.bottom + scrollY);
- } else {
- super.invalidateDrawable(dr);
- }
- }
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- updateDrawableBounds(w, h);
- }
-
- private void updateDrawableBounds(int w, int h) {
- // onDraw will translate the canvas so we draw starting at 0,0
- int right = w - getPaddingRight() - getPaddingLeft();
- int bottom = h - getPaddingBottom() - getPaddingTop();
- int top = 0;
- int left = 0;
-
- if (mIndeterminateDrawable != null) {
- // Aspect ratio logic does not apply to AnimationDrawables
- if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
- // Maintain aspect ratio. Certain kinds of animated drawables
- // get very confused otherwise.
- final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
- final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
- final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
- final float boundAspect = (float) w / h;
- if (intrinsicAspect != boundAspect) {
- if (boundAspect > intrinsicAspect) {
- // New width is larger. Make it smaller to match height.
- final int width = (int) (h * intrinsicAspect);
- left = (w - width) / 2;
- right = left + width;
- } else {
- // New height is larger. Make it smaller to match width.
- final int height = (int) (w * (1 / intrinsicAspect));
- top = (h - height) / 2;
- bottom = top + height;
- }
- }
- }
- mIndeterminateDrawable.setBounds(left, top, right, bottom);
- }
-
- if (mProgressDrawable != null) {
- mProgressDrawable.setBounds(0, 0, right, bottom);
- }
- }
-
- @Override
- protected synchronized void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- Drawable d = mCurrentDrawable;
- if (d != null) {
- // Translate canvas so a indeterminate circular progress bar with padding
- // rotates properly in its animation
- canvas.save();
- canvas.translate(getPaddingLeft(), getPaddingTop());
- long time = getDrawingTime();
- if (mAnimation != null) {
- mAnimation.getTransformation(time, mTransformation);
- float scale = mTransformation.getAlpha();
- try {
- mInDrawing = true;
- d.setLevel((int) (scale * MAX_LEVEL));
- } finally {
- mInDrawing = false;
- }
- if (SystemClock.uptimeMillis() - mLastDrawTime >= ANIMATION_RESOLUTION) {
- mLastDrawTime = SystemClock.uptimeMillis();
- postInvalidateDelayed(ANIMATION_RESOLUTION);
- }
- }
- d.draw(canvas);
- canvas.restore();
- if (mShouldStartAnimationDrawable && d instanceof Animatable) {
- ((Animatable) d).start();
- mShouldStartAnimationDrawable = false;
- }
- }
- }
-
- @Override
- protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- Drawable d = mCurrentDrawable;
-
- int dw = 0;
- int dh = 0;
- if (d != null) {
- dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
- dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
- }
- updateDrawableState();
- dw += getPaddingLeft() + getPaddingRight();
- dh += getPaddingTop() + getPaddingBottom();
-
- setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
- resolveSize(dh, heightMeasureSpec));
- }
-
- @Override
- protected void drawableStateChanged() {
- super.drawableStateChanged();
- updateDrawableState();
- }
-
- private void updateDrawableState() {
- int[] state = getDrawableState();
-
- if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
- mProgressDrawable.setState(state);
- }
-
- if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
- mIndeterminateDrawable.setState(state);
- }
- }
-
- static class SavedState extends BaseSavedState {
- int progress;
- int secondaryProgress;
-
- /**
- * Constructor called from {@link ProgressBarCompat#onSaveInstanceState()}
- */
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- /**
- * Constructor called from {@link #CREATOR}
- */
- private SavedState(Parcel in) {
- super(in);
- progress = in.readInt();
- secondaryProgress = in.readInt();
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(progress);
- out.writeInt(secondaryProgress);
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- // Force our ancestor class to save its state
- Parcelable superState = super.onSaveInstanceState();
- SavedState ss = new SavedState(superState);
-
- ss.progress = mProgress;
- ss.secondaryProgress = mSecondaryProgress;
-
- return ss;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
-
- setProgress(ss.progress);
- setSecondaryProgress(ss.secondaryProgress);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (mIndeterminate) {
- startAnimation();
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- if (mIndeterminate) {
- stopAnimation();
- }
- if(mRefreshProgressRunnable != null) {
- removeCallbacks(mRefreshProgressRunnable);
- }
-
- // This should come after stopAnimation(), otherwise an invalidate message remains in the
- // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
- super.onDetachedFromWindow();
- }
-
-}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java
new file mode 100644
index 0000000..48ebab8
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Movie;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Drawable.ConstantState;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.LongSparseArray;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This extends Resources but delegates the calls to another Resources object. This enables
+ * any customization done by some subclass of Resources to be also picked up.
+ */
+class ResourcesWrapper extends Resources {
+
+ private final Resources mResources;
+
+ public ResourcesWrapper(Resources resources) {
+ super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
+ mResources = resources;
+ }
+
+ @Override
+ public CharSequence getText(int id) throws NotFoundException {
+ return mResources.getText(id);
+ }
+
+ @Override
+ public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+ return mResources.getQuantityText(id, quantity);
+ }
+
+ @Override
+ public String getString(int id) throws NotFoundException {
+ return mResources.getString(id);
+ }
+
+ @Override
+ public String getString(int id, Object... formatArgs) throws NotFoundException {
+ return mResources.getString(id, formatArgs);
+ }
+
+ @Override
+ public String getQuantityString(int id, int quantity, Object... formatArgs)
+ throws NotFoundException {
+ return mResources.getQuantityString(id, quantity, formatArgs);
+ }
+
+ @Override
+ public String getQuantityString(int id, int quantity) throws NotFoundException {
+ return mResources.getQuantityString(id, quantity);
+ }
+
+ @Override
+ public CharSequence getText(int id, CharSequence def) {
+ return mResources.getText(id, def);
+ }
+
+ @Override
+ public CharSequence[] getTextArray(int id) throws NotFoundException {
+ return mResources.getTextArray(id);
+ }
+
+ @Override
+ public String[] getStringArray(int id) throws NotFoundException {
+ return mResources.getStringArray(id);
+ }
+
+ @Override
+ public int[] getIntArray(int id) throws NotFoundException {
+ return mResources.getIntArray(id);
+ }
+
+ @Override
+ public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ return mResources.obtainTypedArray(id);
+ }
+
+ @Override
+ public float getDimension(int id) throws NotFoundException {
+ return mResources.getDimension(id);
+ }
+
+ @Override
+ public int getDimensionPixelOffset(int id) throws NotFoundException {
+ return mResources.getDimensionPixelOffset(id);
+ }
+
+ @Override
+ public int getDimensionPixelSize(int id) throws NotFoundException {
+ return mResources.getDimensionPixelSize(id);
+ }
+
+ @Override
+ public float getFraction(int id, int base, int pbase) {
+ return mResources.getFraction(id, base, pbase);
+ }
+
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ return mResources.getDrawable(id);
+ }
+
+ @Override
+ public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
+ return mResources.getDrawable(id, theme);
+ }
+
+ @Override
+ public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
+ return mResources.getDrawableForDensity(id, density);
+ }
+
+ @Override
+ public Drawable getDrawableForDensity(int id, int density, Theme theme) {
+ return mResources.getDrawableForDensity(id, density, theme);
+ }
+
+ @Override
+ public Movie getMovie(int id) throws NotFoundException {
+ return mResources.getMovie(id);
+ }
+
+ @Override
+ public int getColor(int id) throws NotFoundException {
+ return mResources.getColor(id);
+ }
+
+ @Override
+ public ColorStateList getColorStateList(int id) throws NotFoundException {
+ return mResources.getColorStateList(id);
+ }
+
+ @Override
+ public boolean getBoolean(int id) throws NotFoundException {
+ return mResources.getBoolean(id);
+ }
+
+ @Override
+ public int getInteger(int id) throws NotFoundException {
+ return mResources.getInteger(id);
+ }
+
+ @Override
+ public XmlResourceParser getLayout(int id) throws NotFoundException {
+ return mResources.getLayout(id);
+ }
+
+ @Override
+ public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ return mResources.getAnimation(id);
+ }
+
+ @Override
+ public XmlResourceParser getXml(int id) throws NotFoundException {
+ return mResources.getXml(id);
+ }
+
+ @Override
+ public InputStream openRawResource(int id) throws NotFoundException {
+ return mResources.openRawResource(id);
+ }
+
+ @Override
+ public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+ return mResources.openRawResource(id, value);
+ }
+
+ @Override
+ public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ return mResources.openRawResourceFd(id);
+ }
+
+ @Override
+ public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResources.getValue(id, outValue, resolveRefs);
+ }
+
+ @Override
+ public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResources.getValueForDensity(id, density, outValue, resolveRefs);
+ }
+
+ @Override
+ public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResources.getValue(name, outValue, resolveRefs);
+ }
+
+ @Override
+ public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+ return mResources.obtainAttributes(set, attrs);
+ }
+
+ @Override
+ public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
+ super.updateConfiguration(config, metrics);
+ if (mResources != null) { // called from super's constructor. So, need to check.
+ mResources.updateConfiguration(config, metrics);
+ }
+ }
+
+ @Override
+ public DisplayMetrics getDisplayMetrics() {
+ return mResources.getDisplayMetrics();
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return mResources.getConfiguration();
+ }
+
+ @Override
+ public int getIdentifier(String name, String defType, String defPackage) {
+ return mResources.getIdentifier(name, defType, defPackage);
+ }
+
+ @Override
+ public String getResourceName(int resid) throws NotFoundException {
+ return mResources.getResourceName(resid);
+ }
+
+ @Override
+ public String getResourcePackageName(int resid) throws NotFoundException {
+ return mResources.getResourcePackageName(resid);
+ }
+
+ @Override
+ public String getResourceTypeName(int resid) throws NotFoundException {
+ return mResources.getResourceTypeName(resid);
+ }
+
+ @Override
+ public String getResourceEntryName(int resid) throws NotFoundException {
+ return mResources.getResourceEntryName(resid);
+ }
+
+ @Override
+ public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
+ throws XmlPullParserException, IOException {
+ mResources.parseBundleExtras(parser, outBundle);
+ }
+
+ @Override
+ public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle)
+ throws XmlPullParserException {
+ mResources.parseBundleExtra(tagName, attrs, outBundle);
+ }
+}
+
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java b/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java
new file mode 100644
index 0000000..92c1b40
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.widget.AutoCompleteTextView;
+import android.widget.EditText;
+
+/**
+ * An tint aware {@link android.widget.AutoCompleteTextView}.
+ *
+ * @hide
+ */
+public class TintAutoCompleteTextView extends AutoCompleteTextView {
+
+ private static final int[] TINT_ATTRS = {
+ android.R.attr.background,
+ android.R.attr.popupBackground
+ };
+
+ private final TintManager mTintManager;
+
+ public TintAutoCompleteTextView(Context context) {
+ this(context, null);
+ }
+
+ public TintAutoCompleteTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.autoCompleteTextViewStyle);
+ }
+
+ public TintAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+ defStyleAttr, 0);
+ setBackgroundDrawable(a.getDrawable(0));
+ if (a.hasValue(1)) {
+ setDropDownBackgroundDrawable(a.getDrawable(1));
+ }
+ a.recycle();
+
+ mTintManager = a.getTintManager();
+ }
+
+ @Override
+ public void setDropDownBackgroundResource(int id) {
+ setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintButton.java b/v7/appcompat/src/android/support/v7/internal/widget/TintButton.java
new file mode 100644
index 0000000..fc822cd
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintButton.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * An tint aware {@link android.widget.Button}
+ *
+ * @hide
+ */
+public class TintButton extends Button {
+
+ private static final int[] TINT_ATTRS = {
+ android.R.attr.background,
+ android.R.attr.textAppearance
+ };
+
+ private final TintManager mTintManager;
+
+ public TintButton(Context context) {
+ this(context, null);
+ }
+
+ public TintButton(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.buttonStyle);
+ }
+
+ public TintButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+ defStyleAttr, 0);
+ if (a.hasValue(0)) {
+ setBackgroundDrawable(a.getDrawable(0));
+ }
+
+ // Keep the TintManager in case we need it later
+ mTintManager = a.getTintManager();
+ }
+
+ @Override
+ public void setBackgroundResource(int resid) {
+ setBackgroundDrawable(mTintManager.getDrawable(resid));
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
index bed1cdd..52e3206 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
@@ -59,7 +59,8 @@
R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha,
R.drawable.abc_ic_voice_search_api_mtrl_alpha,
R.drawable.abc_textfield_search_default_mtrl_alpha,
- R.drawable.abc_textfield_default_mtrl_alpha
+ R.drawable.abc_textfield_default_mtrl_alpha,
+ R.drawable.abc_ab_share_pack_mtrl_alpha
};
/**
@@ -92,7 +93,9 @@
R.drawable.abc_textfield_search_material,
R.drawable.abc_spinner_mtrl_am_alpha,
R.drawable.abc_btn_check_material,
- R.drawable.abc_btn_radio_material
+ R.drawable.abc_btn_radio_material,
+ R.drawable.abc_spinner_textfield_background_material,
+ R.drawable.abc_ratingbar_full_material
};
/**
@@ -110,6 +113,7 @@
private ColorStateList mDefaultColorStateList;
private ColorStateList mSwitchThumbStateList;
private ColorStateList mSwitchTrackStateList;
+ private ColorStateList mButtonStateList;
/**
* A helper method to instantiate a {@link TintManager} and then call {@link #getDrawable(int)}.
@@ -133,6 +137,8 @@
Drawable drawable = ContextCompat.getDrawable(mContext, resId);
if (drawable != null) {
+ drawable = drawable.mutate();
+
if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {
drawable = new TintDrawableWrapper(drawable, getDefaultColorStateList());
} else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
@@ -140,6 +146,8 @@
} else if (resId == R.drawable.abc_switch_thumb_material) {
drawable = new TintDrawableWrapper(drawable, getSwitchThumbColorStateList(),
PorterDuff.Mode.MULTIPLY);
+ } else if (resId == R.drawable.abc_btn_default_mtrl_shape) {
+ drawable = new TintDrawableWrapper(drawable, getButtonColorStateList());
} else if (arrayContains(CONTAINERS_WITH_TINT_CHILDREN, resId)) {
drawable = mResources.getDrawable(resId);
} else {
@@ -316,6 +324,35 @@
return mSwitchThumbStateList;
}
+ private ColorStateList getButtonColorStateList() {
+ if (mButtonStateList == null) {
+ final int[][] states = new int[4][];
+ final int[] colors = new int[4];
+ int i = 0;
+
+ // Disabled state
+ states[i] = new int[] { -android.R.attr.state_enabled };
+ colors[i] = getDisabledThemeAttrColor(R.attr.colorButtonNormal);
+ i++;
+
+ states[i] = new int[] { android.R.attr.state_pressed };
+ colors[i] = getThemeAttrColor(R.attr.colorControlHighlight);
+ i++;
+
+ states[i] = new int[] { android.R.attr.state_focused };
+ colors[i] = getThemeAttrColor(R.attr.colorControlHighlight);
+ i++;
+
+ // Default enabled state
+ states[i] = new int[0];
+ colors[i] = getThemeAttrColor(R.attr.colorButtonNormal);
+ i++;
+
+ mButtonStateList = new ColorStateList(states, colors);
+ }
+ return mButtonStateList;
+ }
+
int getThemeAttrColor(int attr) {
if (mContext.getTheme().resolveAttribute(attr, mTypedValue, true)) {
if (mTypedValue.type >= TypedValue.TYPE_FIRST_INT
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java b/v7/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java
new file mode 100644
index 0000000..9746335
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.AutoCompleteTextView;
+import android.widget.MultiAutoCompleteTextView;
+
+/**
+ * An tint aware {@link android.widget.MultiAutoCompleteTextView}.
+ *
+ * @hide
+ */
+public class TintMultiAutoCompleteTextView extends MultiAutoCompleteTextView {
+
+ private static final int[] TINT_ATTRS = {
+ android.R.attr.background,
+ android.R.attr.popupBackground
+ };
+
+ private final TintManager mTintManager;
+
+ public TintMultiAutoCompleteTextView(Context context) {
+ this(context, null);
+ }
+
+ public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.autoCompleteTextViewStyle);
+ }
+
+ public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+ defStyleAttr, 0);
+ setBackgroundDrawable(a.getDrawable(0));
+ if (a.hasValue(1)) {
+ setDropDownBackgroundDrawable(a.getDrawable(1));
+ }
+ a.recycle();
+
+ mTintManager = a.getTintManager();
+ }
+
+ @Override
+ public void setDropDownBackgroundResource(int id) {
+ setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
+ }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java b/v7/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java
new file mode 100644
index 0000000..592c6ff
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Shader;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.RatingBar;
+
+/**
+ * An tint aware {@link android.widget.RatingBar}.
+ *
+ * @hide
+ */
+public class TintRatingBar extends RatingBar {
+
+ private static final int[] TINT_ATTRS = {
+ android.R.attr.indeterminateDrawable,
+ android.R.attr.progressDrawable
+ };
+
+ private Bitmap mSampleTile;
+
+ public TintRatingBar(Context context) {
+ this(context, null);
+ }
+
+ public TintRatingBar(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.ratingBarStyle);
+ }
+
+ public TintRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+ defStyleAttr, 0);
+
+ Drawable drawable = a.getDrawable(0);
+ if (drawable != null) {
+ setIndeterminateDrawable(tileifyIndeterminate(drawable));
+ }
+
+ drawable = a.getDrawable(1);
+ if (drawable != null) {
+ setProgressDrawable(tileify(drawable, false));
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Converts a drawable to a tiled version of itself. It will recursively
+ * traverse layer and state list drawables.
+ */
+ private Drawable tileify(Drawable drawable, boolean clip) {
+ if (drawable instanceof DrawableWrapper) {
+ Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable();
+ if (inner != null) {
+ inner = tileify(inner, clip);
+ ((DrawableWrapper) drawable).setWrappedDrawable(inner);
+ }
+ } else if (drawable instanceof LayerDrawable) {
+ LayerDrawable background = (LayerDrawable) drawable;
+ final int N = background.getNumberOfLayers();
+ Drawable[] outDrawables = new Drawable[N];
+
+ for (int i = 0; i < N; i++) {
+ int id = background.getId(i);
+ outDrawables[i] = tileify(background.getDrawable(i),
+ (id == android.R.id.progress || id == android.R.id.secondaryProgress));
+ }
+ LayerDrawable newBg = new LayerDrawable(outDrawables);
+
+ for (int i = 0; i < N; i++) {
+ newBg.setId(i, background.getId(i));
+ }
+
+ return newBg;
+
+ } else if (drawable instanceof BitmapDrawable) {
+ final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+ if (mSampleTile == null) {
+ mSampleTile = tileBitmap;
+ }
+
+ final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
+ final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
+ Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+ shapeDrawable.getPaint().setShader(bitmapShader);
+ return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
+ ClipDrawable.HORIZONTAL) : shapeDrawable;
+ }
+
+ return drawable;
+ }
+
+ /**
+ * Convert a AnimationDrawable for use as a barberpole animation.
+ * Each frame of the animation is wrapped in a ClipDrawable and
+ * given a tiling BitmapShader.
+ */
+ private Drawable tileifyIndeterminate(Drawable drawable) {
+ if (drawable instanceof AnimationDrawable) {
+ AnimationDrawable background = (AnimationDrawable) drawable;
+ final int N = background.getNumberOfFrames();
+ AnimationDrawable newBg = new AnimationDrawable();
+ newBg.setOneShot(background.isOneShot());
+
+ for (int i = 0; i < N; i++) {
+ Drawable frame = tileify(background.getFrame(i), true);
+ frame.setLevel(10000);
+ newBg.addFrame(frame, background.getDuration(i));
+ }
+ newBg.setLevel(10000);
+ drawable = newBg;
+ }
+ return drawable;
+ }
+
+ private Shape getDrawableShape() {
+ final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
+ return new RoundRectShape(roundedCorners, null, null);
+ }
+
+ @Override
+ protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (mSampleTile != null) {
+ final int width = mSampleTile.getWidth() * getNumStars();
+ setMeasuredDimension(ViewCompat.resolveSizeAndState(width, widthMeasureSpec, 0),
+ getMeasuredHeight());
+ }
+ }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java b/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java
index eb36855..3dfbbc1 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java
@@ -25,12 +25,12 @@
*
* @hide
*/
-class TintResources extends Resources {
+class TintResources extends ResourcesWrapper {
private final TintManager mTintManager;
public TintResources(Resources resources, TintManager tintManager) {
- super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
+ super(resources);
mTintManager = tintManager;
}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java b/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
index e7e79cd..3f81d35 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
@@ -16,12 +16,16 @@
package android.support.v7.internal.widget;
+import android.annotation.TargetApi;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
-import android.widget.EditText;
+import android.widget.ListPopupWindow;
import android.widget.Spinner;
+import java.lang.reflect.Field;
+
/**
* An tint aware {@link android.widget.Spinner}.
*
@@ -49,11 +53,34 @@
defStyleAttr, 0);
setBackgroundDrawable(a.getDrawable(0));
- if (Build.VERSION.SDK_INT >= 16 && a.hasValue(1)) {
- setPopupBackgroundDrawable(a.getDrawable(1));
+ if (a.hasValue(1)) {
+ final Drawable background = a.getDrawable(1);
+ if (Build.VERSION.SDK_INT >= 16) {
+ setPopupBackgroundDrawable(background);
+ } else if (Build.VERSION.SDK_INT >= 11) {
+ setPopupBackgroundDrawableV11(this, background);
+ }
}
a.recycle();
}
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static void setPopupBackgroundDrawableV11(Spinner view, Drawable background) {
+ try {
+ Field popupField = Spinner.class.getDeclaredField("mPopup");
+ popupField.setAccessible(true);
+
+ Object popup = popupField.get(view);
+
+ if (popup instanceof ListPopupWindow) {
+ ((ListPopupWindow) popup).setBackgroundDrawable(background);
+ }
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
index 9d8fc7c..ff22b16 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
@@ -95,6 +95,7 @@
mTitle = toolbar.getTitle();
mSubtitle = toolbar.getSubtitle();
mTitleSet = mTitle != null;
+ mNavIcon = mToolbar.getNavigationIcon();
if (style) {
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
@@ -676,4 +677,41 @@
mToolbar.restoreHierarchyState(toolbarStates);
}
+ @Override
+ public void setBackgroundDrawable(Drawable d) {
+ //noinspection deprecation
+ mToolbar.setBackgroundDrawable(d);
+ }
+
+ @Override
+ public int getHeight() {
+ return mToolbar.getHeight();
+ }
+
+ @Override
+ public void setVisibility(int visible) {
+ mToolbar.setVisibility(visible);
+ }
+
+ @Override
+ public int getVisibility() {
+ return mToolbar.getVisibility();
+ }
+
+ @Override
+ public void setMenuCallbacks(MenuPresenter.Callback actionMenuPresenterCallback,
+ MenuBuilder.Callback menuBuilderCallback) {
+ mToolbar.setMenuCallbacks(actionMenuPresenterCallback, menuBuilderCallback);
+ }
+
+ @Override
+ public Menu getMenu() {
+ return mToolbar.getMenu();
+ }
+
+ @Override
+ public int getPopupTheme() {
+ return mToolbar.getPopupTheme();
+ }
+
}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
index d1f83d4..563961a 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
@@ -439,7 +439,7 @@
}
final int childCount = getChildCount();
- final int midVertical = (top + bottom) / 2;
+ final int midVertical = (bottom - top) / 2;
final int dividerWidth = getDividerWidth();
int overflowWidth = 0;
int nonOverflowWidth = 0;
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
index 687b9c8..01d5e1c 100644
--- a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -212,10 +212,23 @@
* @param defStyleAttr Default style attribute to use for popup content.
*/
public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Create a new, empty popup window capable of displaying items from a ListAdapter.
+ * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+ *
+ * @param context Context used for contained views.
+ * @param attrs Attributes from inflating parent views used to style the popup.
+ * @param defStyleAttr Style attribute to read for default styling of popup content.
+ * @param defStyleRes Style resource ID to use for default styling of popup content.
+ */
+ public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,
- defStyleAttr, 0);
+ defStyleAttr, defStyleRes);
mDropDownHorizontalOffset = a.getDimensionPixelOffset(
R.styleable.ListPopupWindow_android_dropDownHorizontalOffset, 0);
mDropDownVerticalOffset = a.getDimensionPixelOffset(
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 14813b1..5b7d333 100644
--- a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
+++ b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.support.annotation.MenuRes;
+import android.support.v7.appcompat.R;
import android.support.v7.internal.view.SupportMenuInflater;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuPopupHelper;
@@ -70,20 +71,44 @@
}
/**
- * Construct a new PopupMenu.
+ * Constructor to create a new popup menu with an anchor view and alignment
+ * gravity.
*
- * @param context Context for the PopupMenu.
- * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
- * is room, or above it if there is not.
- * @param gravity The {@link Gravity} value for aligning the popup with its anchor
+ * @param context Context the popup menu is running in, through which it
+ * can access the current theme, resources, etc.
+ * @param anchor Anchor view for this popup. The popup will appear below
+ * the anchor if there is room, or above it if there is not.
+ * @param gravity The {@link Gravity} value for aligning the popup with its
+ * anchor.
*/
public PopupMenu(Context context, View anchor, int gravity) {
- // TODO Theme?
+ this(context, anchor, gravity, R.attr.popupMenuStyle, 0);
+ }
+
+ /**
+ * Constructor a create a new popup menu with a specific style.
+ *
+ * @param context Context the popup menu is running in, through which it
+ * can access the current theme, resources, etc.
+ * @param anchor Anchor view for this popup. The popup will appear below
+ * the anchor if there is room, or above it if there is not.
+ * @param gravity The {@link Gravity} value for aligning the popup with its
+ * anchor.
+ * @param popupStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the popup window. Can be 0 to not look for defaults.
+ * @param popupStyleRes A resource identifier of a style resource that
+ * supplies default values for the popup window, used only if
+ * popupStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
+ int popupStyleRes) {
mContext = context;
mMenu = new MenuBuilder(context);
mMenu.setCallback(this);
mAnchor = anchor;
- mPopup = new MenuPopupHelper(context, mMenu, anchor);
+ mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
mPopup.setGravity(gravity);
mPopup.setCallback(this);
}
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index fbf3b24..46ad229 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -39,6 +39,7 @@
import android.support.v4.view.KeyEventCompat;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.appcompat.R;
+import android.support.v7.internal.widget.TintAutoCompleteTextView;
import android.support.v7.internal.widget.TintManager;
import android.support.v7.internal.widget.TintTypedArray;
import android.support.v7.internal.widget.ViewUtils;
@@ -1605,17 +1606,11 @@
* Local subclass for AutoCompleteTextView.
* @hide
*/
- public static class SearchAutoComplete extends AutoCompleteTextView {
-
- private final int[] POPUP_WINDOW_ATTRS = {
- android.R.attr.popupBackground
- };
+ public static class SearchAutoComplete extends TintAutoCompleteTextView {
private int mThreshold;
private SearchView mSearchView;
- private final TintManager mTintManager;
-
public SearchAutoComplete(Context context) {
this(context, null);
}
@@ -1627,16 +1622,6 @@
public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mThreshold = getThreshold();
-
- TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
- POPUP_WINDOW_ATTRS, defStyle, 0);
- if (a.hasValue(0)) {
- setDropDownBackgroundDrawable(a.getDrawable(0));
- }
- a.recycle();
-
- // Keep the TintManager in case we need it later
- mTintManager = a.getTintManager();
}
void setSearchView(SearchView searchView) {
@@ -1649,11 +1634,6 @@
mThreshold = threshold;
}
- @Override
- public void setDropDownBackgroundResource(int id) {
- setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
- }
-
/**
* Returns true if the text field is empty, or contains only whitespace.
*/
diff --git a/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java b/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
index 5c161dc..173e5fc 100644
--- a/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
+++ b/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
@@ -21,7 +21,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.support.v4.content.ContextCompat;
+import android.os.Build;
import android.support.v4.view.ActionProvider;
import android.support.v7.appcompat.R;
import android.support.v7.internal.widget.ActivityChooserModel;
@@ -179,9 +179,11 @@
@Override
public View onCreateActionView() {
// Create the view and set its data model.
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
- activityChooserView.setActivityChooserModel(dataModel);
+ if (!activityChooserView.isInEditMode()) {
+ ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
+ activityChooserView.setActivityChooserModel(dataModel);
+ }
// Lookup and set the expand action icon.
TypedValue outTypedValue = new TypedValue();
@@ -299,6 +301,12 @@
* @see Intent#ACTION_SEND_MULTIPLE
*/
public void setShareIntent(Intent shareIntent) {
+ if (shareIntent != null) {
+ final String action = shareIntent.getAction();
+ if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+ updateIntent(shareIntent);
+ }
+ }
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
mShareHistoryFileName);
dataModel.setIntent(shareIntent);
@@ -315,7 +323,11 @@
final int itemId = item.getItemId();
Intent launchIntent = dataModel.chooseActivity(itemId);
if (launchIntent != null) {
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ final String action = launchIntent.getAction();
+ if (Intent.ACTION_SEND.equals(action) ||
+ Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+ updateIntent(launchIntent);
+ }
mContext.startActivity(launchIntent);
}
return true;
@@ -350,4 +362,15 @@
return false;
}
}
+
+ private void updateIntent(Intent intent) {
+ if (Build.VERSION.SDK_INT >= 21) {
+ // If we're on Lollipop, we can open the intent as a document
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+ Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ } else {
+ // Else, we will use the old CLEAR_WHEN_TASK_RESET flag
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ }
+ }
}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java b/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
index 244ec77..6251e0e 100644
--- a/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
+++ b/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
import android.support.v4.widget.ResourceCursorAdapter;
import android.support.v7.appcompat.R;
import android.text.Spannable;
@@ -64,11 +65,12 @@
static final int REFINE_BY_ENTRY = 1;
static final int REFINE_ALL = 2;
- private SearchManager mSearchManager;
- private SearchView mSearchView;
- private SearchableInfo mSearchable;
- private Context mProviderContext;
- private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
+ private final SearchManager mSearchManager;
+ private final SearchView mSearchView;
+ private final SearchableInfo mSearchable;
+ private final Context mProviderContext;
+ private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
+ private final int mCommitIconResId;
private boolean mClosed = false;
private int mQueryRefinement = REFINE_BY_ENTRY;
@@ -88,15 +90,15 @@
// private final Runnable mStartSpinnerRunnable;
// private final Runnable mStopSpinnerRunnable;
- public SuggestionsAdapter(Context context, SearchView searchView,
- SearchableInfo searchable,
+ public SuggestionsAdapter(Context context, SearchView searchView, SearchableInfo searchable,
WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
- super(context, R.layout.abc_search_dropdown_item_icons_2line,
- null, // no initial cursor
- true); // auto-requery
+ super(context, searchView.getSuggestionRowLayout(), null /* no initial cursor */,
+ true /* auto-requery */);
mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
mSearchView = searchView;
mSearchable = searchable;
+ mCommitIconResId = searchView.getSuggestionCommitIconResId();
+
// set up provider resources (gives us icons, etc.)
mProviderContext = context;
@@ -109,7 +111,7 @@
* copied to the query text field.
* <p>
*
- * @param refineWhat which queries to refine. Possible values are {@link #REFINE_NONE},
+ * @param refine which queries to refine. Possible values are {@link #REFINE_NONE},
* {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
*/
public void setQueryRefinement(int refineWhat) {
@@ -193,9 +195,9 @@
Bundle extras = cursor != null ? cursor.getExtras() : null;
if (DBG) {
Log.d(LOG_TAG, "updateSpinnerState - extra = "
- + (extras != null
- ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
- : null));
+ + (extras != null
+ ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
+ : null));
}
// Check if the Cursor indicates that the query is not complete and show the spinner
if (extras != null
@@ -239,8 +241,12 @@
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View v = super.newView(context, cursor, parent);
+ final View v = super.newView(context, cursor, parent);
v.setTag(new ChildViewCache(v));
+
+ // Set up icon.
+ final ImageView iconRefine = (ImageView) v.findViewById(R.id.edit_query);
+ iconRefine.setImageResource(mCommitIconResId);
return v;
}
@@ -309,7 +315,7 @@
}
if (mQueryRefinement == REFINE_ALL
|| (mQueryRefinement == REFINE_BY_ENTRY
- && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) {
+ && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) {
views.mIconRefine.setVisibility(View.VISIBLE);
views.mIconRefine.setTag(views.mText1.getText());
views.mIconRefine.setOnClickListener(this);
@@ -489,7 +495,7 @@
return drawable;
}
// Not cached, find it by resource ID
- drawable = mProviderContext.getResources().getDrawable(resourceId);
+ drawable = ContextCompat.getDrawable(mProviderContext, resourceId);
// Stick it in the cache, using the URI as key
storeInIconCache(drawableUri, drawable);
return drawable;
diff --git a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
index e894f49..74d6e4a 100644
--- a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
@@ -44,6 +44,7 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
@@ -75,12 +76,6 @@
private static final int TOUCH_MODE_DOWN = 1;
private static final int TOUCH_MODE_DRAGGING = 2;
- private static final int[] TEXT_APPEARANCE_ATTRS = {
- android.R.attr.textColor,
- android.R.attr.textSize,
- R.attr.textAllCaps
- };
-
// Enum for the "typeface" XML parameter.
private static final int SANS = 1;
private static final int SERIF = 2;
@@ -228,12 +223,14 @@
* from the specified TextAppearance resource.
*/
public void setSwitchTextAppearance(Context context, int resid) {
- TypedArray appearance = context.obtainStyledAttributes(resid, TEXT_APPEARANCE_ATTRS);
+ TypedArray appearance = context.obtainStyledAttributes(resid,
+ R.styleable.SwitchCompatTextAppearance);
ColorStateList colors;
int ts;
- colors = appearance.getColorStateList(0);
+ colors = appearance.getColorStateList(
+ R.styleable.SwitchCompatTextAppearance_android_textColor);
if (colors != null) {
mTextColors = colors;
} else {
@@ -241,7 +238,8 @@
mTextColors = getTextColors();
}
- ts = appearance.getDimensionPixelSize(1, 0);
+ ts = appearance.getDimensionPixelSize(
+ R.styleable.SwitchCompatTextAppearance_android_textSize, 0);
if (ts != 0) {
if (ts != mTextPaint.getTextSize()) {
mTextPaint.setTextSize(ts);
@@ -249,7 +247,8 @@
}
}
- boolean allCaps = appearance.getBoolean(2, false);
+ boolean allCaps = appearance.getBoolean(
+ R.styleable.SwitchCompatTextAppearance_textAllCaps, false);
if (allCaps) {
mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
} else {
@@ -685,6 +684,7 @@
// Commit the change if the event is up and not canceled and the switch
// has not been disabled during the drag.
final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
+ final boolean oldState = isChecked();
final boolean newState;
if (commitChange) {
mVelocityTracker.computeCurrentVelocity(1000);
@@ -695,10 +695,13 @@
newState = getTargetCheckedState();
}
} else {
- newState = isChecked();
+ newState = oldState;
}
- setChecked(newState);
+ if (newState != oldState) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ setChecked(newState);
+ }
cancelSuperTouch(ev);
}
@@ -990,7 +993,9 @@
@Override
public void drawableHotspotChanged(float x, float y) {
- super.drawableHotspotChanged(x, y);
+ if (Build.VERSION.SDK_INT >= 21) {
+ super.drawableHotspotChanged(x, y);
+ }
if (mThumbDrawable != null) {
DrawableCompat.setHotspot(mThumbDrawable, x, y);
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
index 2330b04..d98aec7 100644
--- a/v7/appcompat/src/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -114,6 +114,7 @@
private ImageView mLogoView;
private Drawable mCollapseIcon;
+ private CharSequence mCollapseDescription;
private ImageButton mCollapseButtonView;
View mExpandedActionView;
@@ -146,6 +147,7 @@
private int mSubtitleTextColor;
private boolean mEatingTouch;
+ private boolean mEatingHover;
// Clear me after use.
private final ArrayList<View> mTempViews = new ArrayList<View>();
@@ -200,7 +202,7 @@
mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
mGravity = a.getInteger(R.styleable.Toolbar_android_gravity, mGravity);
- mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
+ mButtonGravity = Gravity.TOP;
mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
@@ -246,6 +248,7 @@
}
mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
+ mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription);
final CharSequence title = a.getText(R.styleable.Toolbar_title);
if (!TextUtils.isEmpty(title)) {
@@ -1001,6 +1004,7 @@
mCollapseButtonView = new ImageButton(getContext(), null,
R.attr.toolbarNavigationButtonStyle);
mCollapseButtonView.setImageDrawable(mCollapseIcon);
+ mCollapseButtonView.setContentDescription(mCollapseDescription);
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
lp.mViewType = LayoutParams.EXPANDED;
@@ -1095,6 +1099,30 @@
return true;
}
+ @Override
+ public boolean onHoverEvent(MotionEvent ev) {
+ // Same deal as onTouchEvent() above. Eat all hover events, but still
+ // respect the touch event dispatch contract.
+
+ final int action = MotionEventCompat.getActionMasked(ev);
+ if (action == MotionEvent.ACTION_HOVER_ENTER) {
+ mEatingHover = false;
+ }
+
+ if (!mEatingHover) {
+ final boolean handled = super.onHoverEvent(ev);
+ if (action == MotionEvent.ACTION_HOVER_ENTER && !handled) {
+ mEatingHover = true;
+ }
+ }
+
+ if (action == MotionEvent.ACTION_HOVER_EXIT || action == MotionEvent.ACTION_CANCEL) {
+ mEatingHover = false;
+ }
+
+ return true;
+ }
+
private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed,
int parentHeightSpec, int heightUsed, int heightConstraint) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
diff --git a/v7/cardview/Android.mk b/v7/cardview/Android.mk
index 5c8da8c..ff60510 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -55,7 +55,7 @@
# A helper sub-library that makes direct use of L APIs
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v7-cardview-api21
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, api21)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base \
android-support-v7-cardview-jellybean-mr1
diff --git a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
index 02c77f8..833ccc8 100644
--- a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
+++ b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
@@ -101,4 +101,9 @@
public void onPreventCornerOverlapChanged(CardViewDelegate cardView) {
setMaxElevation(cardView, getMaxElevation(cardView));
}
+
+ @Override
+ public void setBackgroundColor(CardViewDelegate cardView, int color) {
+ ((RoundRectDrawable) (cardView.getBackground())).setColor(color);
+ }
}
\ No newline at end of file
diff --git a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
index 5067487..3477761 100644
--- a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
+++ b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
@@ -118,10 +118,15 @@
@Override
public int getOpacity() {
- return PixelFormat.OPAQUE;
+ return PixelFormat.TRANSLUCENT;
}
public float getRadius() {
return mRadius;
}
+
+ public void setColor(int color) {
+ mPaint.setColor(color);
+ invalidateSelf();
+ }
}
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
index ae78303..24b902c 100644
--- a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
+++ b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
@@ -46,4 +46,6 @@
void onCompatPaddingChanged(CardViewDelegate cardView);
void onPreventCornerOverlapChanged(CardViewDelegate cardView);
+
+ void setBackgroundColor(CardViewDelegate cardView, int color);
}
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
index f9cb50a..8f7adfc 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
@@ -37,30 +37,36 @@
public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
Paint paint) {
final float twoRadius = cornerRadius * 2;
- final float innerWidth = bounds.width() - twoRadius;
- final float innerHeight = bounds.height() - twoRadius;
- sCornerRect.set(bounds.left, bounds.top,
- bounds.left + cornerRadius * 2, bounds.top + cornerRadius * 2);
-
- canvas.drawArc(sCornerRect, 180, 90, true, paint);
- sCornerRect.offset(innerWidth, 0);
- canvas.drawArc(sCornerRect, 270, 90, true, paint);
- sCornerRect.offset(0, innerHeight);
- canvas.drawArc(sCornerRect, 0, 90, true, paint);
- sCornerRect.offset(-innerWidth, 0);
- canvas.drawArc(sCornerRect, 90, 90, true, paint);
-
- //draw top and bottom pieces
- canvas.drawRect(bounds.left + cornerRadius, bounds.top,
- bounds.right - cornerRadius, bounds.top + cornerRadius,
- paint);
- canvas.drawRect(bounds.left + cornerRadius,
- bounds.bottom - cornerRadius, bounds.right - cornerRadius,
- bounds.bottom, paint);
-
- //center
- canvas.drawRect(bounds.left, bounds.top + cornerRadius,
- bounds.right, bounds.bottom - cornerRadius, paint);
+ final float innerWidth = bounds.width() - twoRadius - 1;
+ final float innerHeight = bounds.height() - twoRadius - 1;
+ // increment it to account for half pixels.
+ if (cornerRadius >= 1f) {
+ cornerRadius += .5f;
+ sCornerRect.set(-cornerRadius, -cornerRadius, cornerRadius, cornerRadius);
+ int saved = canvas.save();
+ canvas.translate(bounds.left + cornerRadius, bounds.top + cornerRadius);
+ canvas.drawArc(sCornerRect, 180, 90, true, paint);
+ canvas.translate(innerWidth, 0);
+ canvas.rotate(90);
+ canvas.drawArc(sCornerRect, 180, 90, true, paint);
+ canvas.translate(innerHeight, 0);
+ canvas.rotate(90);
+ canvas.drawArc(sCornerRect, 180, 90, true, paint);
+ canvas.translate(innerWidth, 0);
+ canvas.rotate(90);
+ canvas.drawArc(sCornerRect, 180, 90, true, paint);
+ canvas.restoreToCount(saved);
+ //draw top and bottom pieces
+ canvas.drawRect(bounds.left + cornerRadius - 1f, bounds.top,
+ bounds.right - cornerRadius + 1f, bounds.top + cornerRadius,
+ paint);
+ canvas.drawRect(bounds.left + cornerRadius - 1f,
+ bounds.bottom - cornerRadius + 1f, bounds.right - cornerRadius + 1f,
+ bounds.bottom, paint);
+ }
+//// center
+ canvas.drawRect(bounds.left, bounds.top + Math.max(0, cornerRadius - 1f),
+ bounds.right, bounds.bottom - cornerRadius + 1f, paint);
}
};
}
@@ -85,8 +91,8 @@
public void updatePadding(CardViewDelegate cardView) {
Rect shadowPadding = new Rect();
getShadowBackground(cardView).getMaxShadowAndCornerPadding(shadowPadding);
- ((View)cardView).setMinimumHeight((int) Math.ceil(getMinHeight(cardView)));
- ((View)cardView).setMinimumWidth((int) Math.ceil(getMinWidth(cardView)));
+ ((View) cardView).setMinimumHeight((int) Math.ceil(getMinHeight(cardView)));
+ ((View) cardView).setMinimumWidth((int) Math.ceil(getMinWidth(cardView)));
cardView.setShadowPadding(shadowPadding.left, shadowPadding.top,
shadowPadding.right, shadowPadding.bottom);
}
@@ -103,6 +109,11 @@
}
@Override
+ public void setBackgroundColor(CardViewDelegate cardView, int color) {
+ getShadowBackground(cardView).setColor(color);
+ }
+
+ @Override
public void setRadius(CardViewDelegate cardView, float radius) {
getShadowBackground(cardView).setCornerRadius(radius);
updatePadding(cardView);
@@ -147,4 +158,4 @@
private RoundRectDrawableWithShadow getShadowBackground(CardViewDelegate cardView) {
return ((RoundRectDrawableWithShadow) cardView.getBackground());
}
-}
\ No newline at end of file
+}
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
index 90bfef0..054ada8 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
@@ -28,7 +28,6 @@
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.support.v7.cardview.R;
-import android.util.Log;
/**
* A rounded rectangle drawable which also includes a shadow around.
@@ -39,7 +38,7 @@
final static float SHADOW_MULTIPLIER = 1.5f;
- final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
+ final int mInsetShadow; // extra shadow to avoid gaps between card and shadow
/*
* This helper is set by CardView implementations.
@@ -90,16 +89,27 @@
float shadowSize, float maxShadowSize) {
mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
- mInsetShadow = resources.getDimension(R.dimen.cardview_compat_inset_shadow);
- setShadowSize(shadowSize, maxShadowSize);
+ mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(backgroundColor);
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
- mCornerShadowPaint.setDither(true);
- mCornerRadius = radius;
+ mCornerRadius = (int) (radius + .5f);
mCardBounds = new RectF();
mEdgeShadowPaint = new Paint(mCornerShadowPaint);
+ mEdgeShadowPaint.setAntiAlias(false);
+ setShadowSize(shadowSize, maxShadowSize);
+ }
+
+ /**
+ * Casts the value to an even integer.
+ */
+ private int toEven(float value) {
+ int i = (int) (value + .5f);
+ if (i % 2 == 1) {
+ return i - 1;
+ }
+ return i;
}
public void setAddPaddingForCorners(boolean addPaddingForCorners) {
@@ -124,11 +134,11 @@
if (shadowSize < 0 || maxShadowSize < 0) {
throw new IllegalArgumentException("invalid shadow size");
}
+ shadowSize = toEven(shadowSize);
+ maxShadowSize = toEven(maxShadowSize);
if (shadowSize > maxShadowSize) {
shadowSize = maxShadowSize;
if (!mPrintedShadowClipWarning) {
- Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
- + "{CardView#setMaxCardElevation}.");
mPrintedShadowClipWarning = true;
}
}
@@ -137,7 +147,7 @@
}
mRawShadowSize = shadowSize;
mRawMaxShadowSize = maxShadowSize;
- mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
+ mShadowSize = (int)(shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
mMaxShadowSize = maxShadowSize + mInsetShadow;
mDirty = true;
invalidateSelf();
@@ -180,10 +190,11 @@
@Override
public int getOpacity() {
- return PixelFormat.OPAQUE;
+ return PixelFormat.TRANSLUCENT;
}
void setCornerRadius(float radius) {
+ radius = (int) (radius + .5f);
if (mCornerRadius == radius) {
return;
}
@@ -270,7 +281,6 @@
// inner arc
mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
mCornerShadowPath.close();
-
float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
@@ -284,15 +294,16 @@
-mCornerRadius - mShadowSize,
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
+ mEdgeShadowPaint.setAntiAlias(false);
}
private void buildComponents(Rect bounds) {
// Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
// We could have different top-bottom offsets to avoid extra gap above but in that case
// center aligning Views inside the CardView would be problematic.
- final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
- mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
- bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
+ final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
+ mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
+ bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
buildShadowCorners();
}
@@ -332,7 +343,12 @@
return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
}
+ public void setColor(int color) {
+ mPaint.setColor(color);
+ invalidateSelf();
+ }
+
static interface RoundRectHelper {
void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
}
-}
\ No newline at end of file
+}
diff --git a/v7/cardview/src/android/support/v7/widget/CardView.java b/v7/cardview/src/android/support/v7/widget/CardView.java
index a3ed369..bc992b8 100644
--- a/v7/cardview/src/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/android/support/v7/widget/CardView.java
@@ -225,6 +225,16 @@
}
/**
+ * Updates the background color of the CardView
+ *
+ * @param color The new color to set for the card background
+ * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
+ */
+ public void setCardBackgroundColor(int color) {
+ IMPL.setBackgroundColor(this, color);
+ }
+
+ /**
* Returns the inner padding after the Card's left edge
*
* @return the inner padding after the Card's left edge
diff --git a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
index b5cb092..5369f86 100644
--- a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
+++ b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
@@ -31,7 +31,6 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import android.support.v7.gridlayout.R;
@@ -1491,7 +1490,11 @@
equivalent to the single-source shortest paths problem on a digraph, for
which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
*/
- private void solve(Arc[] arcs, int[] locations) {
+ private boolean solve(Arc[] arcs, int[] locations) {
+ return solve(arcs, locations, true);
+ }
+
+ private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) {
String axisName = horizontal ? "horizontal" : "vertical";
int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
boolean[] originalCulprits = null;
@@ -1509,10 +1512,14 @@
if (originalCulprits != null) {
logError(axisName, arcs, originalCulprits);
}
- return;
+ return true;
}
}
+ if (!modifyOnError) {
+ return false; // cannot solve with these constraints
+ }
+
boolean[] culprits = new boolean[arcs.length];
for (int i = 0; i < N; i++) {
for (int j = 0, length = arcs.length; j < length; j++) {
@@ -1536,6 +1543,7 @@
}
}
}
+ return true;
}
private void computeMargins(boolean leading) {
@@ -1575,8 +1583,8 @@
return trailingMargins;
}
- private void solve(int[] a) {
- solve(getArcs(), a);
+ private boolean solve(int[] a) {
+ return solve(getArcs(), a);
}
private boolean computeHasWeights() {
@@ -1618,28 +1626,18 @@
return deltas;
}
- private void shareOutDelta() {
- int totalDelta = 0;
- float totalWeight = 0;
+ private void shareOutDelta(int totalDelta, float totalWeight) {
+ Arrays.fill(deltas, 0);
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
float weight = spec.weight;
if (weight != 0) {
- int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
- totalDelta += delta;
- totalWeight += weight;
- }
- }
- for (int i = 0, N = getChildCount(); i < N; i++) {
- LayoutParams lp = getLayoutParams(getChildAt(i));
- Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- float weight = spec.weight;
- if (weight != 0) {
int delta = Math.round((weight * totalDelta / totalWeight));
deltas[i] = delta;
- // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
+ // the two adjustments below are to counter the above rounding and avoid
+ // off-by-ones at the end
totalDelta -= delta;
totalWeight -= weight;
}
@@ -1649,12 +1647,46 @@
private void solveAndDistributeSpace(int[] a) {
Arrays.fill(getDeltas(), 0);
solve(a);
- shareOutDelta();
- arcsValid = false;
- forwardLinksValid = false;
- backwardLinksValid = false;
- groupBoundsValid = false;
- solve(a);
+ int deltaMax = parentMin.value * getChildCount() + 1; //exclusive
+ if (deltaMax < 2) {
+ return; //don't have any delta to distribute
+ }
+ int deltaMin = 0; //inclusive
+
+ float totalWeight = calculateTotalWeight();
+
+ int validDelta = -1; //delta for which a solution exists
+ boolean validSolution = true;
+ // do a binary search to find the max delta that won't conflict with constraints
+ while(deltaMin < deltaMax) {
+ final int delta = (deltaMin + deltaMax) / 2;
+ invalidateValues();
+ shareOutDelta(delta, totalWeight);
+ validSolution = solve(getArcs(), a, false);
+ if (validSolution) {
+ validDelta = delta;
+ deltaMin = delta + 1;
+ } else {
+ deltaMax = delta;
+ }
+ }
+ if (validDelta > 0 && !validSolution) {
+ // last solution was not successful but we have a successful one. Use it.
+ invalidateValues();
+ shareOutDelta(validDelta, totalWeight);
+ solve(a);
+ }
+ }
+
+ private float calculateTotalWeight() {
+ float totalWeight = 0f;
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ View c = getChildAt(i);
+ LayoutParams lp = getLayoutParams(c);
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ totalWeight += spec.weight;
+ }
+ return totalWeight;
}
private void computeLocations(int[] a) {
diff --git a/v7/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index 9ad8c4a..a573954 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -48,7 +48,7 @@
# A helper sub-library that makes direct use of JellyBean MR2 APIs.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v7-mediarouter-jellybean-mr2
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 18
LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr2)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr1
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png
new file mode 100644
index 0000000..4a3396a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..440751e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png
new file mode 100644
index 0000000..20740bc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..c6f9002
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..9d7a4e4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..679bb98
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..2910903
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..b7e6db8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-hdpi/ic_media_pause.png
new file mode 100644
index 0000000..1d465a4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_play.png b/v7/mediarouter/res/drawable-hdpi/ic_media_play.png
new file mode 100644
index 0000000..2746d17
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..07603b5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..27e2d4a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..3afde73
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..f4ce1d5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..8dcb6e9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..5584a13
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_pause_dark.png
new file mode 100644
index 0000000..337cf0f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-hdpi/ic_pause_light.png
new file mode 100644
index 0000000..ec70ee1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png
new file mode 100644
index 0000000..b36cf49
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_play_light.png b/v7/mediarouter/res/drawable-hdpi/ic_play_light.png
new file mode 100644
index 0000000..2046149
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png
new file mode 100644
index 0000000..66efe42
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png
new file mode 100644
index 0000000..a1f4101
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index e215b96..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index a014e91..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index bb8bec1..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index aa1737e..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index 2c1434b..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index dbdce3e..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index 1101864..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index e8e9069..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index 8595158..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 14844d4..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index 1565a29..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index 9b8fe87..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png
new file mode 100644
index 0000000..9bee201
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..b60a666
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png
new file mode 100644
index 0000000..6b7885c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..0de509a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..6d94fee
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..af23356
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..8b238d4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..13c53e7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png
new file mode 100644
index 0000000..3e6b2a1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_play.png b/v7/mediarouter/res/drawable-mdpi/ic_media_play.png
new file mode 100644
index 0000000..7966bbc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..a2c68c5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..69287b8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..cf035c0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..247b72f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..eaed2f7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..77bd8f5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_pause_dark.png
new file mode 100644
index 0000000..0842337
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-mdpi/ic_pause_light.png
new file mode 100644
index 0000000..affca8b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png
new file mode 100644
index 0000000..33f08ae
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_play_light.png b/v7/mediarouter/res/drawable-mdpi/ic_play_light.png
new file mode 100644
index 0000000..4360c45
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png
new file mode 100644
index 0000000..28fbc90
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png
new file mode 100644
index 0000000..5625eed
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index 52e3a5a..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index 319c57e..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index f98c0a8..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index b74cdb5..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index a6a4bd0..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index 106fd3a..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index 2c141ab..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index 0b62d0b..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index 23442b0..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 42b329f..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index 58ff506..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index 25257f8..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png
new file mode 100644
index 0000000..c3649df
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..fcb2139
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png
new file mode 100644
index 0000000..ed30868
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..29f3779
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..e6c15a8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..b46e60e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..820f3a8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..aff2845
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png
new file mode 100644
index 0000000..6bd3d48
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png
new file mode 100644
index 0000000..ccfef18
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..14652ef
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..842dfd3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..036cb13
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..dcf61af
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..16ce334
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..0b83548
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_pause_dark.png
new file mode 100644
index 0000000..67abd61
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_pause_light.png
new file mode 100644
index 0000000..007b3ce
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_play_dark.png
new file mode 100644
index 0000000..6584e28
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_play_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_play_light.png
new file mode 100644
index 0000000..0975acf
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png
new file mode 100644
index 0000000..8622345
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png
new file mode 100644
index 0000000..b142237
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index 4119cff..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index b629a57..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index fe81128..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index 9b59eaf..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index 1a513c1..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index ff78803..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index 4c4b624..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index 60f8c4d..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index cdb2f30..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 97a10a3..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index a19a083..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index db30613..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png
new file mode 100644
index 0000000..4f2e24c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..6b74a0f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png
new file mode 100644
index 0000000..444078d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..e6099b5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..bd7bf92
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..63e108f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..f578d88
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..904c2b6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png
new file mode 100644
index 0000000..9a36b17
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_play.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_play.png
new file mode 100644
index 0000000..41f76bb
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..95f639a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..8164bde
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..0663290
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..c9efc4b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..398eb39
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..18d9150
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_dark.png
new file mode 100644
index 0000000..7077089
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_light.png
new file mode 100644
index 0000000..24305f1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_play_dark.png
new file mode 100644
index 0000000..f65aa13
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png
new file mode 100644
index 0000000..00af328
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png
new file mode 100644
index 0000000..7fef5f5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png
new file mode 100644
index 0000000..b5085b1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index 6fad4a6..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index 865617c..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index 44d98d5..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index b5b29b0..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index c807b50..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index 3fc7188..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index d54f44a..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index 092fe8c..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index 17c1d99..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 4fd5808..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index 906401e..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index d29e563..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_light.xml
deleted file mode 100644
index aaa6473..0000000
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_light.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<animation-list
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="false">
- <item android:drawable="@drawable/mr_ic_media_route_on_0_holo_light" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_light" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_2_holo_light" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_light" android:duration="500" />
-</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
similarity index 67%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
copy to v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
index 6b27536..2d593c3 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
@@ -17,8 +17,8 @@
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
- <item android:drawable="@drawable/mr_ic_media_route_on_0_holo_dark" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_2_holo_dark" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
+ <item android:drawable="@drawable/ic_media_route_on_0_mono_dark" android:duration="500" />
+ <item android:drawable="@drawable/ic_media_route_on_1_mono_dark" android:duration="500" />
+ <item android:drawable="@drawable/ic_media_route_on_2_mono_dark" android:duration="500" />
+ <item android:drawable="@drawable/ic_media_route_on_1_mono_dark" android:duration="500" />
</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
similarity index 67%
rename from v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
rename to v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
index 6b27536..d495d99 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
@@ -17,8 +17,8 @@
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
- <item android:drawable="@drawable/mr_ic_media_route_on_0_holo_dark" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_2_holo_dark" android:duration="500" />
- <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
+ <item android:drawable="@drawable/ic_cast_on_0_light" android:duration="500" />
+ <item android:drawable="@drawable/ic_cast_on_1_light" android:duration="500" />
+ <item android:drawable="@drawable/ic_cast_on_2_light" android:duration="500" />
+ <item android:drawable="@drawable/ic_cast_on_1_light" android:duration="500" />
</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_holo_dark.xml
deleted file mode 100644
index 6870591..0000000
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_dark.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_dark" />
- <item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_dark" />
- <item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_dark" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_dark" />
-</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
similarity index 79%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
index 0e4a065..6ae3b0b 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
@@ -16,10 +16,10 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
+ android:drawable="@drawable/ic_media_route_on_mono_dark" />
<item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
+ android:drawable="@drawable/mr_ic_media_route_connecting_mono_dark" />
<item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ android:drawable="@drawable/ic_media_route_off_mono_dark" />
+ <item android:drawable="@drawable/ic_media_route_disabled_mono_dark" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
similarity index 79%
rename from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
rename to v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
index 0e4a065..a8cf6c8 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
@@ -16,10 +16,10 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
+ android:drawable="@drawable/ic_cast_on_light" />
<item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
+ android:drawable="@drawable/mr_ic_media_route_connecting_mono_light" />
<item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ android:drawable="@drawable/ic_cast_off_light" />
+ <item android:drawable="@drawable/ic_cast_disabled_light" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_pause_dark.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_pause_dark.xml
index 0e4a065..f3dc712 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_pause_dark.xml
@@ -15,11 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
- <item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
- <item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ <item android:drawable="@drawable/ic_pause_dark" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_pause_light.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_pause_light.xml
index 0e4a065..9702be8 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_pause_light.xml
@@ -15,11 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
- <item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
- <item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ <item android:drawable="@drawable/ic_pause_light" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_play_dark.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_play_dark.xml
index 0e4a065..99e743c 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_play_dark.xml
@@ -15,11 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
- <item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
- <item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ <item android:drawable="@drawable/ic_play_dark" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_play_light.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_play_light.xml
index 0e4a065..d18cc12 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_play_light.xml
@@ -15,11 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
- <item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
- <item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ <item android:drawable="@drawable/ic_play_light" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
index 0e4a065..0fe662e 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
@@ -15,11 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
- <item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
- <item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ <item android:drawable="@drawable/ic_setting_dark" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_settings_light.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_settings_light.xml
index 0e4a065..a4614f6 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_settings_light.xml
@@ -15,11 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
- <item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
- <item android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
- <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+ <item android:drawable="@drawable/ic_setting_light" />
</selector>
diff --git a/v7/mediarouter/res/layout-v11/mr_media_route_controller_dialog.xml b/v7/mediarouter/res/layout-v11/mr_media_route_controller_dialog.xml
deleted file mode 100644
index b45fd15..0000000
--- a/v7/mediarouter/res/layout-v11/mr_media_route_controller_dialog.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:divider="?android:attr/dividerHorizontal"
- android:showDividers="middle">
- <!-- Optional volume slider section. -->
- <LinearLayout android:id="@+id/media_route_volume_layout"
- android:layout_width="fill_parent"
- android:layout_height="64dp"
- android:gravity="center_vertical"
- android:padding="8dp"
- android:visibility="gone">
- <ImageView android:layout_width="48dp"
- android:layout_height="48dp"
- android:src="@drawable/mr_ic_audio_vol"
- android:gravity="center"
- android:scaleType="center" />
- <SeekBar android:id="@+id/media_route_volume_slider"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp" />
- </LinearLayout>
-
- <!-- Optional content view section. -->
- <FrameLayout android:id="@+id/media_route_control_frame"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
-
- <!-- Disconnect button. -->
- <LinearLayout android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="?attr/buttonBarStyle">
- <Button android:id="@+id/media_route_disconnect_button"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- style="?attr/buttonBarButtonStyle"
- android:gravity="center"
- android:text="@string/mr_media_route_controller_disconnect" />
- </LinearLayout>
-</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_media_route_controller_dialog.xml b/v7/mediarouter/res/layout/mr_media_route_controller_dialog.xml
deleted file mode 100644
index a1b24bd..0000000
--- a/v7/mediarouter/res/layout/mr_media_route_controller_dialog.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:divider="?android:attr/dividerHorizontal"
- android:showDividers="middle">
- <!-- Optional volume slider section. -->
- <LinearLayout android:id="@+id/media_route_volume_layout"
- android:layout_width="fill_parent"
- android:layout_height="64dp"
- android:gravity="center_vertical"
- android:padding="8dp"
- android:visibility="gone">
- <ImageView android:layout_width="48dp"
- android:layout_height="48dp"
- android:src="@drawable/mr_ic_audio_vol"
- android:gravity="center"
- android:scaleType="center" />
- <SeekBar android:id="@+id/media_route_volume_slider"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp" />
- </LinearLayout>
-
- <!-- Optional content view section. -->
- <FrameLayout android:id="@+id/media_route_control_frame"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
-
- <!-- Disconnect button. -->
- <LinearLayout android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="?attr/buttonBarStyle">
- <View android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="0.25" />
- <Button android:id="@+id/media_route_disconnect_button"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="fill_parent"
- style="?attr/buttonBarButtonStyle"
- android:gravity="center"
- android:text="@string/mr_media_route_controller_disconnect" />
- <View android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="0.25" />
- </LinearLayout>
-</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml b/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml
new file mode 100644
index 0000000..3b12b24
--- /dev/null
+++ b/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout android:id="@+id/title_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <TextView android:id="@+id/route_name"
+ android:layout_width="0dp"
+ android:layout_height="72dp"
+ android:layout_weight="1"
+ android:layout_marginLeft="24dip"
+ android:layout_marginRight="24dip"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary" />
+ <ImageButton android:id="@+id/settings"
+ android:layout_width="48dip"
+ android:layout_height="48dip"
+ android:padding="12dip"
+ android:layout_marginTop="12dip"
+ android:layout_marginBottom="12dip"
+ android:layout_marginRight="12dip"
+ android:contentDescription="@string/mr_media_route_controller_settings_description"
+ android:src="?attr/mediaRouteSettingsDrawable"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:visibility="gone" />
+ </LinearLayout>
+ <FrameLayout android:id="@+id/media_route_control_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+ <RelativeLayout android:id="@+id/default_control_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/colorPrimary" >
+ <ImageView android:id="@+id/art"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxHeight="@dimen/mr_media_route_controller_art_max_height"
+ android:adjustViewBounds="true"
+ android:scaleType="centerCrop"/>
+ <ImageButton android:id="@+id/play_pause"
+ android:layout_width="48dip"
+ android:layout_height="48dip"
+ android:padding="12dip"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:layout_alignParentRight="true"
+ android:layout_below="@id/art"
+ android:contentDescription="@string/mr_media_route_controller_play"
+ android:background="?attr/selectableItemBackgroundBorderless"/>
+ <LinearLayout android:id="@+id/text_wrapper"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:minHeight="64dip"
+ android:layout_marginLeft="24dip"
+ android:gravity="center_vertical"
+ android:layout_toLeftOf="@id/play_pause"
+ android:layout_below="@id/art"
+ android:layout_alignParentLeft="true" >
+ <TextView android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="16sp"
+ android:textStyle="bold"
+ android:singleLine="true" />
+ <TextView android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp"
+ android:singleLine="true" />
+ </LinearLayout>
+ </RelativeLayout>
+ </FrameLayout>
+ <LinearLayout android:id="@+id/buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <Button android:id="@+id/disconnect"
+ android:layout_width="0dp"
+ android:layout_height="48dp"
+ android:gravity="center"
+ android:layout_weight="1"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:text="@string/mr_media_route_controller_disconnect"
+ android:visibility="gone" />
+ <Button android:id="@+id/stop"
+ android:layout_width="0dp"
+ android:layout_height="48dp"
+ android:gravity="center"
+ android:layout_weight="1"
+ android:textColor="?attr/colorAccent"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:text="@string/mr_media_route_controller_stop" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/v7/mediarouter/res/values-af/strings.xml b/v7/mediarouter/res/values-af/strings.xml
index e6b8027..0dcfa86 100644
--- a/v7/mediarouter/res/values-af/strings.xml
+++ b/v7/mediarouter/res/values-af/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Koppel aan toestel"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Soek tans vir toestelle…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ontkoppel"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Hou op uitsaai"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Roete-instellings"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Speel"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Laat wag"</string>
</resources>
diff --git a/v7/mediarouter/res/values-am/strings.xml b/v7/mediarouter/res/values-am/strings.xml
index 5bbcea8..5d061c9 100644
--- a/v7/mediarouter/res/values-am/strings.xml
+++ b/v7/mediarouter/res/values-am/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ከመሳሪያ ጋር ያገናኙ"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"መሳሪያዎችን በመፈለግ ላይ…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ግንኙነት አቋርጥ"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"መውሰድ አቁም"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"የመንገድ ቅንብሮች"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"አጫውት"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ለአፍታ አቁም"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ar/strings.xml b/v7/mediarouter/res/values-ar/strings.xml
index dd04c47..ac0fb5d 100644
--- a/v7/mediarouter/res/values-ar/strings.xml
+++ b/v7/mediarouter/res/values-ar/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"الاتصال بجهاز"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"جارٍ البحث عن الأجهزة…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"قطع الاتصال"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"إيقاف الإرسال"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"إعدادات المسار"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"تشغيل"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"إيقاف مؤقت"</string>
</resources>
diff --git a/v7/mediarouter/res/values-bg/strings.xml b/v7/mediarouter/res/values-bg/strings.xml
index d478516..0918332 100644
--- a/v7/mediarouter/res/values-bg/strings.xml
+++ b/v7/mediarouter/res/values-bg/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Свързване с устройство"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Търсят се устройства…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Прекратяване на връзката"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Спиране на предаването"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Настройки за маршрута"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Пускане"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Поставяне на пауза"</string>
</resources>
diff --git a/v7/mediarouter/res/values-bn-rBD/strings.xml b/v7/mediarouter/res/values-bn-rBD/strings.xml
index f1d1499..de862e5 100644
--- a/v7/mediarouter/res/values-bn-rBD/strings.xml
+++ b/v7/mediarouter/res/values-bn-rBD/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ডিভাইসে সংযোগ করুন"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ডিভাইসগুলি অনুসন্ধান করা হচ্ছে…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"সংযোগ বিচ্ছিন্ন করুন"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"কাস্ট করা বন্ধ করুন"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"সেটিংস রুট করুন"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"চালান"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"বিরাম দিন"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ca/strings.xml b/v7/mediarouter/res/values-ca/strings.xml
index 5c956a3..eac6632 100644
--- a/v7/mediarouter/res/values-ca/strings.xml
+++ b/v7/mediarouter/res/values-ca/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connecta al dispositiu"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"S\'estan cercant dispositius…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconnecta"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Atura l\'emissió"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configuració de la ruta"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reprodueix"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Posa en pausa"</string>
</resources>
diff --git a/v7/mediarouter/res/values-cs/strings.xml b/v7/mediarouter/res/values-cs/strings.xml
index 014ac3c..111c02a 100644
--- a/v7/mediarouter/res/values-cs/strings.xml
+++ b/v7/mediarouter/res/values-cs/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Připojení k zařízení"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Vyhledávání zařízení…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Odpojit"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Ukončit odesílání"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Nastavení trasy"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Přehrát"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pozastavit"</string>
</resources>
diff --git a/v7/mediarouter/res/values-da/strings.xml b/v7/mediarouter/res/values-da/strings.xml
index ca47a62..3b4fbf6 100644
--- a/v7/mediarouter/res/values-da/strings.xml
+++ b/v7/mediarouter/res/values-da/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Opret forbindelse til enheden"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Søger efter enheder..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Afbryd forbindelsen"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stop med at caste"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ruteindstillinger"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Afspil"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Sæt på pause"</string>
</resources>
diff --git a/v7/mediarouter/res/values-de/strings.xml b/v7/mediarouter/res/values-de/strings.xml
index 098d4c0..5b8e494 100644
--- a/v7/mediarouter/res/values-de/strings.xml
+++ b/v7/mediarouter/res/values-de/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Mit Gerät verbinden"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Geräte werden gesucht…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Verbindung aufheben"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Übertragung stoppen"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Routingeinstellungen"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Wiedergabe"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
</resources>
diff --git a/v7/mediarouter/res/values-el/strings.xml b/v7/mediarouter/res/values-el/strings.xml
index f844adb..3640111 100644
--- a/v7/mediarouter/res/values-el/strings.xml
+++ b/v7/mediarouter/res/values-el/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Σύνδεση με τη συσκευή"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Αναζήτηση συσκευών…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Αποσύνδεση"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Διακοπή μετάδοσης"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ρυθμίσεις διαδρομής"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Αναπαραγωγή"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Παύση"</string>
</resources>
diff --git a/v7/mediarouter/res/values-en-rGB/strings.xml b/v7/mediarouter/res/values-en-rGB/strings.xml
index c16c294..f5a8531 100644
--- a/v7/mediarouter/res/values-en-rGB/strings.xml
+++ b/v7/mediarouter/res/values-en-rGB/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connect to device"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Searching for devices…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnect"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stop casting"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Route settings"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Play"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
</resources>
diff --git a/v7/mediarouter/res/values-en-rIN/strings.xml b/v7/mediarouter/res/values-en-rIN/strings.xml
index c16c294..f5a8531 100644
--- a/v7/mediarouter/res/values-en-rIN/strings.xml
+++ b/v7/mediarouter/res/values-en-rIN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connect to device"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Searching for devices…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnect"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stop casting"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Route settings"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Play"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
</resources>
diff --git a/v7/mediarouter/res/values-es-rUS/strings.xml b/v7/mediarouter/res/values-es-rUS/strings.xml
index 008c966..e1cf915 100644
--- a/v7/mediarouter/res/values-es-rUS/strings.xml
+++ b/v7/mediarouter/res/values-es-rUS/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar al dispositivo"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Detener transmisión"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configuración de ruta"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproducir"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausar"</string>
</resources>
diff --git a/v7/mediarouter/res/values-es/strings.xml b/v7/mediarouter/res/values-es/strings.xml
index d413fe1..0f2a8ea 100644
--- a/v7/mediarouter/res/values-es/strings.xml
+++ b/v7/mediarouter/res/values-es/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar a dispositivo"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Dejar de enviar contenido"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ajustes de ruta"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproducir"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
</resources>
diff --git a/v7/mediarouter/res/values-et-rEE/strings.xml b/v7/mediarouter/res/values-et-rEE/strings.xml
index 6870211..ebc63fd 100644
--- a/v7/mediarouter/res/values-et-rEE/strings.xml
+++ b/v7/mediarouter/res/values-et-rEE/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Seadmega ühendamine"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Seadmete otsimine …"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Katkesta ühendus"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Lõpeta ülekanne"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Marsruudi seaded"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Esitamine"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Peatamine"</string>
</resources>
diff --git a/v7/mediarouter/res/values-eu-rES/strings.xml b/v7/mediarouter/res/values-eu-rES/strings.xml
index fb36265..d177a55 100644
--- a/v7/mediarouter/res/values-eu-rES/strings.xml
+++ b/v7/mediarouter/res/values-eu-rES/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Konektatu gailura"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Gailuak bilatzen…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Deskonektatu"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Utzi igortzeari"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ibilbidearen ezarpenak"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Erreproduzitu"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausatu"</string>
</resources>
diff --git a/v7/mediarouter/res/values-fa/strings.xml b/v7/mediarouter/res/values-fa/strings.xml
index cb713a8..e094982 100644
--- a/v7/mediarouter/res/values-fa/strings.xml
+++ b/v7/mediarouter/res/values-fa/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"برقراری ارتباط با دستگاه"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"در حال جستجو برای دستگاهها..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"قطع ارتباط"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"توقف فرستادن"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"تنظیمات مسیر"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"پخش"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"توقف موقت"</string>
</resources>
diff --git a/v7/mediarouter/res/values-fi/strings.xml b/v7/mediarouter/res/values-fi/strings.xml
index 86a79b1..a21dc91 100644
--- a/v7/mediarouter/res/values-fi/strings.xml
+++ b/v7/mediarouter/res/values-fi/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Yhdistä laitteeseen"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Etsitään laitteita…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Katkaise yhteys"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Lopeta suoratoisto"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Reitin asetukset"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Toista"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Keskeytä"</string>
</resources>
diff --git a/v7/mediarouter/res/values-fr-rCA/strings.xml b/v7/mediarouter/res/values-fr-rCA/strings.xml
index e1d6072..0655526 100644
--- a/v7/mediarouter/res/values-fr-rCA/strings.xml
+++ b/v7/mediarouter/res/values-fr-rCA/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connexion au périphérique"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Recherche d\'appareils en cours…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Déconnecter"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Arrêter la diffusion"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Paramètres de l\'itinéraire"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Lecture"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Suspendre"</string>
</resources>
diff --git a/v7/mediarouter/res/values-fr/strings.xml b/v7/mediarouter/res/values-fr/strings.xml
index 59bfe66..9fce08a 100644
--- a/v7/mediarouter/res/values-fr/strings.xml
+++ b/v7/mediarouter/res/values-fr/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connecter à l\'appareil"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Recherche d\'appareils en cours…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Déconnecter"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Arrêter la diffusion"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Paramètres de l\'itinéraire"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Lecture"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
</resources>
diff --git a/v7/mediarouter/res/values-gl-rES/strings.xml b/v7/mediarouter/res/values-gl-rES/strings.xml
index 51f980c..d1d73f9 100644
--- a/v7/mediarouter/res/values-gl-rES/strings.xml
+++ b/v7/mediarouter/res/values-gl-rES/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar co dispositivo"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Parar de emitir"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configuración da ruta"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproduce"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
</resources>
diff --git a/v7/mediarouter/res/values-hi/strings.xml b/v7/mediarouter/res/values-hi/strings.xml
index cce275e..6d100ea 100644
--- a/v7/mediarouter/res/values-hi/strings.xml
+++ b/v7/mediarouter/res/values-hi/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"डिवाइस से कनेक्ट करें"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"डिवाइस की खोज हो रही है…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"डिस्कनेक्ट करें"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"कास्ट करना बंद करें"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"मार्ग सेटिंग"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"चलाएं"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"रोकें"</string>
</resources>
diff --git a/v7/mediarouter/res/values-hr/strings.xml b/v7/mediarouter/res/values-hr/strings.xml
index 91f8cd7..74e9270 100644
--- a/v7/mediarouter/res/values-hr/strings.xml
+++ b/v7/mediarouter/res/values-hr/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Povezivanje s uređajem"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Traženje uređaja…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Prekini vezu"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Zaustavi emitiranje"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Postavke rute"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reprodukcija"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pauziraj"</string>
</resources>
diff --git a/v7/mediarouter/res/values-hu/strings.xml b/v7/mediarouter/res/values-hu/strings.xml
index bca65f4..efbc193 100644
--- a/v7/mediarouter/res/values-hu/strings.xml
+++ b/v7/mediarouter/res/values-hu/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Csatlakozás adott eszközhöz"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Eszközkeresés…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Leválasztás"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Átküldés leállítása"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Útvonal-beállítások"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Indítás"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Szüneteltetés"</string>
</resources>
diff --git a/v7/mediarouter/res/values-hy-rAM/strings.xml b/v7/mediarouter/res/values-hy-rAM/strings.xml
index 17058ab..faa6020 100644
--- a/v7/mediarouter/res/values-hy-rAM/strings.xml
+++ b/v7/mediarouter/res/values-hy-rAM/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Միանալ սարքին"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Որոնվում են սարքեր..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Անջատել"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Դադարեցնել հեռարձակումը"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ֆայլերի փոխանցման կարգավորումներ"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Նվագարկել"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Դադար"</string>
</resources>
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
index 578f696..e3123c1 100644
--- a/v7/mediarouter/res/values-in/strings.xml
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Sambungkan ke perangkat"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Menelusuri perangkat…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Putuskan sambungan"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Hentikan transmisi"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Setelan rute"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Putar"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Jeda"</string>
</resources>
diff --git a/v7/mediarouter/res/values-is-rIS/strings.xml b/v7/mediarouter/res/values-is-rIS/strings.xml
index 436e7d2..262e4e9 100644
--- a/v7/mediarouter/res/values-is-rIS/strings.xml
+++ b/v7/mediarouter/res/values-is-rIS/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Tengjast tæki"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Leitar að tækjum…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Aftengja"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stöðva útsendingu"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Leiðarstillingar"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Spila"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Hlé"</string>
</resources>
diff --git a/v7/mediarouter/res/values-it/strings.xml b/v7/mediarouter/res/values-it/strings.xml
index 973627e..bedd617 100644
--- a/v7/mediarouter/res/values-it/strings.xml
+++ b/v7/mediarouter/res/values-it/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connetti al dispositivo"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Ricerca di dispositivi…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnetti"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Interrompi trasmissione"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Impostazioni percorso"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Riproduci"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
</resources>
diff --git a/v7/mediarouter/res/values-iw/strings.xml b/v7/mediarouter/res/values-iw/strings.xml
index 233acbc..12d17b9 100644
--- a/v7/mediarouter/res/values-iw/strings.xml
+++ b/v7/mediarouter/res/values-iw/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"התחבר למכשיר"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"מחפש מכשירים…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"התנתק"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"עצור העברה"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"הגדרות נתיב"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"הפעל"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"השהה"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ja/strings.xml b/v7/mediarouter/res/values-ja/strings.xml
index a89c22d..e97a65a 100644
--- a/v7/mediarouter/res/values-ja/strings.xml
+++ b/v7/mediarouter/res/values-ja/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"端末に接続"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"端末を検索しています…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"接続を解除"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"キャストを停止"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ルーティング設定"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"再生"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"一時停止"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ka-rGE/strings.xml b/v7/mediarouter/res/values-ka-rGE/strings.xml
index f43e31e..758fe73 100644
--- a/v7/mediarouter/res/values-ka-rGE/strings.xml
+++ b/v7/mediarouter/res/values-ka-rGE/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"მოწყობილობასთან დაკავშირება"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"მოწყობილობების ძიება…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"კავშირის გაწყვეტა"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ტრანსლაციის შეჩერება"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"მარშრუტის პარამეტრები"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"დაკვრა"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"პაუზა"</string>
</resources>
diff --git a/v7/mediarouter/res/values-kk-rKZ/strings.xml b/v7/mediarouter/res/values-kk-rKZ/strings.xml
index 6344d71..c549a8c 100644
--- a/v7/mediarouter/res/values-kk-rKZ/strings.xml
+++ b/v7/mediarouter/res/values-kk-rKZ/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Құрылғыға жалғау"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Құрылғыларды іздеуде…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ажырату"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Трансляциялауды тоқтату"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Жол параметрлері"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Ойнату"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Кідірту"</string>
</resources>
diff --git a/v7/mediarouter/res/values-km-rKH/strings.xml b/v7/mediarouter/res/values-km-rKH/strings.xml
index d7cc49f..b3e53c5 100644
--- a/v7/mediarouter/res/values-km-rKH/strings.xml
+++ b/v7/mediarouter/res/values-km-rKH/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ភ្ជាប់ឧបករណ៍"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"កំពុងស្វែងរកឧបករណ៍..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ផ្ដាច់"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"បញ្ឈប់ការខាស"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ការកំណត់ផ្លូវ"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ចាក់"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ផ្អាក"</string>
</resources>
diff --git a/v7/mediarouter/res/values-kn-rIN/strings.xml b/v7/mediarouter/res/values-kn-rIN/strings.xml
index 35a95af..36c3aaa 100644
--- a/v7/mediarouter/res/values-kn-rIN/strings.xml
+++ b/v7/mediarouter/res/values-kn-rIN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ಸಾಧನಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಿ"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ಸಾಧನಗಳನ್ನು ಹುಡುಕಲಾಗುತ್ತಿದೆ…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ಬಿತ್ತರಿಸುವಿಕೆ ನಿಲ್ಲಿಸು"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ಮಾರ್ಗ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ಪ್ಲೇ ಮಾಡು"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ವಿರಾಮ"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ko/strings.xml b/v7/mediarouter/res/values-ko/strings.xml
index 8f1faa6..d165e52 100644
--- a/v7/mediarouter/res/values-ko/strings.xml
+++ b/v7/mediarouter/res/values-ko/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"기기에 연결"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"기기 검색 중…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"연결 해제"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"전송 중지"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"경로 설정"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"재생"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"일시중지"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ky-rKG/strings.xml b/v7/mediarouter/res/values-ky-rKG/strings.xml
index 37ed974..1f7aba0 100644
--- a/v7/mediarouter/res/values-ky-rKG/strings.xml
+++ b/v7/mediarouter/res/values-ky-rKG/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Түзмөккө туташуу"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Түзмөктөр изделүүдө..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ажыратуу"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Тышк экранга чыгарну токтотуу"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Багыт жөндөөлөрү"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Ойнотуу"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Тындыруу"</string>
</resources>
diff --git a/v7/mediarouter/res/values-lo-rLA/strings.xml b/v7/mediarouter/res/values-lo-rLA/strings.xml
index 503ac98..6d61f7d 100644
--- a/v7/mediarouter/res/values-lo-rLA/strings.xml
+++ b/v7/mediarouter/res/values-lo-rLA/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ເຊື່ອມຕໍ່ຫາອຸປະກອນ"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ກຳລັງຊອກຫາອຸປະກອນ..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ຕັດການເຊື່ອມຕໍ່"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ຢຸດການສົ່ງສັນຍານ"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ການຕັ້ງຄ່າເສັ້ນທາງ"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ຫຼິ້ນ"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ຢຸດຊົ່ວຄາວ"</string>
</resources>
diff --git a/v7/mediarouter/res/values-lt/strings.xml b/v7/mediarouter/res/values-lt/strings.xml
index 6bc430b..2315618 100644
--- a/v7/mediarouter/res/values-lt/strings.xml
+++ b/v7/mediarouter/res/values-lt/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Prijungimas prie įrenginio"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Ieškoma įrenginių…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Atjungti"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Sustabdyti perdavimą"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Maršruto nustatymai"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Leisti"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pristabdyti"</string>
</resources>
diff --git a/v7/mediarouter/res/values-lv/strings.xml b/v7/mediarouter/res/values-lv/strings.xml
index a32e383..93e45de 100644
--- a/v7/mediarouter/res/values-lv/strings.xml
+++ b/v7/mediarouter/res/values-lv/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Savienojuma izveide ar ierīci"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Notiek ierīču meklēšana..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Atvienot"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Pārtraukt apraidi"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Maršruta iestatījumi"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Atskaņot"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Apturēt"</string>
</resources>
diff --git a/v7/mediarouter/res/values-mk-rMK/strings.xml b/v7/mediarouter/res/values-mk-rMK/strings.xml
index 310f15b..9b3f875 100644
--- a/v7/mediarouter/res/values-mk-rMK/strings.xml
+++ b/v7/mediarouter/res/values-mk-rMK/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Поврзи се со уредот"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Се пребаруваат уреди..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Исклучи се"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Запри префрлување"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Поставки на маршрутата"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Репродуцирај"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Пауза"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ml-rIN/strings.xml b/v7/mediarouter/res/values-ml-rIN/strings.xml
index 84bedd2..64c74b1 100644
--- a/v7/mediarouter/res/values-ml-rIN/strings.xml
+++ b/v7/mediarouter/res/values-ml-rIN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ഉപകരണത്തിലേക്ക് കണക്റ്റുചെയ്യുക"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ഉപകരണങ്ങൾക്കായി തിരയുന്നു…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"വിച്ഛേദിക്കുക"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"കാസ്റ്റുചെയ്യൽ നിർത്തുക"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"റൂട്ട് ക്രമീകരണം"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"പ്ലേ ചെയ്യുക"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"താൽക്കാലികമായി നിർത്തുക"</string>
</resources>
diff --git a/v7/mediarouter/res/values-mn-rMN/strings.xml b/v7/mediarouter/res/values-mn-rMN/strings.xml
index 6fbeef3..2074767 100644
--- a/v7/mediarouter/res/values-mn-rMN/strings.xml
+++ b/v7/mediarouter/res/values-mn-rMN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Төхөөрөмжтэй холбох"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Төхөөрөмжүүдийг хайж байна…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Салгах"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Нэвтрүүлэхийг зогсоох"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Маршрут тохиргоо"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Тоглуулах"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Түр зогсоох"</string>
</resources>
diff --git a/v7/mediarouter/res/values-mr-rIN/strings.xml b/v7/mediarouter/res/values-mr-rIN/strings.xml
index 62b81fe..bd020a7 100644
--- a/v7/mediarouter/res/values-mr-rIN/strings.xml
+++ b/v7/mediarouter/res/values-mr-rIN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"डिव्हाइसला कनेक्ट करा"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"डिव्हाइसेस शोधत आहे…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"डिस्कनेक्ट करा"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"कास्ट करणे थांबवा"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"मार्ग सेटिंग्ज"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"प्ले करा"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"विराम द्या"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ms-rMY/strings.xml b/v7/mediarouter/res/values-ms-rMY/strings.xml
index 3e82660..05e9ffa 100644
--- a/v7/mediarouter/res/values-ms-rMY/strings.xml
+++ b/v7/mediarouter/res/values-ms-rMY/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Sambung kepada peranti"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Mencari peranti..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Putuskan sambungan"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Berhenti menghantar"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Tetapan laluan"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Main"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Jeda"</string>
</resources>
diff --git a/v7/mediarouter/res/values-my-rMM/strings.xml b/v7/mediarouter/res/values-my-rMM/strings.xml
index f789bf0..20bfd8d 100644
--- a/v7/mediarouter/res/values-my-rMM/strings.xml
+++ b/v7/mediarouter/res/values-my-rMM/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"စက်တစ်ခုကို ချိတ်ဆက်ပါ"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"စက်ပစ္စည်းများကို ရှာဖွေနေပါသည်"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ချိတ်ဆက်ခြင်းရပ်တန့်ရန်"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ပုံစံသွင်းမှု ရပ်ရန်"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"လမ်းကြောင်း အပြင်အဆင်များ"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ဖွင့်ရန်"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ခဏရပ်ရန်"</string>
</resources>
diff --git a/v7/mediarouter/res/values-nb/strings.xml b/v7/mediarouter/res/values-nb/strings.xml
index f7955a5..5ee8ec8 100644
--- a/v7/mediarouter/res/values-nb/strings.xml
+++ b/v7/mediarouter/res/values-nb/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Koble til enheten"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Søker etter enheter …"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Koble fra"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stopp castingen"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ruteinnstillinger"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Spill av"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Sett på pause"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ne-rNP/strings.xml b/v7/mediarouter/res/values-ne-rNP/strings.xml
index 0d521b9..aadcbcf 100644
--- a/v7/mediarouter/res/values-ne-rNP/strings.xml
+++ b/v7/mediarouter/res/values-ne-rNP/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"उपकरणसँग जडान गर्नुहोस्"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"उपकरणहरूका लागि खोजी गरिँदै..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"विच्छेदन गर्नुहोस्"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"कास्टिंग रोक्नुहोस्"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"मार्ग सेटिङ"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"बजाउनुहोस्"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"रोक्नुहोस्"</string>
</resources>
diff --git a/v7/mediarouter/res/values-nl/strings.xml b/v7/mediarouter/res/values-nl/strings.xml
index 934ff3b..fcfac4d 100644
--- a/v7/mediarouter/res/values-nl/strings.xml
+++ b/v7/mediarouter/res/values-nl/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Verbinding maken met apparaat"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Zoeken naar apparaten…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Verbinding verbreken"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Casten stoppen"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Route-instellingen"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Afspelen"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Onderbreken"</string>
</resources>
diff --git a/v7/mediarouter/res/values-pl/strings.xml b/v7/mediarouter/res/values-pl/strings.xml
index 21dc070..34fea86 100644
--- a/v7/mediarouter/res/values-pl/strings.xml
+++ b/v7/mediarouter/res/values-pl/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Połącz z urządzeniem"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Szukam urządzeń…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Rozłącz"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Zakończ przesyłanie"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ustawienia trasy"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Odtwórz"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Wstrzymaj"</string>
</resources>
diff --git a/v7/mediarouter/res/values-pt-rPT/strings.xml b/v7/mediarouter/res/values-pt-rPT/strings.xml
index c591eb3..1e1dbbb 100644
--- a/v7/mediarouter/res/values-pt-rPT/strings.xml
+++ b/v7/mediarouter/res/values-pt-rPT/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Ligar ao dispositivo"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"A pesquisar dispositivos…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desassociar"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Parar a transmissão"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Definições de trajeto"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproduzir"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Colocar em pausa"</string>
</resources>
diff --git a/v7/mediarouter/res/values-pt/strings.xml b/v7/mediarouter/res/values-pt/strings.xml
index 31f2f27..67648a7 100644
--- a/v7/mediarouter/res/values-pt/strings.xml
+++ b/v7/mediarouter/res/values-pt/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar ao dispositivo"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Procurando dispositivos…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Interromper transmissão"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configurações de rota"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproduzir"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausar"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ro/strings.xml b/v7/mediarouter/res/values-ro/strings.xml
index e34f7ec..d738bab 100644
--- a/v7/mediarouter/res/values-ro/strings.xml
+++ b/v7/mediarouter/res/values-ro/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectați-vă la dispozitiv"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Se caută dispozitive..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Deconectați-vă"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Nu mai proiectați"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Setări pentru traseu"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Redați"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Întrerupeți"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ru/strings.xml b/v7/mediarouter/res/values-ru/strings.xml
index 2d4fae4..dfa836c 100644
--- a/v7/mediarouter/res/values-ru/strings.xml
+++ b/v7/mediarouter/res/values-ru/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Подключение к устройству"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Поиск устройств…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Отключить"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Остановить трансляцию"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Настройки передачи файлов"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Воспроизвести."</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Приостановить."</string>
</resources>
diff --git a/v7/mediarouter/res/values-si-rLK/strings.xml b/v7/mediarouter/res/values-si-rLK/strings.xml
index c22f92f..1ac7319 100644
--- a/v7/mediarouter/res/values-si-rLK/strings.xml
+++ b/v7/mediarouter/res/values-si-rLK/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"උපාංගයට සම්බන්ධ වන්න"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"උපාංග සඳහා සොයමින්…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"විසන්ධි කරන්න"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"කාස්ට් කිරීම නවත්වන්න"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ගමන් මගේ සැකසීම්"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ධාවනය කරන්න"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"විරාමය"</string>
</resources>
diff --git a/v7/mediarouter/res/values-sk/strings.xml b/v7/mediarouter/res/values-sk/strings.xml
index 76078f7..3156edf 100644
--- a/v7/mediarouter/res/values-sk/strings.xml
+++ b/v7/mediarouter/res/values-sk/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Pripojenie k zariadeniu"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Prebieha vyhľadávanie zariadení…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Odpojiť"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Zastaviť prenášanie"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Nastavenia trasy"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Prehrať"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pozastaviť"</string>
</resources>
diff --git a/v7/mediarouter/res/values-sl/strings.xml b/v7/mediarouter/res/values-sl/strings.xml
index d642459..3de14aa 100644
--- a/v7/mediarouter/res/values-sl/strings.xml
+++ b/v7/mediarouter/res/values-sl/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Povezovanje z napravo"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Iskanje naprav …"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Prekini povezavo"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Ustavi predvajanje"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Nastavitve poti"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Predvajaj"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Zaustavi"</string>
</resources>
diff --git a/v7/mediarouter/res/values-sr/strings.xml b/v7/mediarouter/res/values-sr/strings.xml
index 6f26da9..de10685 100644
--- a/v7/mediarouter/res/values-sr/strings.xml
+++ b/v7/mediarouter/res/values-sr/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Повежите са уређајем"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Претраживање уређаја…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Прекини везу"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Заустави пребацивање"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Подешавања путање"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Пусти"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Паузирај"</string>
</resources>
diff --git a/v7/mediarouter/res/values-sv/strings.xml b/v7/mediarouter/res/values-sv/strings.xml
index 750e68a..3ac428a 100644
--- a/v7/mediarouter/res/values-sv/strings.xml
+++ b/v7/mediarouter/res/values-sv/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Anslut till enhet"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Söker efter enheter ..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Koppla från"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Sluta casta"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Inställningar för omdirigering"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Spela upp"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
</resources>
diff --git a/v7/mediarouter/res/values-sw/strings.xml b/v7/mediarouter/res/values-sw/strings.xml
index 73df654..00ce337 100644
--- a/v7/mediarouter/res/values-sw/strings.xml
+++ b/v7/mediarouter/res/values-sw/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Unganisha kwenye kifaa"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Inatafuta vifaa..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Tenganisha"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Acha kutuma"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Mipangilio ya njia"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Google Play"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Sitisha"</string>
</resources>
diff --git a/v7/mediarouter/res/values-sw600dp/dimens.xml b/v7/mediarouter/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..5b29058
--- /dev/null
+++ b/v7/mediarouter/res/values-sw600dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <dimen name="mr_media_route_controller_art_max_height">480dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values-ta-rIN/strings.xml b/v7/mediarouter/res/values-ta-rIN/strings.xml
index 14ab0e6..f92c432 100644
--- a/v7/mediarouter/res/values-ta-rIN/strings.xml
+++ b/v7/mediarouter/res/values-ta-rIN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"சாதனத்துடன் இணைக்கவும்"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"சாதனங்களைத் தேடுகிறது..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"துண்டி"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"அனுப்புவதை நிறுத்து"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"வழி அமைப்புகள்"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"இயக்கு"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"இடைநிறுத்து"</string>
</resources>
diff --git a/v7/mediarouter/res/values-te-rIN/strings.xml b/v7/mediarouter/res/values-te-rIN/strings.xml
index 2733225..0913420 100644
--- a/v7/mediarouter/res/values-te-rIN/strings.xml
+++ b/v7/mediarouter/res/values-te-rIN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"పరికరానికి కనెక్ట్ చేయండి"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"పరికరాల కోసం శోధిస్తోంది…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"డిస్కనెక్ట్ చేయి"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ప్రసారాన్ని ఆపివేయి"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"మార్గ సెట్టింగ్లు"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ప్లే చేయి"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"పాజ్ చేయి"</string>
</resources>
diff --git a/v7/mediarouter/res/values-th/strings.xml b/v7/mediarouter/res/values-th/strings.xml
index 236f4e9..31fc9c7 100644
--- a/v7/mediarouter/res/values-th/strings.xml
+++ b/v7/mediarouter/res/values-th/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"เชื่อมต่อกับอุปกรณ์"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"กำลังค้นหาอุปกรณ์…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ยกเลิกการเชื่อมต่อ"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"หยุดการส่ง"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"การตั้งค่าเส้นทาง"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"เล่น"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"หยุดชั่วคราว"</string>
</resources>
diff --git a/v7/mediarouter/res/values-tl/strings.xml b/v7/mediarouter/res/values-tl/strings.xml
index edc1236..d4896b5 100644
--- a/v7/mediarouter/res/values-tl/strings.xml
+++ b/v7/mediarouter/res/values-tl/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Kumonekta sa device"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Naghahanap ng mga device…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Idiskonekta"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Itigil ang pagca-cast"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Mga setting ng ruta"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"I-play"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"I-pause"</string>
</resources>
diff --git a/v7/mediarouter/res/values-tr/strings.xml b/v7/mediarouter/res/values-tr/strings.xml
index 3106251..05344ff 100644
--- a/v7/mediarouter/res/values-tr/strings.xml
+++ b/v7/mediarouter/res/values-tr/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Cihaza bağlanın"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Cihaz arayın…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Bağlantıyı kes"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Yayını durdur"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Rota ayarları"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Oynat"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Duraklat"</string>
</resources>
diff --git a/v7/mediarouter/res/values-uk/strings.xml b/v7/mediarouter/res/values-uk/strings.xml
index 110e445..b445b9c 100644
--- a/v7/mediarouter/res/values-uk/strings.xml
+++ b/v7/mediarouter/res/values-uk/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Під’єднатися до пристрою"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Пошук пристроїв…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Від’єднатися"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Зупинити трансляцію"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Налаштування маршруту"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Відтворити"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Призупинити"</string>
</resources>
diff --git a/v7/mediarouter/res/values-ur-rPK/strings.xml b/v7/mediarouter/res/values-ur-rPK/strings.xml
index 1ab69c5..e6ce4d6 100644
--- a/v7/mediarouter/res/values-ur-rPK/strings.xml
+++ b/v7/mediarouter/res/values-ur-rPK/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"آلہ سے مربوط ہوں"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"آلات تلاش کر رہا ہے…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"غیر مربوط کریں"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"کاسٹ کرنا بند کریں"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"روٹ کی ترتیبات"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"چلائیں"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"موقوف کریں"</string>
</resources>
diff --git a/v7/mediarouter/res/values-uz-rUZ/strings.xml b/v7/mediarouter/res/values-uz-rUZ/strings.xml
index 6d953cb..d2829ee 100644
--- a/v7/mediarouter/res/values-uz-rUZ/strings.xml
+++ b/v7/mediarouter/res/values-uz-rUZ/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Qurilmaga ulanish"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Qurilmalar izlanmoqda…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Uzish"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Translatsiyani to‘xtatish"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Yo‘naltirish sozlamalari"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Ijro qilish"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"To‘xtatib turish"</string>
</resources>
diff --git a/v7/mediarouter/res/values-vi/strings.xml b/v7/mediarouter/res/values-vi/strings.xml
index c6e107e..01ec106 100644
--- a/v7/mediarouter/res/values-vi/strings.xml
+++ b/v7/mediarouter/res/values-vi/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Kết nối với thiết bị"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Đang tìm kiếm thiết bị…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ngắt kết nối"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Ngừng truyền"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Cài đặt tuyến đường"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Phát"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Tạm dừng"</string>
</resources>
diff --git a/v7/mediarouter/res/values-zh-rCN/strings.xml b/v7/mediarouter/res/values-zh-rCN/strings.xml
index 9750e6c..070f1de 100644
--- a/v7/mediarouter/res/values-zh-rCN/strings.xml
+++ b/v7/mediarouter/res/values-zh-rCN/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"连接到设备"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜索设备…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"断开连接"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"停止投射"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"路由设置"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"播放"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"暂停"</string>
</resources>
diff --git a/v7/mediarouter/res/values-zh-rHK/strings.xml b/v7/mediarouter/res/values-zh-rHK/strings.xml
index e8ea767..a73d636 100644
--- a/v7/mediarouter/res/values-zh-rHK/strings.xml
+++ b/v7/mediarouter/res/values-zh-rHK/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"連線至裝置"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜尋裝置…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"中斷連線"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"停止投放"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"路由設定"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"播放"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"暫停"</string>
</resources>
diff --git a/v7/mediarouter/res/values-zh-rTW/strings.xml b/v7/mediarouter/res/values-zh-rTW/strings.xml
index 935f877..cb07c25 100644
--- a/v7/mediarouter/res/values-zh-rTW/strings.xml
+++ b/v7/mediarouter/res/values-zh-rTW/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"連線至裝置"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜尋裝置..."</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"中斷連線"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"停止投放"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"路由設定"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"播放"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"暫停"</string>
</resources>
diff --git a/v7/mediarouter/res/values-zu/strings.xml b/v7/mediarouter/res/values-zu/strings.xml
index 3791584..24f0a37 100644
--- a/v7/mediarouter/res/values-zu/strings.xml
+++ b/v7/mediarouter/res/values-zu/strings.xml
@@ -22,4 +22,8 @@
<string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Xhumeka kudivayisi"</string>
<string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Iseshela amadivayisi…"</string>
<string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Nqamula"</string>
+ <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Misa ukusakaza"</string>
+ <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Izilungiselelo zomzila"</string>
+ <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Dlala"</string>
+ <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Misa isikhashana"</string>
</resources>
diff --git a/v7/mediarouter/res/values/attrs.xml b/v7/mediarouter/res/values/attrs.xml
index 2272e7a..5cd5606 100644
--- a/v7/mediarouter/res/values/attrs.xml
+++ b/v7/mediarouter/res/values/attrs.xml
@@ -30,4 +30,7 @@
<attr name="mediaRouteOffDrawable" format="reference" />
<attr name="mediaRouteConnectingDrawable" format="reference" />
<attr name="mediaRouteOnDrawable" format="reference" />
+ <attr name="mediaRouteSettingsDrawable" format="reference" />
+ <attr name="mediaRoutePlayDrawable" format="reference" />
+ <attr name="mediaRoutePauseDrawable" format="reference" />
</resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values/dimens.xml b/v7/mediarouter/res/values/dimens.xml
new file mode 100644
index 0000000..e687c82
--- /dev/null
+++ b/v7/mediarouter/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <dimen name="mr_media_route_controller_art_max_height">320dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values/strings.xml b/v7/mediarouter/res/values/strings.xml
index b8944b6..c6eb0fb 100644
--- a/v7/mediarouter/res/values/strings.xml
+++ b/v7/mediarouter/res/values/strings.xml
@@ -34,4 +34,15 @@
<!-- Button to disconnect from a media route. [CHAR LIMIT=30] -->
<string name="mr_media_route_controller_disconnect">Disconnect</string>
+ <!-- Button to stop playback and disconnect from a media route. [CHAR LIMIT=30] -->
+ <string name="mr_media_route_controller_stop">Stop casting</string>
+
+ <!-- Description for a button that takes you to settings for the active route -->
+ <string name="mr_media_route_controller_settings_description">Route settings</string>
+
+ <!-- Accessibility description for the play button -->
+ <string name="mr_media_route_controller_play">Play</string>
+
+ <!-- Accessibility description for the pause button -->
+ <string name="mr_media_route_controller_pause">Pause</string>
</resources>
diff --git a/v7/mediarouter/res/values/styles.xml b/v7/mediarouter/res/values/styles.xml
index fc6b411..9be8545 100644
--- a/v7/mediarouter/res/values/styles.xml
+++ b/v7/mediarouter/res/values/styles.xml
@@ -22,7 +22,7 @@
<item name="android:padding">0dp</item>
<item name="android:focusable">true</item>
<item name="android:contentDescription">@string/mr_media_route_button_content_description</item>
- <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_holo_dark</item>
+ <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_mono_dark</item>
</style>
<style name="Widget.MediaRouter.Light.MediaRouteButton"
@@ -32,6 +32,6 @@
<item name="android:padding">0dp</item>
<item name="android:focusable">true</item>
<item name="android:contentDescription">@string/mr_media_route_button_content_description</item>
- <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_holo_light</item>
+ <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_mono_light</item>
</style>
</resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values/themes.xml b/v7/mediarouter/res/values/themes.xml
index 879188d..8350e04 100644
--- a/v7/mediarouter/res/values/themes.xml
+++ b/v7/mediarouter/res/values/themes.xml
@@ -19,17 +19,23 @@
<style name="Theme.MediaRouter" parent="">
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
- <item name="mediaRouteOffDrawable">@drawable/mr_ic_media_route_off_holo_dark</item>
- <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_holo_dark</item>
- <item name="mediaRouteOnDrawable">@drawable/mr_ic_media_route_on_holo_dark</item>
+ <item name="mediaRouteOffDrawable">@drawable/ic_media_route_off_mono_dark</item>
+ <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_dark</item>
+ <item name="mediaRouteOnDrawable">@drawable/ic_media_route_on_mono_dark</item>
+ <item name="mediaRouteSettingsDrawable">@drawable/mr_ic_settings_dark</item>
+ <item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_dark</item>
+ <item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_dark</item>
</style>
<style name="Theme.MediaRouter.Light" parent="">
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.Light.MediaRouteButton</item>
- <item name="mediaRouteOffDrawable">@drawable/mr_ic_media_route_off_holo_light</item>
- <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_holo_light</item>
- <item name="mediaRouteOnDrawable">@drawable/mr_ic_media_route_on_holo_light</item>
+ <item name="mediaRouteOffDrawable">@drawable/ic_cast_off_light</item>
+ <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_light</item>
+ <item name="mediaRouteOnDrawable">@drawable/ic_cast_on_light</item>
+ <item name="mediaRouteSettingsDrawable">@drawable/mr_ic_settings_light</item>
+ <item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_light</item>
+ <item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_light</item>
</style>
</resources>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index f5103fa..896c116 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -119,7 +119,7 @@
}
public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(MediaRouterThemeHelper.createThemedContext(context, false), attrs, defStyleAttr);
+ super(MediaRouterThemeHelper.createThemedContext(context), attrs, defStyleAttr);
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index 3a87f02..779ae8b 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -62,7 +62,7 @@
}
public MediaRouteChooserDialog(Context context, int theme) {
- super(MediaRouterThemeHelper.createThemedContext(context, true), theme);
+ super(MediaRouterThemeHelper.createThemedContext(context), theme);
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 3fe9c78..b43af77 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -18,18 +18,29 @@
import android.app.Dialog;
import android.content.Context;
+import android.content.IntentSender;
+import android.content.IntentSender.SendIntentException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.mediarouter.R;
+import android.text.TextUtils;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
/**
* This class implements the route controller dialog for {@link MediaRouter}.
@@ -43,40 +54,49 @@
public class MediaRouteControllerDialog extends Dialog {
private static final String TAG = "MediaRouteControllerDialog";
- // Time to wait before updating the volume when the user lets go of the seek bar
- // to allow the route provider time to propagate the change and publish a new
- // route descriptor.
- private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
-
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
private final MediaRouter.RouteInfo mRoute;
private boolean mCreated;
+ private boolean mAttachedToWindow;
private Drawable mMediaRouteConnectingDrawable;
private Drawable mMediaRouteOnDrawable;
private Drawable mCurrentIconDrawable;
-
- private boolean mVolumeControlEnabled = true;
- private LinearLayout mVolumeLayout;
- private SeekBar mVolumeSlider;
- private boolean mVolumeSliderTouched;
+ private Drawable mSettingsDrawable;
private View mControlView;
private Button mDisconnectButton;
+ private Button mStopCastingButton;
+ private ImageButton mPlayPauseButton;
+ private ImageButton mSettingsButton;
+
+ private ImageView mArtView;
+ private TextView mTitleView;
+ private TextView mSubtitleView;
+ private TextView mRouteNameView;
+ private View mTitlesWrapper;
+
+ private MediaControllerCompat mMediaController;
+ private MediaControllerCallback mControllerCallback;
+ private PlaybackStateCompat mState;
+ private MediaDescriptionCompat mDescription;
+
public MediaRouteControllerDialog(Context context) {
this(context, 0);
}
public MediaRouteControllerDialog(Context context, int theme) {
- super(MediaRouterThemeHelper.createThemedContext(context, true), theme);
+ super(MediaRouterThemeHelper.createThemedContext(context), theme);
context = getContext();
+ mControllerCallback = new MediaControllerCallback();
mRouter = MediaRouter.getInstance(context);
mCallback = new MediaRouterCallback();
mRoute = mRouter.getSelectedRoute();
+ setMediaSession(mRouter.getMediaSessionToken());
}
/**
@@ -108,85 +128,73 @@
}
/**
- * Sets whether to enable the volume slider and volume control using the volume keys
- * when the route supports it.
- * <p>
- * The default value is true.
- * </p>
+ * Set the session to use for metadata and transport controls. The dialog
+ * will listen to changes on this session and update the UI automatically in
+ * response to changes.
+ *
+ * @param sessionToken The token for the session to use.
*/
- public void setVolumeControlEnabled(boolean enable) {
- if (mVolumeControlEnabled != enable) {
- mVolumeControlEnabled = enable;
- if (mCreated) {
- updateVolume();
- }
+ private void setMediaSession(MediaSessionCompat.Token sessionToken) {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mControllerCallback);
+ mMediaController = null;
}
+ if (sessionToken == null) {
+ return;
+ }
+ if (!mAttachedToWindow) {
+ return;
+ }
+ try {
+ mMediaController = new MediaControllerCompat(getContext(), sessionToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating media controller in setMediaSession.", e);
+ }
+ if (mMediaController != null) {
+ mMediaController.registerCallback(mControllerCallback);
+ }
+ MediaMetadataCompat metadata = mMediaController == null ? null
+ : mMediaController.getMetadata();
+ mDescription = metadata == null ? null : metadata.getDescription();
+ mState = mMediaController == null ? null : mMediaController.getPlaybackState();
+ update();
}
/**
- * Returns whether to enable the volume slider and volume control using the volume keys
- * when the route supports it.
+ * Gets the description being used by the default UI.
+ *
+ * @return The current description.
*/
- public boolean isVolumeControlEnabled() {
- return mVolumeControlEnabled;
+ public MediaSessionCompat.Token getMediaSession() {
+ return mMediaController == null ? null : mMediaController.getSessionToken();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+ getWindow().requestFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.mr_media_route_controller_dialog);
+ setContentView(R.layout.mr_media_route_controller_material_dialog_b);
- mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout);
- mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider);
- mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- private final Runnable mStopTrackingTouch = new Runnable() {
- @Override
- public void run() {
- if (mVolumeSliderTouched) {
- mVolumeSliderTouched = false;
- updateVolume();
- }
- }
- };
+ ClickListener listener = new ClickListener();
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- if (mVolumeSliderTouched) {
- mVolumeSlider.removeCallbacks(mStopTrackingTouch);
- } else {
- mVolumeSliderTouched = true;
- }
- }
+ mDisconnectButton = (Button) findViewById(R.id.disconnect);
+ mDisconnectButton.setOnClickListener(listener);
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- // Defer resetting mVolumeSliderTouched to allow the media route provider
- // a little time to settle into its new state and publish the final
- // volume update.
- mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
- }
+ mStopCastingButton = (Button) findViewById(R.id.stop);
+ mStopCastingButton.setOnClickListener(listener);
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (fromUser) {
- mRoute.requestSetVolume(progress);
- }
- }
- });
+ mSettingsButton = (ImageButton) findViewById(R.id.settings);
+ mSettingsButton.setOnClickListener(listener);
- mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button);
- mDisconnectButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mRoute.isSelected()) {
- mRouter.getDefaultRoute().select();
- }
- dismiss();
- }
- });
+ mArtView = (ImageView) findViewById(R.id.art);
+ mTitleView = (TextView) findViewById(R.id.title);
+ mSubtitleView = (TextView) findViewById(R.id.subtitle);
+ mTitlesWrapper = findViewById(R.id.text_wrapper);
+ mPlayPauseButton = (ImageButton) findViewById(R.id.play_pause);
+ mPlayPauseButton.setOnClickListener(listener);
+ mRouteNameView = (TextView) findViewById(R.id.route_name);
mCreated = true;
if (update()) {
@@ -194,28 +202,27 @@
FrameLayout controlFrame =
(FrameLayout)findViewById(R.id.media_route_control_frame);
if (mControlView != null) {
+ controlFrame.findViewById(R.id.default_control_frame).setVisibility(View.GONE);
controlFrame.addView(mControlView);
- controlFrame.setVisibility(View.VISIBLE);
- } else {
- controlFrame.setVisibility(View.GONE);
}
}
}
-
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
+ mAttachedToWindow = true;
mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback,
MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
- update();
+ setMediaSession(mRouter.getMediaSessionToken());
}
@Override
public void onDetachedFromWindow() {
mRouter.removeCallback(mCallback);
-
+ setMediaSession(null);
+ mAttachedToWindow = false;
super.onDetachedFromWindow();
}
@@ -243,20 +250,89 @@
dismiss();
return false;
}
+ if (!mCreated) {
+ return false;
+ }
- setTitle(mRoute.getName());
- updateVolume();
+ mRouteNameView.setText(mRoute.getName());
- Drawable icon = getIconDrawable();
- if (icon != mCurrentIconDrawable) {
- mCurrentIconDrawable = icon;
+ if (mRoute.canDisconnect()) {
+ mDisconnectButton.setVisibility(View.VISIBLE);
+ } else {
+ mDisconnectButton.setVisibility(View.GONE);
+ }
- // Prior to KLP MR1 there was a bug in ImageView that caused feature drawables
- // to not start animating unless they experienced a transition from
- // invisible to visible. So we force the drawable to be invisible here.
- // The window will make the drawable visible when attached.
- icon.setVisible(false, true);
- getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon);
+ if (mRoute.getSettingsIntent() != null) {
+ mSettingsButton.setVisibility(View.VISIBLE);
+ } else {
+ mSettingsButton.setVisibility(View.GONE);
+ }
+
+ if (mControlView == null) {
+ if (mDescription != null) {
+ if (mDescription.getIconBitmap() != null) {
+ mArtView.setImageBitmap(mDescription.getIconBitmap());
+ mArtView.setVisibility(View.VISIBLE);
+ } else if (mDescription.getIconUri() != null) {
+ // TODO replace with background load of icon
+ mArtView.setImageURI(mDescription.getIconUri());
+ mArtView.setVisibility(View.VISIBLE);
+ } else {
+ mArtView.setImageDrawable(null);
+ mArtView.setVisibility(View.GONE);
+ }
+
+ boolean haveText = false;
+ CharSequence text = mDescription.getTitle();
+ if (!TextUtils.isEmpty(text)) {
+ mTitleView.setText(text);
+ haveText = true;
+ } else {
+ mTitleView.setText(null);
+ mTitleView.setVisibility(View.GONE);
+ }
+ text = mDescription.getSubtitle();
+ if (!TextUtils.isEmpty(text)) {
+ mSubtitleView.setText(mDescription.getSubtitle());
+ haveText = true;
+ } else {
+ mSubtitleView.setText(null);
+ mSubtitleView.setVisibility(View.GONE);
+ }
+ if (!haveText) {
+ mTitlesWrapper.setVisibility(View.GONE);
+ } else {
+ mTitlesWrapper.setVisibility(View.VISIBLE);
+ }
+ } else {
+ mArtView.setVisibility(View.GONE);
+ mTitlesWrapper.setVisibility(View.GONE);
+ }
+ if (mState != null) {
+ boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING
+ || mState.getState() == PlaybackStateCompat.STATE_PLAYING;
+ boolean supportsPlay = (mState.getActions() & (PlaybackStateCompat.ACTION_PLAY
+ | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
+ boolean supportsPause = (mState.getActions() & (PlaybackStateCompat.ACTION_PAUSE
+ | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
+ if (isPlaying && supportsPause) {
+ mPlayPauseButton.setVisibility(View.VISIBLE);
+ mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
+ getContext(), R.attr.mediaRoutePauseDrawable));
+ mPlayPauseButton.setContentDescription(getContext().getResources()
+ .getText(R.string.mr_media_route_controller_pause));
+ } else if (!isPlaying && supportsPlay) {
+ mPlayPauseButton.setVisibility(View.VISIBLE);
+ mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
+ getContext(), R.attr.mediaRoutePlayDrawable));
+ mPlayPauseButton.setContentDescription(getContext().getResources()
+ .getText(R.string.mr_media_route_controller_play));
+ } else {
+ mPlayPauseButton.setVisibility(View.GONE);
+ }
+ } else {
+ mPlayPauseButton.setVisibility(View.GONE);
+ }
}
return true;
}
@@ -277,23 +353,6 @@
}
}
- private void updateVolume() {
- if (!mVolumeSliderTouched) {
- if (isVolumeControlAvailable()) {
- mVolumeLayout.setVisibility(View.VISIBLE);
- mVolumeSlider.setMax(mRoute.getVolumeMax());
- mVolumeSlider.setProgress(mRoute.getVolume());
- } else {
- mVolumeLayout.setVisibility(View.GONE);
- }
- }
- }
-
- private boolean isVolumeControlAvailable() {
- return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
- MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
- }
-
private final class MediaRouterCallback extends MediaRouter.Callback {
@Override
public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
@@ -308,7 +367,61 @@
@Override
public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
if (route == mRoute) {
- updateVolume();
+ }
+ }
+ }
+
+ private final class MediaControllerCallback extends MediaControllerCompat.Callback {
+ @Override
+ public void onSessionDestroyed() {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mControllerCallback);
+ mMediaController = null;
+ }
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ mState = state;
+ update();
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) {
+ mDescription = metadata == null ? null : metadata.getDescription();
+ update();
+ }
+ }
+
+ private final class ClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.stop || id == R.id.disconnect) {
+ if (mRoute.isSelected()) {
+ mRouter.unselect(id == R.id.stop ?
+ MediaRouter.UNSELECT_REASON_STOPPED :
+ MediaRouter.UNSELECT_REASON_DISCONNECTED);
+ }
+ dismiss();
+ } else if (id == R.id.play_pause) {
+ if (mMediaController != null && mState != null) {
+ if (mState.getState() == PlaybackStateCompat.STATE_PLAYING) {
+ mMediaController.getTransportControls().pause();
+ } else {
+ mMediaController.getTransportControls().play();
+ }
+ }
+ } else if (id == R.id.settings) {
+ IntentSender is = mRoute.getSettingsIntent();
+ if (is != null) {
+ try {
+ is.sendIntent(null, 0, null, null, null);
+ dismiss();
+ } catch (Exception e) {
+ Log.e(TAG, "Error opening route settings.", e);
+ }
+ }
}
}
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
index 1fb9346..09999a1 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
@@ -26,12 +26,8 @@
private MediaRouterThemeHelper() {
}
- public static Context createThemedContext(Context context, boolean forceDark) {
+ public static Context createThemedContext(Context context) {
boolean isLightTheme = isLightTheme(context);
- if (isLightTheme && forceDark) {
- context = new ContextThemeWrapper(context, R.style.Theme_AppCompat);
- isLightTheme = false;
- }
return new ContextThemeWrapper(context, isLightTheme ?
R.style.Theme_MediaRouter_Light : R.style.Theme_MediaRouter);
}
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
index 7ccffe1..d83ad2f 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
@@ -16,6 +16,7 @@
package android.support.v7.media;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.os.Bundle;
import android.text.TextUtils;
@@ -48,6 +49,8 @@
private static final String KEY_VOLUME_HANDLING = "volumeHandling";
private static final String KEY_PRESENTATION_DISPLAY_ID = "presentationDisplayId";
private static final String KEY_EXTRAS = "extras";
+ private static final String KEY_CAN_DISCONNECT = "canDisconnect";
+ private static final String KEY_SETTINGS_INTENT = "settingsIntent";
private final Bundle mBundle;
private List<IntentFilter> mControlFilters;
@@ -106,6 +109,27 @@
}
/**
+ * Gets whether the route can be disconnected without stopping playback. To
+ * specify that the route should disconnect without stopping use
+ * {@link MediaRouter#unselect(int)} with
+ * {@link MediaRouter#UNSELECT_REASON_DISCONNECTED}.
+ */
+ public boolean canDisconnectAndKeepPlaying() {
+ return mBundle.getBoolean(KEY_CAN_DISCONNECT, false);
+ }
+
+ /**
+ * Gets an {@link IntentSender} for starting a settings activity for this
+ * route. The activity may have specific route settings or general settings
+ * for the connected device or route provider.
+ *
+ * @return An {@link IntentSender} to start a settings activity.
+ */
+ public IntentSender getSettingsActivity() {
+ return mBundle.getParcelable(KEY_SETTINGS_INTENT);
+ }
+
+ /**
* Gets the route's {@link MediaControlIntent media control intent} filters.
*/
public List<IntentFilter> getControlFilters() {
@@ -323,6 +347,23 @@
}
/**
+ * Sets whether the route can be disconnected without stopping playback.
+ */
+ public Builder setCanDisconnect(boolean canDisconnect) {
+ mBundle.putBoolean(KEY_CAN_DISCONNECT, canDisconnect);
+ return this;
+ }
+
+ /**
+ * Sets an intent sender for launching the settings activity for this
+ * route.
+ */
+ public Builder setSettingsActivity(IntentSender is) {
+ mBundle.putParcelable(KEY_SETTINGS_INTENT, is);
+ return this;
+ }
+
+ /**
* Adds a {@link MediaControlIntent media control intent} filter for the route.
*/
public Builder addControlFilter(IntentFilter filter) {
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
index e011877..f370e95 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
@@ -331,6 +331,24 @@
}
/**
+ * Unselects the route and provides a reason. The default implementation
+ * calls {@link #onUnselect()}.
+ * <p>
+ * The reason provided will be one of the following:
+ * <ul>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * </ul>
+ *
+ * @param reason The reason for unselecting the route.
+ */
+ public void onUnselect(int reason) {
+ onUnselect();
+ }
+
+ /**
* Requests to set the volume of the route.
*
* @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
index f44dcac..3f0976f 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
@@ -121,6 +121,7 @@
public static final String CLIENT_DATA_ROUTE_ID = "routeId";
public static final String CLIENT_DATA_VOLUME = "volume";
+ public static final String CLIENT_DATA_UNSELECT_REASON = "unselectReason";
/*
* Messages sent from the service to the client.
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index d3f548d..79d87e1 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -234,13 +234,13 @@
}
private boolean onUnselectRoute(Messenger messenger, int requestId,
- int controllerId) {
+ int controllerId, int reason) {
ClientRecord client = getClient(messenger);
if (client != null) {
MediaRouteProvider.RouteController controller =
client.getRouteController(controllerId);
if (controller != null) {
- controller.onUnselect();
+ controller.onUnselect(reason);
if (DEBUG) {
Log.d(TAG, client + ": Route unselected"
+ ", controllerId=" + controllerId);
@@ -633,7 +633,11 @@
return service.onSelectRoute(messenger, requestId, arg);
case CLIENT_MSG_UNSELECT_ROUTE:
- return service.onUnselectRoute(messenger, requestId, arg);
+ int reason = data == null ?
+ MediaRouter.UNSELECT_REASON_UNKNOWN
+ : data.getInt(CLIENT_DATA_UNSELECT_REASON,
+ MediaRouter.UNSELECT_REASON_UNKNOWN);
+ return service.onUnselectRoute(messenger, requestId, arg, reason);
case CLIENT_MSG_SET_ROUTE_VOLUME: {
int volume = data.getInt(CLIENT_DATA_VOLUME, -1);
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index 9169b6b..5bb998e 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -17,11 +17,13 @@
package android.support.v7.media;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.os.Bundle;
@@ -69,6 +71,30 @@
private static final String TAG = "MediaRouter";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * when the reason the route was unselected is unknown.
+ */
+ public static final int UNSELECT_REASON_UNKNOWN = 0;
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * when the user pressed the disconnect button to disconnect and keep playing.
+ * <p>
+ *
+ * @see {@link MediaRouteDescriptor#canDisconnectAndKeepPlaying()}.
+ */
+ public static final int UNSELECT_REASON_DISCONNECTED = 1;
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * when the user pressed the stop casting button.
+ */
+ public static final int UNSELECT_REASON_STOPPED = 2;
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * when the user selected a different route.
+ */
+ public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
+
// Maintains global media router state for the process.
// This field is initialized in MediaRouter.getInstance() before any
// MediaRouter objects are instantiated so it is guaranteed to be
@@ -354,13 +380,37 @@
}
/**
+ * Unselects the current round and selects the default route instead.
+ * <p>
+ * The reason given must be one of:
+ * <ul>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * </ul>
+ *
+ * @param reason The reason for disconnecting the current route.
+ */
+ public void unselect(int reason) {
+ if (reason < MediaRouter.UNSELECT_REASON_UNKNOWN ||
+ reason > MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
+ throw new IllegalArgumentException("Unsupported reason to unselect route");
+ }
+ checkCallingThread();
+
+ sGlobal.selectRoute(getDefaultRoute(), reason);
+ }
+
+ /**
* Returns true if there is a route that matches the specified selector.
* <p>
- * This method returns true if there are any available routes that match the selector
- * regardless of whether they are enabled or disabled. If the
+ * This method returns true if there are any available routes that match the
+ * selector regardless of whether they are enabled or disabled. If the
* {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
* the method will only consider non-default routes.
- * </p><p class="note">
+ * </p>
+ * <p class="note">
* On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
* will return true if it is possible to discover a matching route even if
* discovery is not in progress or if no matching route has yet been found.
@@ -368,9 +418,10 @@
* </p>
*
* @param selector The selector to match.
- * @param flags Flags to control the determination of whether a route may be available.
- * May be zero or some combination of {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}
- * and {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
+ * @param flags Flags to control the determination of whether a route may be
+ * available. May be zero or some combination of
+ * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
+ * {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
* @return True if a matching route may be available.
*/
public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
@@ -670,6 +721,25 @@
}
/**
+ * Sets a compat media session to enable remote control of the volume of the
+ * selected route. This should be used instead of
+ * {@link #addRemoteControlClient} when using {@link MediaSessionCompat}.
+ * Set the session to null to clear it.
+ *
+ * @param mediaSession
+ */
+ public void setMediaSessionCompat(MediaSessionCompat mediaSession) {
+ if (DEBUG) {
+ Log.d(TAG, "addMediaSessionCompat: " + mediaSession);
+ }
+ sGlobal.setMediaSessionCompat(mediaSession);
+ }
+
+ public MediaSessionCompat.Token getMediaSessionToken() {
+ return sGlobal.getMediaSessionToken();
+ }
+
+ /**
* Ensures that calls into the media router are on the correct thread.
* It pays to be a little paranoid when global state invariants are at risk.
*/
@@ -700,6 +770,7 @@
private String mDescription;
private boolean mEnabled;
private boolean mConnecting;
+ private boolean mCanDisconnect;
private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>();
private int mPlaybackType;
private int mPlaybackStream;
@@ -709,6 +780,7 @@
private Display mPresentationDisplay;
private int mPresentationDisplayId = -1;
private Bundle mExtras;
+ private IntentSender mSettingsIntent;
private MediaRouteDescriptor mDescriptor;
/** @hide */
@@ -1072,6 +1144,17 @@
}
/**
+ * Gets whether this route supports disconnecting without interrupting
+ * playback.
+ *
+ * @return True if this route can disconnect without stopping playback,
+ * false otherwise.
+ */
+ public boolean canDisconnect() {
+ return mCanDisconnect;
+ }
+
+ /**
* Requests a volume change for this route asynchronously.
* <p>
* This function may only be called on a selected route. It will have
@@ -1149,6 +1232,15 @@
}
/**
+ * Gets an intent sender for launching a settings activity for this
+ * route.
+ */
+ @Nullable
+ public IntentSender getSettingsIntent() {
+ return mSettingsIntent;
+ }
+
+ /**
* Selects this media route.
*/
public void select() {
@@ -1163,6 +1255,7 @@
+ ", description=" + mDescription
+ ", enabled=" + mEnabled
+ ", connecting=" + mConnecting
+ + ", canDisconnect=" + mCanDisconnect
+ ", playbackType=" + mPlaybackType
+ ", playbackStream=" + mPlaybackStream
+ ", volumeHandling=" + mVolumeHandling
@@ -1170,6 +1263,7 @@
+ ", volumeMax=" + mVolumeMax
+ ", presentationDisplayId=" + mPresentationDisplayId
+ ", extras=" + mExtras
+ + ", settingsIntent=" + mSettingsIntent
+ ", providerPackageName=" + mProvider.getPackageName()
+ " }";
}
@@ -1229,6 +1323,14 @@
mExtras = descriptor.getExtras();
changes |= CHANGE_GENERAL;
}
+ if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
+ mSettingsIntent = descriptor.getSettingsActivity();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
+ mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
+ changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
+ }
}
}
return changes;
@@ -1521,6 +1623,21 @@
private MediaRouteProvider.RouteController mSelectedRouteController;
private MediaRouteDiscoveryRequest mDiscoveryRequest;
private MediaSessionRecord mMediaSession;
+ private MediaSessionCompat mRccMediaSession;
+ private MediaSessionCompat mCompatSession;
+ private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
+ new MediaSessionCompat.OnActiveChangeListener() {
+ @Override
+ public void onActiveChanged() {
+ if(mRccMediaSession != null) {
+ if (mRccMediaSession.isActive()) {
+ addRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ } else {
+ removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ }
+ }
+ }
+ };
GlobalMediaRouter(Context applicationContext) {
mApplicationContext = applicationContext;
@@ -1634,6 +1751,10 @@
}
public void selectRoute(RouteInfo route) {
+ selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+ }
+
+ public void selectRoute(RouteInfo route, int unselectReason) {
if (!mRoutes.contains(route)) {
Log.w(TAG, "Ignoring attempt to select removed route: " + route);
return;
@@ -1643,7 +1764,7 @@
return;
}
- setSelectedRouteInternal(route);
+ setSelectedRouteInternal(route, unselectReason);
}
public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
@@ -1952,13 +2073,15 @@
if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) {
Log.i(TAG, "Unselecting the current route because it "
+ "is no longer selectable: " + mSelectedRoute);
- setSelectedRouteInternal(null);
+ setSelectedRouteInternal(null,
+ MediaRouter.UNSELECT_REASON_UNKNOWN);
}
if (mSelectedRoute == null) {
// Choose a new route.
// This will have the side-effect of updating the playback info when
// the new route is selected.
- setSelectedRouteInternal(chooseFallbackRoute());
+ setSelectedRouteInternal(chooseFallbackRoute(),
+ MediaRouter.UNSELECT_REASON_UNKNOWN);
} else if (selectedRouteDescriptorChanged) {
// Update the playback info because the properties of the route have changed.
updatePlaybackInfoFromSelectedRoute();
@@ -1998,15 +2121,16 @@
SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
}
- private void setSelectedRouteInternal(RouteInfo route) {
+ private void setSelectedRouteInternal(RouteInfo route, int unselectReason) {
if (mSelectedRoute != route) {
if (mSelectedRoute != null) {
if (DEBUG) {
- Log.d(TAG, "Route unselected: " + mSelectedRoute);
+ Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
+ + unselectReason);
}
mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
if (mSelectedRouteController != null) {
- mSelectedRouteController.onUnselect();
+ mSelectedRouteController.onUnselect(unselectReason);
mSelectedRouteController.onRelease();
mSelectedRouteController = null;
}
@@ -2071,6 +2195,38 @@
}
}
+ public void setMediaSessionCompat(final MediaSessionCompat session) {
+ mCompatSession = session;
+ if (session == null) {
+ if (mRccMediaSession != null) {
+ removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
+ }
+ }
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ setMediaSession(session.getMediaSession());
+ } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+ if (mRccMediaSession != null) {
+ removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
+ }
+ mRccMediaSession = session;
+ session.addOnActiveChangeListener(mSessionActiveListener);
+ if (session.isActive()) {
+ addRemoteControlClient(session.getRemoteControlClient());
+ }
+ }
+ }
+
+ public MediaSessionCompat.Token getMediaSessionToken() {
+ if (mMediaSession != null) {
+ return mMediaSession.getToken();
+ } else if (mCompatSession != null) {
+ return mCompatSession.getSessionToken();
+ }
+ return null;
+ }
+
private int findRemoteControlClientRecord(Object rcc) {
final int count = mRemoteControlClients.size();
for (int i = 0; i < count; i++) {
@@ -2096,13 +2252,22 @@
record.updatePlaybackInfo();
}
if (mMediaSession != null) {
- int controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED;
- if (mPlaybackInfo.volumeHandling
- == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
- controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ if (mSelectedRoute == getDefaultRoute()) {
+ // Local route
+ mMediaSession.clearVolumeHandling();
+ } else {
+ int controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED;
+ if (mPlaybackInfo.volumeHandling
+ == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
+ controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ }
+ mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
+ mPlaybackInfo.volume);
}
- mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
- mPlaybackInfo.volume);
+ }
+ } else {
+ if (mMediaSession != null) {
+ mMediaSession.clearVolumeHandling();
}
}
}
@@ -2123,7 +2288,7 @@
private VolumeProviderCompat mVpCompat;
public MediaSessionRecord(Object mediaSession) {
- mMsCompat = MediaSessionCompat.obtain(mediaSession);
+ mMsCompat = MediaSessionCompat.obtain(mApplicationContext, mediaSession);
}
public void configureVolume(int controlType, int max, int current) {
@@ -2135,17 +2300,27 @@
// Otherwise create a new provider and update
mVpCompat = new VolumeProviderCompat(controlType, max, current) {
@Override
- public void onSetVolumeTo(int volume) {
- if (mSelectedRoute != null) {
- mSelectedRoute.requestSetVolume(volume);
- }
+ public void onSetVolumeTo(final int volume) {
+ mCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectedRoute != null) {
+ mSelectedRoute.requestSetVolume(volume);
+ }
+ }
+ });
}
@Override
- public void onAdjustVolume(int direction) {
- if (mSelectedRoute != null) {
- mSelectedRoute.requestUpdateVolume(direction);
- }
+ public void onAdjustVolume(final int direction) {
+ mCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectedRoute != null) {
+ mSelectedRoute.requestUpdateVolume(direction);
+ }
+ }
+ });
}
};
mMsCompat.setPlaybackToRemote(mVpCompat);
@@ -2157,6 +2332,10 @@
mVpCompat = null;
}
+ public MediaSessionCompat.Token getToken() {
+ return mMsCompat.getSessionToken();
+ }
+
}
private final class RemoteControlClientRecord
diff --git a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
index 641e80d..e84276d 100644
--- a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
@@ -344,9 +344,14 @@
@Override
public void onUnselect() {
+ onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
+ }
+
+ @Override
+ public void onUnselect(int reason) {
mSelected = false;
if (mConnection != null) {
- mConnection.unselectRoute(mControllerId);
+ mConnection.unselectRoute(mControllerId, reason);
}
}
@@ -525,9 +530,11 @@
mNextRequestId++, controllerId, null, null);
}
- public void unselectRoute(int controllerId) {
+ public void unselectRoute(int controllerId, int reason) {
+ Bundle extras = new Bundle();
+ extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason);
sendRequest(CLIENT_MSG_UNSELECT_ROUTE,
- mNextRequestId++, controllerId, null, null);
+ mNextRequestId++, controllerId, null, extras);
}
public void setVolume(int controllerId, int volume) {
diff --git a/v7/palette/src/android/support/v7/graphics/ColorUtils.java b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
index 84d330b..597fb6e 100644
--- a/v7/palette/src/android/support/v7/graphics/ColorUtils.java
+++ b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
@@ -123,25 +123,15 @@
return maxAlpha;
}
- static int getTextColorForBackground(int backgroundColor, float minContrastRatio) {
- // First we will check white as most colors will be dark
- final int whiteMinAlpha = ColorUtils
- .findMinimumAlpha(Color.WHITE, backgroundColor, minContrastRatio);
+ static int getTextColorForBackground(int backgroundColor, int textColor, float minContrastRatio) {
+ final int minAlpha = ColorUtils
+ .findMinimumAlpha(textColor, backgroundColor, minContrastRatio);
- if (whiteMinAlpha >= 0) {
- return ColorUtils.modifyAlpha(Color.WHITE, whiteMinAlpha);
+ if (minAlpha >= 0) {
+ return ColorUtils.modifyAlpha(textColor, minAlpha);
}
- // If we hit here then there is not an translucent white which provides enough contrast,
- // so check black
- final int blackMinAlpha = ColorUtils
- .findMinimumAlpha(Color.BLACK, backgroundColor, minContrastRatio);
-
- if (blackMinAlpha >= 0) {
- return ColorUtils.modifyAlpha(Color.BLACK, blackMinAlpha);
- }
-
- // This should not happen!
+ // Didn't find an opacity which provided enough contrast
return -1;
}
diff --git a/v7/palette/src/android/support/v7/graphics/Palette.java b/v7/palette/src/android/support/v7/graphics/Palette.java
index 0831b75..84543d5 100644
--- a/v7/palette/src/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/android/support/v7/graphics/Palette.java
@@ -186,6 +186,18 @@
}, bitmap);
}
+ /**
+ * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches.
+ * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
+ * list of swatches. Will return null if the {@code swatches} is null.
+ */
+ public static Palette from(List<Swatch> swatches) {
+ if (swatches == null) {
+ return null;
+ }
+ return new Palette(swatches);
+ }
+
private Palette(List<Swatch> swatches) {
mSwatches = swatches;
mHighestPopulation = findMaxPopulation();
@@ -540,11 +552,11 @@
private float[] mHsl;
- Swatch(int rgbColor, int population) {
- mRed = Color.red(rgbColor);
- mGreen = Color.green(rgbColor);
- mBlue = Color.blue(rgbColor);
- mRgb = rgbColor;
+ public Swatch(int color, int population) {
+ mRed = Color.red(color);
+ mGreen = Color.green(color);
+ mBlue = Color.blue(color);
+ mRgb = color;
mPopulation = population;
}
@@ -605,10 +617,37 @@
private void ensureTextColorsGenerated() {
if (!mGeneratedTextColors) {
- mTitleTextColor = ColorUtils.getTextColorForBackground(mRgb,
- MIN_CONTRAST_TITLE_TEXT);
- mBodyTextColor = ColorUtils.getTextColorForBackground(mRgb,
- MIN_CONTRAST_BODY_TEXT);
+ // First check white, as most colors will be dark
+ final int lightBody = ColorUtils.getTextColorForBackground(
+ mRgb, Color.WHITE, MIN_CONTRAST_BODY_TEXT);
+ final int lightTitle = ColorUtils.getTextColorForBackground(
+ mRgb, Color.WHITE, MIN_CONTRAST_TITLE_TEXT);
+
+ if (lightBody != -1 && lightTitle != -1) {
+ // If we found valid light values, use them and return
+ mBodyTextColor = lightBody;
+ mTitleTextColor = lightTitle;
+ mGeneratedTextColors = true;
+ return;
+ }
+
+ final int darkBody = ColorUtils.getTextColorForBackground(
+ mRgb, Color.BLACK, MIN_CONTRAST_BODY_TEXT);
+ final int darkTitle = ColorUtils.getTextColorForBackground(
+ mRgb, Color.BLACK, MIN_CONTRAST_TITLE_TEXT);
+
+ if (darkBody != -1 && darkBody != -1) {
+ // If we found valid dark values, use them and return
+ mBodyTextColor = darkBody;
+ mTitleTextColor = darkTitle;
+ mGeneratedTextColors = true;
+ return;
+ }
+
+ // If we reach here then we can not find title and body values which use the same
+ // lightness, we need to use mismatched values
+ mBodyTextColor = lightBody != -1 ? lightBody : darkBody;
+ mTitleTextColor = lightTitle != -1 ? lightTitle : darkTitle;
mGeneratedTextColors = true;
}
}
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index 40a58ad..4d0b564 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -4,6 +4,7 @@
dependencies {
compile project(':support-v4')
+ compile project(':support-annotations')
}
android {
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
index 84070ab..032449c 100644
--- a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
@@ -559,6 +559,42 @@
recycleUpdateOpsAndClearList(mPendingUpdates);
}
+ public int applyPendingUpdatesToPosition(int position) {
+ final int size = mPendingUpdates.size();
+ for (int i = 0; i < size; i ++) {
+ UpdateOp op = mPendingUpdates.get(i);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ if (op.positionStart <= position) {
+ position += op.itemCount;
+ }
+ break;
+ case UpdateOp.REMOVE:
+ if (op.positionStart <= position) {
+ final int end = op.positionStart + op.itemCount;
+ if (end > position) {
+ return RecyclerView.NO_POSITION;
+ }
+ position -= op.itemCount;
+ }
+ break;
+ case UpdateOp.MOVE:
+ if (op.positionStart == position) {
+ position = op.itemCount;//position end
+ } else {
+ if (op.positionStart < position) {
+ position -= 1;
+ }
+ if (op.itemCount <= position) {
+ position += 1;
+ }
+ }
+ break;
+ }
+ }
+ return position;
+ }
+
/**
* Queued operation to happen when child views are updated.
*/
diff --git a/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java b/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
index e555679..d6e409d 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
@@ -181,7 +181,7 @@
for (int i = 0; i < count; i++) {
final View view = mHiddenViews.get(i);
RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
- if (holder.getPosition() == position && !holder.isInvalid() &&
+ if (holder.getLayoutPosition() == position && !holder.isInvalid() &&
(type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) {
return view;
}
@@ -207,6 +207,9 @@
}
mCallback.attachViewToParent(child, offset, layoutParams);
mBucket.insert(offset, hidden);
+ if (hidden) {
+ mHiddenViews.add(child);
+ }
if (DEBUG) {
Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," +
"h:" + hidden + ", " + this);
@@ -311,7 +314,7 @@
@Override
public String toString() {
- return mBucket.toString();
+ return mBucket.toString() + ", hidden list:" + mHiddenViews.size();
}
/**
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index 2a27d65..950b254 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -331,31 +331,33 @@
private void animateChangeImpl(final ChangeInfo changeInfo) {
final ViewHolder holder = changeInfo.oldHolder;
- final View view = holder.itemView;
+ final View view = holder == null ? null : holder.itemView;
final ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
- mChangeAnimations.add(changeInfo.oldHolder);
+ if (view != null) {
+ mChangeAnimations.add(changeInfo.oldHolder);
+ final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
+ getChangeDuration());
+ oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
+ oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
+ oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
+ @Override
+ public void onAnimationStart(View view) {
+ dispatchChangeStarting(changeInfo.oldHolder, true);
+ }
- final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
- getChangeDuration());
- oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
- oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
- oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchChangeStarting(changeInfo.oldHolder, true);
- }
- @Override
- public void onAnimationEnd(View view) {
- oldViewAnim.setListener(null);
- ViewCompat.setAlpha(view, 1);
- ViewCompat.setTranslationX(view, 0);
- ViewCompat.setTranslationY(view, 0);
- dispatchChangeFinished(changeInfo.oldHolder, true);
- mChangeAnimations.remove(changeInfo.oldHolder);
- dispatchFinishedWhenDone();
- }
- }).start();
+ @Override
+ public void onAnimationEnd(View view) {
+ oldViewAnim.setListener(null);
+ ViewCompat.setAlpha(view, 1);
+ ViewCompat.setTranslationX(view, 0);
+ ViewCompat.setTranslationY(view, 0);
+ dispatchChangeFinished(changeInfo.oldHolder, true);
+ mChangeAnimations.remove(changeInfo.oldHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
if (newView != null) {
mChangeAnimations.add(changeInfo.newHolder);
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
@@ -427,7 +429,7 @@
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(item);
- mPendingMoves.remove(item);
+ mPendingMoves.remove(i);
}
}
endChangeAnimation(mPendingChanges, item);
@@ -444,7 +446,7 @@
ArrayList<ChangeInfo> changes = mChangesList.get(i);
endChangeAnimation(changes, item);
if (changes.isEmpty()) {
- mChangesList.remove(changes);
+ mChangesList.remove(i);
}
}
for (int i = mMovesList.size() - 1; i >= 0; i--) {
@@ -457,7 +459,7 @@
dispatchMoveFinished(item);
moves.remove(j);
if (moves.isEmpty()) {
- mMovesList.remove(moves);
+ mMovesList.remove(i);
}
break;
}
@@ -469,7 +471,7 @@
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
if (additions.isEmpty()) {
- mAdditionsList.remove(additions);
+ mAdditionsList.remove(i);
}
}
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index a05ac47..383717e 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -129,7 +129,7 @@
return;
}
LayoutParams glp = (LayoutParams) lp;
- int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewPosition());
+ int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewLayoutPosition());
if (mOrientation == HORIZONTAL) {
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
glp.getSpanIndex(), glp.getSpanSize(),
@@ -164,7 +164,7 @@
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
- final int viewPosition = lp.getViewPosition();
+ final int viewPosition = lp.getViewLayoutPosition();
mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
}
@@ -753,6 +753,10 @@
/**
* LayoutParams used by GridLayoutManager.
+ * <p>
+ * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
+ * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
+ * expected to fill all of the space given to it.
*/
public static class LayoutParams extends RecyclerView.LayoutParams {
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index fd8bd03..8d6dbf7 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -217,6 +217,7 @@
}
SavedState state = new SavedState();
if (getChildCount() > 0) {
+ ensureLayoutState();
boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
state.mAnchorLayoutFromEnd = didLayoutFromEnd;
if (didLayoutFromEnd) {
@@ -460,8 +461,10 @@
int extraForStart;
int extraForEnd;
final int extra = getExtraLayoutSpace(state);
- boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition;
- if (before == mShouldReverseLayout) {
+ // default extra space to the tail of the list.
+ boolean before = state.hasTargetScrollPosition() &&
+ state.getTargetScrollPosition() < mAnchorInfo.mPosition;
+ if (before == mAnchorInfo.mLayoutFromEnd) {
extraForEnd = extra;
extraForStart = 0;
} else {
@@ -599,7 +602,7 @@
final int firstChildPos = getPosition(getChildAt(0));
for (int i = 0; i < scrapSize; i++) {
RecyclerView.ViewHolder scrap = scrapList.get(i);
- final int position = scrap.getPosition();
+ final int position = scrap.getLayoutPosition();
final int direction = position < firstChildPos != mShouldReverseLayout
? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
if (direction == LayoutState.LAYOUT_START) {
@@ -990,27 +993,33 @@
if (getChildCount() == 0) {
return 0;
}
+ ensureLayoutState();
return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
- getChildClosestToStart(), getChildClosestToEnd(), this,
- mSmoothScrollbarEnabled, mShouldReverseLayout);
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled, mShouldReverseLayout);
}
private int computeScrollExtent(RecyclerView.State state) {
if (getChildCount() == 0) {
return 0;
}
+ ensureLayoutState();
return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
- getChildClosestToStart(), getChildClosestToEnd(), this,
- mSmoothScrollbarEnabled);
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled);
}
private int computeScrollRange(RecyclerView.State state) {
if (getChildCount() == 0) {
return 0;
}
+ ensureLayoutState();
return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
- getChildClosestToStart(), getChildClosestToEnd(), this,
- mSmoothScrollbarEnabled);
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled);
}
/**
@@ -1428,6 +1437,42 @@
return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
}
+ /**
+ * Convenience method to find the visible child closes to start. Caller should check if it has
+ * enough children.
+ *
+ * @param completelyVisible Whether child should be completely visible or not
+ * @return The first visible child closest to start of the layout from user's perspective.
+ */
+ private View findFirstVisibleChildClosestToStart(boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ if (mShouldReverseLayout) {
+ return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+ acceptPartiallyVisible);
+ } else {
+ return findOneVisibleChild(0, getChildCount(), completelyVisible,
+ acceptPartiallyVisible);
+ }
+ }
+
+ /**
+ * Convenience method to find the visible child closes to end. Caller should check if it has
+ * enough children.
+ *
+ * @param completelyVisible Whether child should be completely visible or not
+ * @return The first visible child closest to end of the layout from user's perspective.
+ */
+ private View findFirstVisibleChildClosestToEnd(boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ if (mShouldReverseLayout) {
+ return findOneVisibleChild(0, getChildCount(), completelyVisible,
+ acceptPartiallyVisible);
+ } else {
+ return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+ acceptPartiallyVisible);
+ }
+ }
+
/**
* Among the children that are suitable to be considered as an anchor child, returns the one
@@ -1469,6 +1514,7 @@
}
private View findReferenceChild(int start, int end, int itemCount) {
+ ensureLayoutState();
View invalidMatch = null;
View outOfBoundsMatch = null;
final int boundsStart = mOrientationHelper.getStartAfterPadding();
@@ -1496,7 +1542,8 @@
}
/**
- * Returns the adapter position of the first visible view.
+ * Returns the adapter position of the first visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
* <p>
* Note that, this value is not affected by layout orientation or item order traversal.
* ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
@@ -1513,12 +1560,13 @@
* @see #findLastVisibleItemPosition()
*/
public int findFirstVisibleItemPosition() {
- final View child = findOneVisibleChild(0, getChildCount(), false);
+ final View child = findOneVisibleChild(0, getChildCount(), false, true);
return child == null ? NO_POSITION : getPosition(child);
}
/**
- * Returns the adapter position of the first fully visible view.
+ * Returns the adapter position of the first fully visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
* <p>
* Note that bounds check is only performed in the current orientation. That means, if
* LayoutManager is horizontal, it will only check the view's left and right edges.
@@ -1529,12 +1577,13 @@
* @see #findLastCompletelyVisibleItemPosition()
*/
public int findFirstCompletelyVisibleItemPosition() {
- final View child = findOneVisibleChild(0, getChildCount(), true);
+ final View child = findOneVisibleChild(0, getChildCount(), true, false);
return child == null ? NO_POSITION : getPosition(child);
}
/**
- * Returns the adapter position of the last visible view.
+ * Returns the adapter position of the last visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
* <p>
* Note that, this value is not affected by layout orientation or item order traversal.
* ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
@@ -1551,12 +1600,13 @@
* @see #findFirstVisibleItemPosition()
*/
public int findLastVisibleItemPosition() {
- final View child = findOneVisibleChild(getChildCount() - 1, -1, false);
+ final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true);
return child == null ? NO_POSITION : getPosition(child);
}
/**
- * Returns the adapter position of the last fully visible view.
+ * Returns the adapter position of the last fully visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
* <p>
* Note that bounds check is only performed in the current orientation. That means, if
* LayoutManager is horizontal, it will only check the view's left and right edges.
@@ -1567,14 +1617,17 @@
* @see #findFirstCompletelyVisibleItemPosition()
*/
public int findLastCompletelyVisibleItemPosition() {
- final View child = findOneVisibleChild(getChildCount() - 1, -1, true);
+ final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false);
return child == null ? NO_POSITION : getPosition(child);
}
- View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
+ View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ ensureLayoutState();
final int start = mOrientationHelper.getStartAfterPadding();
final int end = mOrientationHelper.getEndAfterPadding();
final int next = toIndex > fromIndex ? 1 : -1;
+ View partiallyVisible = null;
for (int i = fromIndex; i != toIndex; i+=next) {
final View child = getChildAt(i);
final int childStart = mOrientationHelper.getDecoratedStart(child);
@@ -1583,13 +1636,15 @@
if (completelyVisible) {
if (childStart >= start && childEnd <= end) {
return child;
+ } else if (acceptPartiallyVisible && partiallyVisible == null) {
+ partiallyVisible = child;
}
} else {
return child;
}
}
}
- return null;
+ return partiallyVisible;
}
@Override
@@ -1604,6 +1659,7 @@
if (layoutDir == LayoutState.INVALID_LAYOUT) {
return null;
}
+ ensureLayoutState();
final View referenceChild;
if (layoutDir == LayoutState.LAYOUT_START) {
referenceChild = findReferenceChildClosestToStart(state);
@@ -1821,7 +1877,8 @@
if (!mIsPreLayout && viewHolder.isRemoved()) {
continue;
}
- final int distance = (viewHolder.getPosition() - mCurrentPosition) * mItemDirection;
+ final int distance = (viewHolder.getLayoutPosition() - mCurrentPosition) *
+ mItemDirection;
if (distance < 0) {
continue; // item is not in current direction
}
@@ -1837,7 +1894,7 @@
Log.d(TAG, "layout from scrap. found view:?" + (closest != null));
}
if (closest != null) {
- mCurrentPosition = closest.getPosition() + mItemDirection;
+ mCurrentPosition = closest.getLayoutPosition() + mItemDirection;
return closest.itemView;
}
return null;
@@ -1945,8 +2002,8 @@
*/
public boolean assignFromViewIfValid(View child, RecyclerView.State state) {
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
- if (!lp.isItemRemoved() && lp.getViewPosition() >= 0
- && lp.getViewPosition() < state.getItemCount()) {
+ if (!lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
+ && lp.getViewLayoutPosition() < state.getItemCount()) {
assignFromView(child);
return true;
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 149a1df..e5d4a29 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -31,6 +31,7 @@
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -82,6 +83,46 @@
* <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
* being displayed.</li>
* </ul>
+ *
+ * <h4>Positions in RecyclerView:</h4>
+ * <p>
+ * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and
+ * {@link LayoutManager} to be able to detect data set changes in batches during a layout
+ * calculation. This saves LayoutManager from tracking adapter changes to calculate animations.
+ * It also helps with performance because all view bindings happen at the same time and unnecessary
+ * bindings are avoided.
+ * <p>
+ * For this reason, there are two types of <code>position</code> related methods in RecyclerView:
+ * <ul>
+ * <li>layout position: Position of an item in the latest layout calculation. This is the
+ * position from the LayoutManager's perspective.</li>
+ * <li>adapter position: Position of an item in the adapter. This is the position from
+ * the Adapter's perspective.</li>
+ * </ul>
+ * <p>
+ * These two positions are the same except the time between dispatching <code>adapter.notify*
+ * </code> events and calculating the updated layout.
+ * <p>
+ * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest
+ * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()},
+ * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the
+ * last layout calculation. You can rely on these positions to be consistent with what user is
+ * currently seeing on the screen. For example, if you have a list of items on the screen and user
+ * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user
+ * is seeing.
+ * <p>
+ * The other set of position related methods are in the form of
+ * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()},
+ * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to
+ * work with up-to-date adapter positions even if they may not have been reflected to layout yet.
+ * For example, if you want to access the item in the adapter on a ViewHolder click, you should use
+ * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate
+ * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has
+ * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or
+ * <code>null</code> results from these methods.
+ * <p>
+ * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
+ * writing an {@link Adapter}, you probably want to use adapter positions.
*/
public class RecyclerView extends ViewGroup {
private static final String TAG = "RecyclerView";
@@ -106,6 +147,20 @@
public static final long NO_ID = -1;
public static final int INVALID_TYPE = -1;
+ /**
+ * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+ * that the RecyclerView should use the standard touch slop for smooth,
+ * continuous scrolling.
+ */
+ public static final int TOUCH_SLOP_DEFAULT = 0;
+
+ /**
+ * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+ * that the RecyclerView should use the standard touch slop for scrolling
+ * widgets that snap to a page or other coarse-grained barrier.
+ */
+ public static final int TOUCH_SLOP_PAGING = 1;
+
private static final int MAX_SCROLL_DURATION = 2000;
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
@@ -135,16 +190,13 @@
*/
private final Runnable mUpdateChildViewsRunnable = new Runnable() {
public void run() {
- if (!mAdapterHelper.hasPendingUpdates()) {
- return;
- }
if (!mFirstLayoutComplete) {
// a layout request will happen, we should not do layout here.
return;
}
if (mDataSetHasChangedAfterLayout) {
dispatchLayout();
- } else {
+ } else if (mAdapterHelper.hasPendingUpdates()) {
eatRequestLayout();
mAdapterHelper.preProcess();
if (!mLayoutRequestEaten) {
@@ -224,7 +276,7 @@
private int mInitialTouchY;
private int mLastTouchX;
private int mLastTouchY;
- private final int mTouchSlop;
+ private int mTouchSlop;
private final int mMinFlingVelocity;
private final int mMaxFlingVelocity;
@@ -241,6 +293,11 @@
new ItemAnimatorRestoreListener();
private boolean mPostedAnimatorRunner = false;
private RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
+
+ // simple array to keep min and max child position during a layout calculation
+ // preserved not to create a new one in each layout pass
+ private final int[] mMinMaxLayoutPositions = new int[2];
+
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
@@ -359,11 +416,36 @@
@Override
public void attachViewToParent(View child, int index,
ViewGroup.LayoutParams layoutParams) {
+ final ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ if (!vh.isTmpDetached() && !vh.shouldIgnore()) {
+ throw new IllegalArgumentException("Called attach on a child which is not"
+ + " detached: " + vh);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "reAttach " + vh);
+ }
+ vh.clearTmpDetachFlag();
+ }
RecyclerView.this.attachViewToParent(child, index, layoutParams);
}
@Override
public void detachViewFromParent(int offset) {
+ final View view = getChildAt(offset);
+ if (view != null) {
+ final ViewHolder vh = getChildViewHolderInt(view);
+ if (vh != null) {
+ if (vh.isTmpDetached() && !vh.shouldIgnore()) {
+ throw new IllegalArgumentException("called detach on an already"
+ + " detached child " + vh);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "tmpDetach " + vh);
+ }
+ vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
+ }
+ }
RecyclerView.this.detachViewFromParent(offset);
}
});
@@ -469,6 +551,32 @@
}
/**
+ * Configure the scrolling touch slop for a specific use case.
+ *
+ * Set up the RecyclerView's scrolling motion threshold based on common usages.
+ * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}.
+ *
+ * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing
+ * the intended usage of this RecyclerView
+ */
+ public void setScrollingTouchSlop(int slopConstant) {
+ final ViewConfiguration vc = ViewConfiguration.get(getContext());
+ switch (slopConstant) {
+ default:
+ Log.w(TAG, "setScrollingTouchSlop(): bad argument constant "
+ + slopConstant + "; using default value");
+ // fall-through
+ case TOUCH_SLOP_DEFAULT:
+ mTouchSlop = vc.getScaledTouchSlop();
+ break;
+
+ case TOUCH_SLOP_PAGING:
+ mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);
+ break;
+ }
+ }
+
+ /**
* Swaps the current adapter with the provided one. It is similar to
* {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same
* {@link ViewHolder} and does not clear the RecycledViewPool.
@@ -484,7 +592,7 @@
*/
public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
- mDataSetHasChangedAfterLayout = true;
+ setDataSetChangedAfterLayout();
requestLayout();
}
/**
@@ -514,6 +622,7 @@
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
+ mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
// end all running animations
@@ -526,14 +635,17 @@
// count.
if (mLayout != null) {
mLayout.removeAndRecycleAllViews(mRecycler);
- mLayout.removeAndRecycleScrapInt(mRecycler, true);
+ mLayout.removeAndRecycleScrapInt(mRecycler);
}
+ // we should clear it here before adapters are swapped to ensure correct callbacks.
+ mRecycler.clear();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
+ adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
@@ -636,12 +748,16 @@
* purely for the purpose of being animated out of view. They are drawn as a regular
* part of the child list of the RecyclerView, but they are invisible to the LayoutManager
* as they are managed separately from the regular child views.
- * @param view The view to be removed
+ * @param viewHolder The ViewHolder to be removed
*/
- private void addAnimatingView(View view) {
+ private void addAnimatingView(ViewHolder viewHolder) {
+ final View view = viewHolder.itemView;
final boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
- if (!alreadyParented) {
+ if (viewHolder.isTmpDetached()) {
+ // re-attach
+ mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
+ } else if(!alreadyParented) {
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
@@ -651,11 +767,13 @@
/**
* Removes a view from the animatingViews list.
* @param view The view to be removed
- * @see #addAnimatingView(View)
+ * @see #addAnimatingView(RecyclerView.ViewHolder)
+ * @return true if an animating view is removed
*/
- private void removeAnimatingView(View view) {
+ private boolean removeAnimatingView(View view) {
eatRequestLayout();
- if (mChildHelper.removeViewIfHidden(view)) {
+ final boolean removed = mChildHelper.removeViewIfHidden(view);
+ if (removed) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
mRecycler.unscrapView(viewHolder);
mRecycler.recycleViewHolderInternal(viewHolder);
@@ -664,6 +782,7 @@
}
}
resumeRequestLayout(false);
+ return removed;
}
/**
@@ -750,7 +869,9 @@
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(this, state);
}
- mLayout.onScrollStateChanged(state);
+ if (mLayout != null) {
+ mLayout.onScrollStateChanged(state);
+ }
}
/**
@@ -841,6 +962,11 @@
*/
public void scrollToPosition(int position) {
stopScroll();
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot scroll to position a LayoutManager set. " +
+ "Call setLayoutManager with a non-null argument.");
+ return;
+ }
mLayout.scrollToPosition(position);
awakenScrollBars();
}
@@ -861,6 +987,11 @@
* @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
*/
public void smoothScrollToPosition(int position) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
+ "Call setLayoutManager with a non-null argument.");
+ return;
+ }
mLayout.smoothScrollToPosition(this, mState, position);
}
@@ -873,8 +1004,9 @@
@Override
public void scrollBy(int x, int y) {
if (mLayout == null) {
- throw new IllegalStateException("Cannot scroll without a LayoutManager set. " +
+ Log.e(TAG, "Cannot scroll without a LayoutManager set. " +
"Call setLayoutManager with a non-null argument.");
+ return;
}
final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically();
@@ -892,15 +1024,15 @@
* This method consumes all deferred changes to avoid that case.
*/
private void consumePendingUpdateOperations() {
- if (mAdapterHelper.hasPendingUpdates()) {
- mUpdateChildViewsRunnable.run();
- }
+ mUpdateChildViewsRunnable.run();
}
/**
* Does not perform bounds checking. Used by internal methods that have already validated input.
+ *
+ * @return Whether any scroll was consumed in either direction.
*/
- void scrollByInternal(int x, int y) {
+ boolean scrollByInternal(int x, int y) {
int overscrollX = 0, overscrollY = 0;
int hresult = 0, vresult = 0;
consumePendingUpdateOperations();
@@ -947,14 +1079,12 @@
pullGlows(overscrollX, overscrollY);
}
if (hresult != 0 || vresult != 0) {
- onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
- if (mScrollListener != null) {
- mScrollListener.onScrolled(this, hresult, vresult);
- }
+ notifyOnScrolled(hresult, vresult);
}
if (!awakenScrollBars()) {
invalidate();
}
+ return hresult != 0 || vresult != 0;
}
/**
@@ -1112,6 +1242,17 @@
* @param dy Pixels to scroll vertically
*/
public void smoothScrollBy(int dx, int dy) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
+ "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ if (!mLayout.canScrollHorizontally()) {
+ dx = 0;
+ }
+ if (!mLayout.canScrollVertically()) {
+ dy = 0;
+ }
if (dx != 0 || dy != 0) {
mViewFlinger.smoothScrollBy(dx, dy);
}
@@ -1124,13 +1265,24 @@
*
* @param velocityX Initial horizontal velocity in pixels per second
* @param velocityY Initial vertical velocity in pixels per second
- * @return true if the fling was started, false if the velocity was too low to fling
+ * @return true if the fling was started, false if the velocity was too low to fling or
+ * LayoutManager does not support scrolling in the axis fling is issued.
+ *
+ * @see LayoutManager#canScrollVertically()
+ * @see LayoutManager#canScrollHorizontally()
*/
public boolean fling(int velocityX, int velocityY) {
- if (Math.abs(velocityX) < mMinFlingVelocity) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot fling without a LayoutManager set. " +
+ "Call setLayoutManager with a non-null argument.");
+ return false;
+ }
+ final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+ final boolean canScrollVertical = mLayout.canScrollVertically();
+ if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
velocityX = 0;
}
- if (Math.abs(velocityY) < mMinFlingVelocity) {
+ if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
velocityY = 0;
}
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
@@ -1156,7 +1308,9 @@
*/
private void stopScrollersInternal() {
mViewFlinger.stop();
- mLayout.stopSmoothScroller();
+ if (mLayout != null) {
+ mLayout.stopSmoothScroller();
+ }
}
/**
@@ -1303,7 +1457,7 @@
}
final FocusFinder ff = FocusFinder.getInstance();
result = ff.findNextFocus(this, focused, direction);
- if (result == null && mAdapter != null) {
+ if (result == null && mAdapter != null && mLayout != null) {
eatRequestLayout();
result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
resumeRequestLayout(false);
@@ -1531,7 +1685,6 @@
startScroll = true;
}
if (startScroll) {
- getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
}
@@ -1606,15 +1759,16 @@
startScroll = true;
}
if (startScroll) {
- getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
final int dx = x - mLastTouchX;
final int dy = y - mLastTouchY;
- scrollByInternal(canScrollHorizontally ? -dx : 0,
- canScrollVertically ? -dy : 0);
+ if (scrollByInternal(
+ canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
}
mLastTouchX = x;
mLastTouchY = y;
@@ -1690,11 +1844,52 @@
} else {
mState.mItemCount = 0;
}
+ if (mLayout == null) {
+ defaultOnMeasure(widthSpec, heightSpec);
+ } else {
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ }
- mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
mState.mInPreLayout = false; // clear
}
+ /**
+ * Used when onMeasure is called before layout manager is set
+ */
+ private void defaultOnMeasure(int widthSpec, int heightSpec) {
+ final int widthMode = MeasureSpec.getMode(widthSpec);
+ final int heightMode = MeasureSpec.getMode(heightSpec);
+ final int widthSize = MeasureSpec.getSize(widthSpec);
+ final int heightSize = MeasureSpec.getSize(heightSpec);
+
+ int width = 0;
+ int height = 0;
+
+ switch (widthMode) {
+ case MeasureSpec.EXACTLY:
+ case MeasureSpec.AT_MOST:
+ width = widthSize;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ width = ViewCompat.getMinimumWidth(this);
+ break;
+ }
+
+ switch (heightMode) {
+ case MeasureSpec.EXACTLY:
+ case MeasureSpec.AT_MOST:
+ height = heightSize;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ height = ViewCompat.getMinimumHeight(this);
+ break;
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -1817,6 +2012,10 @@
Log.e(TAG, "No adapter attached; skipping layout");
return;
}
+ if (mLayout == null) {
+ Log.e(TAG, "No layout manager attached; skipping layout");
+ return;
+ }
mDisappearingViewsInLayoutPass.clear();
eatRequestLayout();
mRunningLayoutOrScroll = true;
@@ -1829,6 +2028,7 @@
ArrayMap<View, Rect> appearingViewInitialBounds = null;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
+ findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
@@ -1952,9 +2152,7 @@
mState.mPreLayoutHolderMap.removeAt(i);
View disappearingItemView = disappearingItem.holder.itemView;
- removeDetachedView(disappearingItemView, false);
mRecycler.unscrapView(disappearingItem.holder);
-
animateDisappearance(disappearingItem);
}
}
@@ -2015,7 +2213,7 @@
}
}
resumeRequestLayout(false);
- mLayout.removeAndRecycleScrapInt(mRecycler, !mState.mRunPredictiveAnimations);
+ mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mState.mRunSimpleAnimations = false;
@@ -2026,6 +2224,69 @@
mRecycler.mChangedScrap.clear();
}
mState.mOldChangedHolders = null;
+
+ if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
+ notifyOnScrolled(0, 0);
+ }
+ }
+
+ private void findMinMaxChildLayoutPositions(int[] into) {
+ final int count = mChildHelper.getChildCount();
+ if (count == 0) {
+ into[0] = 0;
+ into[1] = 0;
+ return;
+ }
+ int minPositionPreLayout = Integer.MAX_VALUE;
+ int maxPositionPreLayout = Integer.MIN_VALUE;
+ for (int i = 0; i < count; ++i) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore()) {
+ continue;
+ }
+ final int pos = holder.getLayoutPosition();
+ if (pos < minPositionPreLayout) {
+ minPositionPreLayout = pos;
+ }
+ if (pos > maxPositionPreLayout) {
+ maxPositionPreLayout = pos;
+ }
+ }
+ into[0] = minPositionPreLayout;
+ into[1] = maxPositionPreLayout;
+ }
+
+ private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) {
+ int count = mChildHelper.getChildCount();
+ if (count == 0) {
+ return minPositionPreLayout != 0 || maxPositionPreLayout != 0;
+ }
+ for (int i = 0; i < count; ++i) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore()) {
+ continue;
+ }
+ final int pos = holder.getLayoutPosition();
+ if (pos < minPositionPreLayout || pos > maxPositionPreLayout) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void removeDetachedView(View child, boolean animate) {
+ ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ if (vh.isTmpDetached()) {
+ vh.clearTmpDetachFlag();
+ } else if (!vh.shouldIgnore()) {
+ throw new IllegalArgumentException("Called removeDetachedView with a view which"
+ + " is not flagged as tmp detached." + vh);
+ }
+ }
+ dispatchChildDetached(child);
+ super.removeDetachedView(child, animate);
}
/**
@@ -2092,7 +2353,7 @@
private void animateDisappearance(ItemHolderInfo disappearingItem) {
View disappearingItemView = disappearingItem.holder.itemView;
- addAnimatingView(disappearingItemView);
+ addAnimatingView(disappearingItem.holder);
int oldLeft = disappearingItem.left;
int oldTop = disappearingItem.top;
int newLeft = disappearingItemView.getLeft();
@@ -2124,8 +2385,7 @@
private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) {
oldHolder.setIsRecyclable(false);
- removeDetachedView(oldHolder.itemView, false);
- addAnimatingView(oldHolder.itemView);
+ addAnimatingView(oldHolder);
oldHolder.mShadowedHolder = newHolder;
mRecycler.unscrapView(oldHolder);
if (DEBUG) {
@@ -2443,6 +2703,21 @@
}
}
+ private void setDataSetChangedAfterLayout() {
+ if (mDataSetHasChangedAfterLayout) {
+ return;
+ }
+ mDataSetHasChangedAfterLayout = true;
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore()) {
+ holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ }
+ }
+ mRecycler.setAdapterPositionsAsUnknown();
+ }
+
/**
* Mark all known views as invalid. Used in response to a, "the whole world might have changed"
* data change event.
@@ -2498,14 +2773,38 @@
}
/**
+ * @deprecated use {@link #getChildAdapterPosition(View)} or
+ * {@link #getChildLayoutPosition(View)}.
+ */
+ @Deprecated
+ public int getChildPosition(View child) {
+ return getChildAdapterPosition(child);
+ }
+
+ /**
* Return the adapter position that the given child view corresponds to.
*
* @param child Child View to query
* @return Adapter position corresponding to the given view or {@link #NO_POSITION}
*/
- public int getChildPosition(View child) {
+ public int getChildAdapterPosition(View child) {
final ViewHolder holder = getChildViewHolderInt(child);
- return holder != null ? holder.getPosition() : NO_POSITION;
+ return holder != null ? holder.getAdapterPosition() : NO_POSITION;
+ }
+
+ /**
+ * Return the adapter position of the given child view as of the latest completed layout pass.
+ * <p>
+ * This position may not be equal to Item's adapter position if there are pending changes
+ * in the adapter which have not been reflected to the layout yet.
+ *
+ * @param child Child View to query
+ * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if
+ * the View is representing a removed item.
+ */
+ public int getChildLayoutPosition(View child) {
+ final ViewHolder holder = getChildViewHolderInt(child);
+ return holder != null ? holder.getLayoutPosition() : NO_POSITION;
}
/**
@@ -2523,15 +2822,61 @@
}
/**
- * Return the ViewHolder for the item in the given position of the data set.
- *
- * @param position The position of the item in the data set of the adapter
- * @return The ViewHolder at <code>position</code>
+ * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or
+ * {@link #findViewHolderForAdapterPosition(int)}
*/
+ @Deprecated
public ViewHolder findViewHolderForPosition(int position) {
return findViewHolderForPosition(position, false);
}
+ /**
+ * Return the ViewHolder for the item in the given position of the data set as of the latest
+ * layout pass.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item at the given
+ * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+ * <p>
+ * Note that when Adapter contents change, ViewHolder positions are not updated until the
+ * next layout calculation. If there are pending adapter updates, the return value of this
+ * method may not match your adapter contents. You can use
+ * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
+ *
+ * @param position The position of the item in the data set of the adapter
+ * @return The ViewHolder at <code>position</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForLayoutPosition(int position) {
+ return findViewHolderForPosition(position, false);
+ }
+
+ /**
+ * Return the ViewHolder for the item in the given position of the data set. Unlike
+ * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending
+ * adapter changes that may not be reflected to the layout yet. On the other hand, if
+ * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been
+ * calculated yet, this method will return <code>null</code> since the new positions of views
+ * are unknown until the layout is calculated.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item at the given
+ * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+ *
+ * @param position The position of the item in the data set of the adapter
+ * @return The ViewHolder at <code>position</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForAdapterPosition(int position) {
+ if (mDataSetHasChangedAfterLayout) {
+ return null;
+ }
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) {
+ return holder;
+ }
+ }
+ return null;
+ }
+
ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
@@ -2541,7 +2886,7 @@
if (holder.mPosition == position) {
return holder;
}
- } else if (holder.getPosition() == position) {
+ } else if (holder.getLayoutPosition() == position) {
return holder;
}
}
@@ -2556,10 +2901,12 @@
* Return the ViewHolder for the item with the given id. The RecyclerView must
* use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
* return a non-null value.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item with the given
+ * <code>id</code> is not laid out, it <em>will not</em> create a new one.
*
* @param id The id for the requested item
- * @return The ViewHolder with the given <code>id</code>, of null if there
- * is no such item.
+ * @return The ViewHolder with the given <code>id</code> or null if there is no such item
*/
public ViewHolder findViewHolderForItemId(long id) {
final int childCount = mChildHelper.getUnfilteredChildCount();
@@ -2722,17 +3069,14 @@
View view = mChildHelper.getChildAt(i);
ViewHolder holder = getChildViewHolder(view);
if (holder != null && holder.mShadowingHolder != null) {
- View shadowingView = holder.mShadowingHolder != null ?
- holder.mShadowingHolder.itemView : null;
- if (shadowingView != null) {
- int left = view.getLeft();
- int top = view.getTop();
- if (left != shadowingView.getLeft() ||
- top != shadowingView.getTop()) {
- shadowingView.layout(left, top,
- left + shadowingView.getWidth(),
- top + shadowingView.getHeight());
- }
+ View shadowingView = holder.mShadowingHolder.itemView;
+ int left = view.getLeft();
+ int top = view.getTop();
+ if (left != shadowingView.getLeft() ||
+ top != shadowingView.getTop()) {
+ shadowingView.layout(left, top,
+ left + shadowingView.getWidth(),
+ top + shadowingView.getHeight());
}
}
}
@@ -2753,7 +3097,6 @@
mRunningLayoutOrScroll = false;
resumeRequestLayout(false);
}
- final boolean fullyConsumedScroll = dx == hresult && dy == vresult;
if (!mItemDecorations.isEmpty()) {
invalidate();
}
@@ -2784,18 +3127,21 @@
}
}
if (hresult != 0 || vresult != 0) {
- // dummy values, View's implementation does not use these.
- onScrollChanged(0, 0, 0, 0);
- if (mScrollListener != null) {
- mScrollListener.onScrolled(RecyclerView.this, hresult, vresult);
- }
+ notifyOnScrolled(hresult, vresult);
}
if (!awakenScrollBars()) {
invalidate();
}
- if (scroller.isFinished() || !fullyConsumedScroll) {
+ final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically()
+ && vresult == dy;
+ final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally()
+ && hresult == dx;
+ final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal
+ || fullyConsumedVertical;
+
+ if (scroller.isFinished() || !fullyConsumedAny) {
setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
} else {
postOnAnimation();
@@ -2824,6 +3170,7 @@
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true;
} else {
+ removeCallbacks(this);
ViewCompat.postOnAnimation(RecyclerView.this, this);
}
}
@@ -2894,6 +3241,14 @@
}
+ private void notifyOnScrolled(int hresult, int vresult) {
+ // dummy values, View's implementation does not use these.
+ onScrollChanged(0, 0, 0, 0);
+ if (mScrollListener != null) {
+ mScrollListener.onScrolled(this, hresult, vresult);
+ }
+ }
+
private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onChanged() {
@@ -2903,10 +3258,10 @@
// This is more important to implement now since this callback will disable all
// animations because we cannot rely on positions.
mState.mStructureChanged = true;
- mDataSetHasChangedAfterLayout = true;
+ setDataSetChangedAfterLayout();
} else {
mState.mStructureChanged = true;
- mDataSetHasChangedAfterLayout = true;
+ setDataSetChangedAfterLayout();
}
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
@@ -3112,11 +3467,7 @@
mViewCacheMax = viewCount;
// first, try the views that can be recycled
for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) {
- tryToRecycleCachedViewAt(i);
- }
- // if we could not recycle enough of them, remove some.
- while (mCachedViews.size() > viewCount) {
- mCachedViews.remove(mCachedViews.size() - 1);
+ recycleCachedViewAt(i);
}
}
@@ -3432,8 +3783,8 @@
* Recycle a detached view. The specified view will be added to a pool of views
* for later rebinding and reuse.
*
- * <p>A view must be fully detached before it may be recycled. If the View is scrapped,
- * it will be removed from scrap list.</p>
+ * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
+ * View is scrapped, it will be removed from scrap list.</p>
*
* @param view Removed view for recycling
* @see LayoutManager#removeAndRecycleView(View, Recycler)
@@ -3442,6 +3793,9 @@
// This public recycle method tries to make view recycle-able since layout manager
// intended to recycle this view (e.g. even if it is in scrap or change cache)
ViewHolder holder = getChildViewHolderInt(view);
+ if (holder.isTmpDetached()) {
+ removeDetachedView(view, false);
+ }
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()){
@@ -3462,33 +3816,32 @@
void recycleAndClearCachedViews() {
final int count = mCachedViews.size();
for (int i = count - 1; i >= 0; i--) {
- tryToRecycleCachedViewAt(i);
+ recycleCachedViewAt(i);
}
mCachedViews.clear();
}
/**
- * Tries to recyle a cached view and removes the view from the list if and only if it
- * is recycled.
+ * Recycles a cached view and removes the view from the list. Views are added to cache
+ * if and only if they are recyclable, so this method does not check it again.
+ * <p>
+ * A small exception to this rule is when the view does not have an animator reference
+ * but transient state is true (due to animations created outside ItemAnimator). In that
+ * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is
+ * still recyclable since Adapter wants to do so.
*
* @param cachedViewIndex The index of the view in cached views list
- * @return True if item is recycled
*/
- boolean tryToRecycleCachedViewAt(int cachedViewIndex) {
+ void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
- Log.d(TAG, "CachedViewHolder to be recycled(if recycleable): " + viewHolder);
+ Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
- if (viewHolder.isRecyclable()) {
- getRecycledViewPool().putRecycledView(viewHolder);
- dispatchViewRecycled(viewHolder);
- mCachedViews.remove(cachedViewIndex);
- return true;
- }
- return false;
+ addViewHolderToRecycledViewPool(viewHolder);
+ mCachedViews.remove(cachedViewIndex);
}
/**
@@ -3504,30 +3857,35 @@
+ (holder.itemView.getParent() != null));
}
+ if (holder.isTmpDetached()) {
+ throw new IllegalArgumentException("Tmp detached view should be removed "
+ + "from RecyclerView before it can be recycled: " + holder);
+ }
+
if (holder.shouldIgnore()) {
throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+ " should first call stopIgnoringView(view) before calling recycle.");
}
- if (holder.isRecyclable()) {
+ //noinspection unchecked
+ final boolean forceRecycle = mAdapter != null
+ && holder.doesTransientStatePreventRecycling()
+ && mAdapter.onFailedToRecycleView(holder);
+ if (forceRecycle || holder.isRecyclable()) {
boolean cached = false;
if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) &&
!holder.isChanged()) {
- // Retire oldest cached views first
- if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) {
- for (int i = 0; i < mCachedViews.size(); i++) {
- if (tryToRecycleCachedViewAt(i)) {
- break;
- }
- }
+ // Retire oldest cached view
+ final int cachedViewSize = mCachedViews.size();
+ if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
+ recycleCachedViewAt(0);
}
- if (mCachedViews.size() < mViewCacheMax) {
+ if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
- getRecycledViewPool().putRecycledView(holder);
- dispatchViewRecycled(holder);
+ addViewHolderToRecycledViewPool(holder);
}
} else if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
@@ -3538,6 +3896,12 @@
mState.onViewRecycled(holder);
}
+ void addViewHolderToRecycledViewPool(ViewHolder holder) {
+ ViewCompat.setAccessibilityDelegate(holder.itemView, null);
+ getRecycledViewPool().putRecycledView(holder);
+ dispatchViewRecycled(holder);
+ }
+
/**
* Used as a fast path for unscrapping and recycling a view during a bulk operation.
* The caller must call {@link #clearScrap()} when it's done to update the recycler's
@@ -3614,7 +3978,7 @@
// find by position
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
- if (!holder.wasReturnedFromScrap() && holder.getPosition() == position) {
+ if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
@@ -3651,7 +4015,7 @@
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
- if (!holder.wasReturnedFromScrap() && holder.getPosition() == position
+ if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
if (type != INVALID_TYPE && holder.getItemViewType() != type) {
Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" +
@@ -3678,7 +4042,7 @@
final ViewHolder holder = mCachedViews.get(i);
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapViewForId
- if (!holder.isInvalid() && holder.getPosition() == position) {
+ if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i);
}
@@ -3735,7 +4099,7 @@
}
return holder;
} else if (!dryRun) {
- tryToRecycleCachedViewAt(i);
+ recycleCachedViewAt(i);
}
}
}
@@ -3794,7 +4158,7 @@
final int cachedCount = mCachedViews.size();
for (int i = 0; i < cachedCount; i++) {
final ViewHolder holder = mCachedViews.get(i);
- if (holder != null && holder.getPosition() >= insertedAt) {
+ if (holder != null && holder.getLayoutPosition() >= insertedAt) {
if (DEBUG) {
Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " +
holder + " now at position " + (holder.mPosition + count));
@@ -3816,28 +4180,16 @@
for (int i = cachedCount - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder != null) {
- if (holder.getPosition() >= removedEnd) {
+ if (holder.getLayoutPosition() >= removedEnd) {
if (DEBUG) {
Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
" holder " + holder + " now at position " +
(holder.mPosition - count));
}
holder.offsetPosition(-count, applyToPreLayout);
- } else if (holder.getPosition() >= removedFrom) {
+ } else if (holder.getLayoutPosition() >= removedFrom) {
// Item for this view was removed. Dump it from the cache.
- if (!tryToRecycleCachedViewAt(i)) {
- // if we cannot recycle it, at least invalidate so that we won't return
- // it by position.
- holder.addFlags(ViewHolder.FLAG_INVALID);
- if (DEBUG) {
- Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
- " holder " + holder + " now flagged as invalid because it "
- + "could not be recycled");
- }
- } else if (DEBUG) {
- Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
- " holder " + holder + " now placed in pool");
- }
+ recycleCachedViewAt(i);
}
}
}
@@ -3873,7 +4225,7 @@
continue;
}
- final int pos = holder.getPosition();
+ final int pos = holder.getLayoutPosition();
if (pos >= positionStart && pos < positionEnd) {
holder.addFlags(ViewHolder.FLAG_UPDATE);
// cached views should not be flagged as changed because this will cause them
@@ -3882,6 +4234,16 @@
}
}
+ void setAdapterPositionsAsUnknown() {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ }
+ }
+ }
+
void markKnownViewsInvalid() {
if (mAdapter != null && mAdapter.hasStableIds()) {
final int cachedCount = mCachedViews.size();
@@ -3892,17 +4254,9 @@
}
}
} else {
- // we cannot re-use cached views in this case. Recycle the ones we can and flag
- // the remaining as invalid so that they can be recycled later on (when their
- // animations end.)
- for (int i = mCachedViews.size() - 1; i >= 0; i--) {
- if (!tryToRecycleCachedViewAt(i)) {
- final ViewHolder holder = mCachedViews.get(i);
- holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
- }
- }
+ // we cannot re-use cached views in this case. Recycle them all
+ recycleAndClearCachedViews();
}
-
}
void clearOldPositions() {
@@ -4012,8 +4366,8 @@
* is invalidated or the new position cannot be determined. For this reason, you should only
* use the <code>position</code> parameter while acquiring the related data item inside this
* method and should not keep a copy of it. If you need the position of an item later on
- * (e.g. in a click listener), use {@link ViewHolder#getPosition()} which will have the
- * updated position.
+ * (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will have
+ * the updated adapter position.
*
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
@@ -4047,7 +4401,8 @@
}
onBindViewHolder(holder, position);
holder.setFlags(ViewHolder.FLAG_BOUND,
- ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+ ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
}
/**
@@ -4128,6 +4483,44 @@
}
/**
+ * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled
+ * due to its transient state. Upon receiving this callback, Adapter can clear the
+ * animation(s) that effect the View's transient state and return <code>true</code> so that
+ * the View can be recycled. Keep in mind that the View in question is already removed from
+ * the RecyclerView.
+ * <p>
+ * In some cases, it is acceptable to recycle a View although it has transient state. Most
+ * of the time, this is a case where the transient state will be cleared in
+ * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position.
+ * For this reason, RecyclerView leaves the decision to the Adapter and uses the return
+ * value of this method to decide whether the View should be recycled or not.
+ * <p>
+ * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you
+ * should never receive this callback because RecyclerView keeps those Views as children
+ * until their animations are complete. This callback is useful when children of the item
+ * views create animations which may not be easy to implement using an {@link ItemAnimator}.
+ * <p>
+ * You should <em>never</em> fix this issue by calling
+ * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called
+ * <code>holder.itemView.setHasTransientState(true);</code>. Each
+ * <code>View.setHasTransientState(true)</code> call must be matched by a
+ * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View
+ * may become inconsistent. You should always prefer to end or cancel animations that are
+ * triggering the transient state instead of handling it manually.
+ *
+ * @param holder The ViewHolder containing the View that could not be recycled due to its
+ * transient state.
+ * @return True if the View should be recycled, false otherwise. Note that if this method
+ * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of
+ * the View and recycle it regardless. If this method returns <code>false</code>,
+ * RecyclerView will check the View's transient state again before giving a final decision.
+ * Default implementation returns false.
+ */
+ public boolean onFailedToRecycleView(VH holder) {
+ return false;
+ }
+
+ /**
* Called when a view created by this adapter has been attached to a window.
*
* <p>This can be used as a reasonable signal that the view is about to be seen
@@ -4196,6 +4589,26 @@
}
/**
+ * Called by RecyclerView when it starts observing this Adapter.
+ * <p>
+ * Keep in mind that same adapter may be observed by multiple RecyclerViews.
+ *
+ * @param recyclerView The RecyclerView instance which started observing this adapter.
+ * @see #onDetachedFromRecyclerView(RecyclerView)
+ */
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ }
+
+ /**
+ * Called by RecyclerView when it stops observing this Adapter.
+ *
+ * @param recyclerView The RecyclerView instance which stopped observing this adapter.
+ * @see #onAttachedToRecyclerView(RecyclerView)
+ */
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ }
+
+ /**
* Notify any registered observers that the data set has changed.
*
* <p>There are two different classes of data change events, item changes and structural
@@ -4892,19 +5305,19 @@
// Only remove non-animating views
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
mChildHelper.removeViewAt(i);
}
}
/**
- * Returns the adapter position of the item represented by the given View.
+ * Returns the adapter position of the item represented by the given View. This does not
+ * contain any adapter changes that might have happened after the last layout.
*
* @param view The view to query
* @return The adapter position of the item which is rendered by this View.
*/
public int getPosition(View view) {
- return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewPosition();
+ return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
}
/**
@@ -4918,7 +5331,6 @@
}
/**
- * <p>
* Finds the view which represents the given adapter position.
* <p>
* This method traverses each child since it has no information about child order.
@@ -4929,7 +5341,7 @@
*
* @param position Position of the item in adapter
* @return The child view that represents the given position or null if the position is not
- * visible
+ * laid out
*/
public View findViewByPosition(int position) {
final int childCount = getChildCount();
@@ -4939,7 +5351,7 @@
if (vh == null) {
continue;
}
- if (vh.getPosition() == position && !vh.shouldIgnore() &&
+ if (vh.getLayoutPosition() == position && !vh.shouldIgnore() &&
(mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) {
return child;
}
@@ -5367,23 +5779,22 @@
* call remove and invalidate RecyclerView to ensure UI update.
*
* @param recycler Recycler
- * @param remove Whether scrapped views should be removed from ViewGroup or not. This
- * method will invalidate RecyclerView if it removes any scrapped child.
*/
- void removeAndRecycleScrapInt(Recycler recycler, boolean remove) {
+ void removeAndRecycleScrapInt(Recycler recycler) {
final int scrapCount = recycler.getScrapCount();
for (int i = 0; i < scrapCount; i++) {
final View scrap = recycler.getScrapViewAt(i);
- if (getChildViewHolderInt(scrap).shouldIgnore()) {
+ final ViewHolder vh = getChildViewHolderInt(scrap);
+ if (vh.shouldIgnore()) {
continue;
}
- if (remove) {
+ if (vh.isTmpDetached()) {
mRecyclerView.removeDetachedView(scrap, false);
}
recycler.quickRecycleScrapView(scrap);
}
recycler.clearScrap();
- if (remove && scrapCount > 0) {
+ if (scrapCount > 0) {
mRecyclerView.invalidate();
}
}
@@ -5748,8 +6159,8 @@
final int parentBottom = getHeight() - getPaddingBottom();
final int childLeft = child.getLeft() + rect.left;
final int childTop = child.getTop() + rect.top;
- final int childRight = childLeft + rect.right;
- final int childBottom = childTop + rect.bottom;
+ final int childRight = childLeft + rect.width();
+ final int childBottom = childTop + rect.height();
final int offScreenLeft = Math.min(0, childLeft - parentLeft);
final int offScreenTop = Math.min(0, childTop - parentTop);
@@ -5784,7 +6195,8 @@
*/
@Deprecated
public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
- return false;
+ // eat the request if we are in the middle of a scroll or layout
+ return isSmoothScrolling() || parent.mRunningLayoutOrScroll;
}
/**
@@ -6016,37 +6428,7 @@
* @param heightSpec Height {@link android.view.View.MeasureSpec}
*/
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
- final int widthMode = MeasureSpec.getMode(widthSpec);
- final int heightMode = MeasureSpec.getMode(heightSpec);
- final int widthSize = MeasureSpec.getSize(widthSpec);
- final int heightSize = MeasureSpec.getSize(heightSpec);
-
- int width = 0;
- int height = 0;
-
- switch (widthMode) {
- case MeasureSpec.EXACTLY:
- case MeasureSpec.AT_MOST:
- width = widthSize;
- break;
- case MeasureSpec.UNSPECIFIED:
- default:
- width = getMinimumWidth();
- break;
- }
-
- switch (heightMode) {
- case MeasureSpec.EXACTLY:
- case MeasureSpec.AT_MOST:
- height = heightSize;
- break;
- case MeasureSpec.UNSPECIFIED:
- default:
- height = getMinimumHeight();
- break;
- }
-
- setMeasuredDimension(width, height);
+ mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
/**
@@ -6220,10 +6602,13 @@
}
// called by accessibility delegate
- void onInitializeAccessibilityNodeInfoForItem(View host,
- AccessibilityNodeInfoCompat info) {
- onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, mRecyclerView.mState,
- host, info);
+ void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) {
+ final ViewHolder vh = getChildViewHolderInt(host);
+ // avoid trying to create accessibility node info for removed children
+ if (vh != null && !vh.isRemoved()) {
+ onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler,
+ mRecyclerView.mState, host, info);
+ }
}
/**
@@ -6390,7 +6775,7 @@
/**
* Called by AccessibilityDelegate when an accessibility action is requested on one of the
- * chidren of LayoutManager.
+ * children of LayoutManager.
* <p>
* Default implementation does not do anything.
*
@@ -6487,9 +6872,15 @@
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
*
- * <p>If this ItemDecoration does not affect the positioning of item views it should set
+ * <p>
+ * If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of <code>outRect</code> (left, top, right, bottom) to zero
- * before returning.</p>
+ * before returning.
+ *
+ * <p>
+ * If you need to access Adapter for additional data, you can call
+ * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
+ * View.
*
* @param outRect Rect to receive the output.
* @param view The child view to decorate
@@ -6497,7 +6888,7 @@
* @param state The current state of RecyclerView.
*/
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
- getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewPosition(),
+ getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
@@ -6558,6 +6949,9 @@
/**
* Callback method to be invoked when the RecyclerView has been scrolled. This will be
* called after the scroll has completed.
+ * <p>
+ * This callback will also be called if visible item range changes after a layout
+ * calculation. In that case, dx and dy will be 0.
*
* @param recyclerView The RecyclerView which scrolled.
* @param dx The amount of horizontal scroll.
@@ -6660,6 +7054,21 @@
*/
static final int FLAG_IGNORE = 1 << 7;
+ /**
+ * When the View is detached form the parent, we set this flag so that we can take correct
+ * action when we need to remove it or add it back.
+ */
+ static final int FLAG_TMP_DETACHED = 1 << 8;
+
+ /**
+ * Set when we can no longer determine the adapter position of this ViewHolder until it is
+ * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is
+ * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon
+ * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is
+ * re-calculated.
+ */
+ static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9;
+
private int mFlags;
private int mIsRecyclableCount = 0;
@@ -6712,11 +7121,77 @@
return (mFlags & FLAG_IGNORE) != 0;
}
+ /**
+ * @deprecated This method is deprecated because its meaning is ambiguous due to the async
+ * handling of adapter updates. Please use {@link #getLayoutPosition()} or
+ * {@link #getAdapterPosition()} depending on your use case.
+ *
+ * @see #getLayoutPosition()
+ * @see #getAdapterPosition()
+ */
+ @Deprecated
public final int getPosition() {
return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
}
/**
+ * Returns the position of the ViewHolder in terms of the latest layout pass.
+ * <p>
+ * This position is mostly used by RecyclerView components to be consistent while
+ * RecyclerView lazily processes adapter updates.
+ * <p>
+ * For performance and animation reasons, RecyclerView batches all adapter updates until the
+ * next layout pass. This may cause mismatches between the Adapter position of the item and
+ * the position it had in the latest layout calculations.
+ * <p>
+ * LayoutManagers should always call this method while doing calculations based on item
+ * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State},
+ * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position
+ * of the item.
+ * <p>
+ * If LayoutManager needs to call an external method that requires the adapter position of
+ * the item, it can use {@link #getAdapterPosition()} or
+ * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
+ *
+ * @return Returns the adapter position of the ViewHolder in the latest layout pass.
+ * @see #getAdapterPosition()
+ */
+ public final int getLayoutPosition() {
+ return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
+ }
+
+ /**
+ * Returns the Adapter position of the item represented by this ViewHolder.
+ * <p>
+ * Note that this might be different than the {@link #getLayoutPosition()} if there are
+ * pending adapter updates but a new layout pass has not happened yet.
+ * <p>
+ * RecyclerView does not handle any adapter updates until the next layout traversal. This
+ * may create temporary inconsistencies between what user sees on the screen and what
+ * adapter contents have. This inconsistency is not important since it will be less than
+ * 16ms but it might be a problem if you want to use ViewHolder position to access the
+ * adapter. Sometimes, you may need to get the exact adapter position to do
+ * some actions in response to user events. In that case, you should use this method which
+ * will calculate the Adapter position of the ViewHolder.
+ * <p>
+ * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
+ * next layout pass, the return value of this method will be {@link #NO_POSITION}.
+ *
+ * @return The adapter position of the item if it still exists in the adapter.
+ * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
+ * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
+ * layout pass or the ViewHolder has been removed from the RecyclerView.
+ */
+ public final int getAdapterPosition() {
+ final ViewParent parent = itemView.getParent();
+ if (!(parent instanceof RecyclerView)) {
+ return -1;
+ }
+ final RecyclerView rv = (RecyclerView) parent;
+ return rv.getAdapterPositionFor(this);
+ }
+
+ /**
* When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
* to perform animations.
* <p>
@@ -6764,6 +7239,10 @@
mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
}
+ void clearTmpDetachFlag() {
+ mFlags = mFlags & ~FLAG_TMP_DETACHED;
+ }
+
void stopIgnoring() {
mFlags = mFlags & ~FLAG_IGNORE;
}
@@ -6792,6 +7271,18 @@
return (mFlags & FLAG_REMOVED) != 0;
}
+ boolean hasAnyOfTheFlags(int flags) {
+ return (mFlags & flags) != 0;
+ }
+
+ boolean isTmpDetached() {
+ return (mFlags & FLAG_TMP_DETACHED) != 0;
+ }
+
+ boolean isAdapterPositionUnknown() {
+ return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0;
+ }
+
void setFlags(int flags, int mask) {
mFlags = (mFlags & ~mask) | (flags & mask);
}
@@ -6823,7 +7314,10 @@
if (isRemoved()) sb.append(" removed");
if (shouldIgnore()) sb.append(" ignored");
if (isChanged()) sb.append(" changed");
+ if (isTmpDetached()) sb.append(" tmpDetached");
if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
+ if (!isAdapterPositionUnknown()) sb.append("undefined adapter position");
+
if (itemView.getParent() == null) sb.append(" no parent");
sb.append("}");
return sb.toString();
@@ -6869,6 +7363,30 @@
return (mFlags & FLAG_NOT_RECYCLABLE) == 0 &&
!ViewCompat.hasTransientState(itemView);
}
+
+ /**
+ * Returns whether we have animations referring to this view holder or not.
+ * This is similar to isRecyclable flag but does not check transient state.
+ */
+ private boolean shouldBeKeptAsChild() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) != 0;
+ }
+
+ /**
+ * @return True if ViewHolder is not refenrenced by RecyclerView animations but has
+ * transient state which will prevent it from being recycled.
+ */
+ private boolean doesTransientStatePreventRecycling() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView);
+ }
+ }
+
+ private int getAdapterPositionFor(ViewHolder viewHolder) {
+ if (viewHolder.hasAnyOfTheFlags(
+ ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
+ return RecyclerView.NO_POSITION;
+ }
+ return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
}
/**
@@ -6949,13 +7467,33 @@
}
/**
- * Returns the position that the view this LayoutParams is attached to corresponds to.
- *
- * @return the adapter position this view was bound from
+ * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()}
*/
public int getViewPosition() {
return mViewHolder.getPosition();
}
+
+ /**
+ * Returns the adapter position that the view this LayoutParams is attached to corresponds
+ * to as of latest layout calculation.
+ *
+ * @return the adapter position this view as of latest layout pass
+ */
+ public int getViewLayoutPosition() {
+ return mViewHolder.getLayoutPosition();
+ }
+
+ /**
+ * Returns the up-to-date adapter position that the view this LayoutParams is attached to
+ * corresponds to.
+ *
+ * @return the up-to-date adapter position this view. It may return
+ * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or
+ * its up-to-date position cannot be calculated.
+ */
+ public int getViewAdapterPosition() {
+ return mViewHolder.getAdapterPosition();
+ }
}
/**
@@ -7122,10 +7660,10 @@
}
/**
- * @see RecyclerView#getChildPosition(android.view.View)
+ * @see RecyclerView#getChildLayoutPosition(android.view.View)
*/
public int getChildPosition(View view) {
- return mRecyclerView.getChildPosition(view);
+ return mRecyclerView.getChildLayoutPosition(view);
}
/**
@@ -7202,7 +7740,6 @@
* @param state Transient state of RecyclerView
* @param action Action instance that you should update to define final scroll action
* towards the targetView
- * @return An {@link Action} to finalize the smooth scrolling
*/
abstract protected void onTargetFound(View targetView, State state, Action action);
@@ -7679,14 +8216,15 @@
@Override
public void onRemoveFinished(ViewHolder item) {
item.setIsRecyclable(true);
- removeAnimatingView(item.itemView);
- removeDetachedView(item.itemView, false);
+ if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
+ removeDetachedView(item.itemView, false);
+ }
}
@Override
public void onAddFinished(ViewHolder item) {
item.setIsRecyclable(true);
- if (item.isRecyclable()) {
+ if (!item.shouldBeKeptAsChild()) {
removeAnimatingView(item.itemView);
}
}
@@ -7694,7 +8232,7 @@
@Override
public void onMoveFinished(ViewHolder item) {
item.setIsRecyclable(true);
- if (item.isRecyclable()) {
+ if (!item.shouldBeKeptAsChild()) {
removeAnimatingView(item.itemView);
}
}
@@ -7736,7 +8274,7 @@
// always null this because an OldViewHolder can never become NewViewHolder w/o being
// recycled.
item.mShadowingHolder = null;
- if (item.isRecyclable()) {
+ if (!item.shouldBeKeptAsChild()) {
removeAnimatingView(item.itemView);
}
}
@@ -7773,7 +8311,7 @@
private long mMoveDuration = 250;
private long mChangeDuration = 250;
- private boolean mSupportsChangeAnimations = false;
+ private boolean mSupportsChangeAnimations = true;
/**
* Gets the current duration for which all move animations will run.
@@ -7858,12 +8396,11 @@
/**
* Sets whether this ItemAnimator supports animations of item change events.
- * By default, ItemAnimator only supports animations when items are added or removed.
- * By setting this property to true, actions on the data set which change the
- * contents of items may also be animated. What those animations are is left
+ * If you set this property to false, actions on the data set which change the
+ * contents of items will not be animated. What those animations are is left
* up to the discretion of the ItemAnimator subclass, in its
* {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
- * The value of this property is false by default.
+ * The value of this property is true by default.
*
* @see Adapter#notifyItemChanged(int)
* @see Adapter#notifyItemRangeChanged(int, int)
diff --git a/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java b/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
index 0903f64..724fac8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
@@ -33,17 +33,20 @@
endChild == null) {
return 0;
}
- final int minPosition = Math.min(lm.getPosition(startChild), lm.getPosition(endChild));
- final int maxPosition = Math.max(lm.getPosition(startChild), lm.getPosition(endChild));
+ final int minPosition = Math.min(lm.getPosition(startChild),
+ lm.getPosition(endChild));
+ final int maxPosition = Math.max(lm.getPosition(startChild),
+ lm.getPosition(endChild));
final int itemsBefore = reverseLayout
? Math.max(0, state.getItemCount() - maxPosition - 1)
- : Math.max(0, minPosition - 1);
+ : Math.max(0, minPosition);
if (!smoothScrollbarEnabled) {
return itemsBefore;
}
final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) -
orientation.getDecoratedStart(startChild));
- final int itemRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
+ final int itemRange = Math.abs(lm.getPosition(startChild) -
+ lm.getPosition(endChild)) + 1;
final float avgSizePerRow = (float) laidOutArea / itemRange;
return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
@@ -86,7 +89,8 @@
// smooth scrollbar enabled. try to estimate better.
final int laidOutArea = orientation.getDecoratedEnd(endChild) -
orientation.getDecoratedStart(startChild);
- final int laidOutRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild))
+ final int laidOutRange = Math.abs(lm.getPosition(startChild) -
+ lm.getPosition(endChild))
+ 1;
// estimate a size for full list.
return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index aab16e4..0abed16 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -74,17 +74,18 @@
* and move items to correct positions with animations.
* <p>
* For example, if LayoutManager ends up with the following layout due to adapter changes:
- * <code>
+ * <pre>
* AAA
* _BC
* DDD
- * </code>
+ * </pre>
+ * <p>
* It will animate to the following state:
- * <code>
+ * <pre>
* AAA
* BC_
* DDD
- * </code>
+ * </pre>
*/
public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
@@ -239,7 +240,7 @@
}
int invalidGapDir = mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
- .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir);
+ .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true);
if (invalidFsi == null) {
mLaidOutInvalidFullSpan = false;
mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
@@ -247,7 +248,7 @@
}
final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
.getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
- invalidGapDir * -1);
+ invalidGapDir * -1, true);
if (validFsi == null) {
mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
} else {
@@ -530,8 +531,6 @@
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
ensureOrientationHelper();
- // Update adapter size.
- mLazySpanLookup.mAdapterSize = state.getItemCount();
final AnchorInfo anchorInfo = mAnchorInfo;
anchorInfo.reset();
@@ -913,9 +912,10 @@
if (getChildCount() == 0) {
return 0;
}
+ ensureOrientationHelper();
return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
- findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
- , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+ findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
+ , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
this, mSmoothScrollbarEnabled, mShouldReverseLayout);
}
@@ -933,9 +933,10 @@
if (getChildCount() == 0) {
return 0;
}
+ ensureOrientationHelper();
return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
- findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
- , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+ findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
+ , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
this, mSmoothScrollbarEnabled);
}
@@ -953,9 +954,10 @@
if (getChildCount() == 0) {
return 0;
}
+ ensureOrientationHelper();
return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
- findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
- , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+ findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
+ , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
this, mSmoothScrollbarEnabled);
}
@@ -967,12 +969,28 @@
private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) {
if (lp.mFullSpan) {
if (mOrientation == VERTICAL) {
- measureChildWithDecorationsAndMargin(child, mFullSizeSpec, mHeightSpec);
+ measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
+ getSpecForDimension(lp.height, mHeightSpec));
} else {
- measureChildWithDecorationsAndMargin(child, mWidthSpec, mFullSizeSpec);
+ measureChildWithDecorationsAndMargin(child,
+ getSpecForDimension(lp.width, mWidthSpec), mFullSizeSpec);
}
} else {
- measureChildWithDecorationsAndMargin(child, mWidthSpec, mHeightSpec);
+ if (mOrientation == VERTICAL) {
+ measureChildWithDecorationsAndMargin(child, mWidthSpec,
+ getSpecForDimension(lp.height, mHeightSpec));
+ } else {
+ measureChildWithDecorationsAndMargin(child,
+ getSpecForDimension(lp.width, mWidthSpec), mHeightSpec);
+ }
+ }
+ }
+
+ private int getSpecForDimension(int dim, int defaultSpec) {
+ if (dim < 0) {
+ return defaultSpec;
+ } else {
+ return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
}
}
@@ -1028,6 +1046,7 @@
}
if (getChildCount() > 0) {
+ ensureOrientationHelper();
state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
: getFirstChildPosition();
state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
@@ -1087,8 +1106,8 @@
if (getChildCount() > 0) {
final AccessibilityRecordCompat record = AccessibilityEventCompat
.asRecord(event);
- final View start = findFirstVisibleItemClosestToStart(false);
- final View end = findFirstVisibleItemClosestToEnd(false);
+ final View start = findFirstVisibleItemClosestToStart(false, true);
+ final View end = findFirstVisibleItemClosestToEnd(false, true);
if (start == null || end == null) {
return;
}
@@ -1106,11 +1125,12 @@
/**
* Finds the first fully visible child to be used as an anchor child if span count changes when
- * state is restored.
+ * state is restored. If no children is fully visible, returns a partially visible child instead
+ * of returning null.
*/
int findFirstVisibleItemPositionInt() {
- final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) :
- findFirstVisibleItemClosestToStart(true);
+ final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true, true) :
+ findFirstVisibleItemClosestToStart(true, true);
return first == null ? NO_POSITION : getPosition(first);
}
@@ -1132,31 +1152,42 @@
return super.getColumnCountForAccessibility(recycler, state);
}
- View findFirstVisibleItemClosestToStart(boolean fullyVisible) {
+ View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPartiallyVisible) {
+ ensureOrientationHelper();
final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
final int limit = getChildCount();
- for (int i = 0; i < limit; i ++) {
+ View partiallyVisible = null;
+ for (int i = 0; i < limit; i++) {
final View child = getChildAt(i);
- if ((!fullyVisible || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart)
- && mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
- return child;
+ if (mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
+ if ((!fullyVisible
+ || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart)) {
+ return child;
+ } else if (acceptPartiallyVisible && partiallyVisible == null) {
+ partiallyVisible = child;
+ }
}
}
- return null;
+ return partiallyVisible;
}
- View findFirstVisibleItemClosestToEnd(boolean fullyVisible) {
+ View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartiallyVisible) {
+ ensureOrientationHelper();
final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
- for (int i = getChildCount() - 1; i >= 0; i --) {
+ View partiallyVisible = null;
+ for (int i = getChildCount() - 1; i >= 0; i--) {
final View child = getChildAt(i);
- if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart && (!fullyVisible
- || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd)) {
- return child;
+ if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart) {
+ if (!fullyVisible || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
+ return child;
+ } else if (acceptPartiallyVisible && partiallyVisible == null) {
+ partiallyVisible = child;
+ }
}
}
- return null;
+ return partiallyVisible;
}
private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
@@ -1274,7 +1305,23 @@
*/
private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
- mLazySpanLookup.invalidateAfter(positionStart);
+ final int affectedRangeEnd;// exclusive
+ final int affectedRangeStart;// inclusive
+
+ if (cmd == AdapterHelper.UpdateOp.MOVE) {
+ if (positionStart < itemCountOrToPosition) {
+ affectedRangeEnd = itemCountOrToPosition + 1;
+ affectedRangeStart = positionStart;
+ } else {
+ affectedRangeEnd = positionStart + 1;
+ affectedRangeStart = itemCountOrToPosition;
+ }
+ } else {
+ affectedRangeStart = positionStart;
+ affectedRangeEnd = positionStart + itemCountOrToPosition;
+ }
+
+ mLazySpanLookup.invalidateAfter(affectedRangeStart);
switch (cmd) {
case AdapterHelper.UpdateOp.ADD:
mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
@@ -1289,12 +1336,12 @@
break;
}
- if (positionStart + itemCountOrToPosition <= minPosition) {
+ if (affectedRangeEnd <= minPosition) {
return;
-
}
+
int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
- if (positionStart <= maxPosition) {
+ if (affectedRangeStart <= maxPosition) {
requestLayout();
}
}
@@ -1332,17 +1379,10 @@
while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
View view = layoutState.next(recycler);
LayoutParams lp = ((LayoutParams) view.getLayoutParams());
- if (layoutState.mLayoutDirection == LAYOUT_END) {
- addView(view);
- } else {
- addView(view, 0);
- }
- measureChildWithDecorationsAndMargin(view, lp);
-
- final int position = lp.getViewPosition();
+ final int position = lp.getViewLayoutPosition();
final int spanIndex = mLazySpanLookup.getSpan(position);
Span currentSpan;
- boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
+ final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
if (assignSpan) {
currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
mLazySpanLookup.setSpan(position, currentSpan);
@@ -1355,9 +1395,17 @@
}
currentSpan = mSpans[spanIndex];
}
+ // assign span before measuring so that item decorators can get updated span index
+ lp.mSpan = currentSpan;
+ if (layoutState.mLayoutDirection == LAYOUT_END) {
+ addView(view);
+ } else {
+ addView(view, 0);
+ }
+ measureChildWithDecorationsAndMargin(view, lp);
+
final int start;
final int end;
-
if (layoutState.mLayoutDirection == LAYOUT_END) {
start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
: currentSpan.getEndLine(defaultNewViewLine);
@@ -1383,11 +1431,27 @@
}
// check if this item may create gaps in the future
- if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD && assignSpan) {
- mLaidOutInvalidFullSpan = true;
- }
+ if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD) {
+ if (assignSpan) {
+ mLaidOutInvalidFullSpan = true;
+ } else {
+ final boolean hasInvalidGap;
+ if (layoutState.mLayoutDirection == LAYOUT_END) {
+ hasInvalidGap = !areAllEndsEqual();
+ } else { // layoutState.mLayoutDirection == LAYOUT_START
+ hasInvalidGap = !areAllStartsEqual();
+ }
+ if (hasInvalidGap) {
+ final LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup
+ .getFullSpanItem(position);
+ if (fullSpanItem != null) {
+ fullSpanItem.mHasUnwantedGapAfter = true;
+ }
+ mLaidOutInvalidFullSpan = true;
+ }
+ }
- lp.mSpan = currentSpan;
+ }
attachViewToSpans(view, lp, layoutState);
final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
: currentSpan.mIndex * mSizePerSpan +
@@ -1484,7 +1548,7 @@
private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (DEBUG) {
- Log.d(TAG, "layout decorated pos: " + lp.getViewPosition() + ", span:"
+ Log.d(TAG, "layout decorated pos: " + lp.getViewLayoutPosition() + ", span:"
+ lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan
+ ". l:" + left + ",t:" + top
+ ", r:" + right + ", b:" + bottom);
@@ -1539,6 +1603,26 @@
return minStart;
}
+ boolean areAllEndsEqual() {
+ int end = mSpans[0].getEndLine(Span.INVALID_LINE);
+ for (int i = 1; i < mSpanCount; i++) {
+ if (mSpans[i].getEndLine(Span.INVALID_LINE) != end) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean areAllStartsEqual() {
+ int start = mSpans[0].getStartLine(Span.INVALID_LINE);
+ for (int i = 1; i < mSpanCount; i++) {
+ if (mSpans[i].getStartLine(Span.INVALID_LINE) != start) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private int getMaxEnd(int def) {
int maxEnd = mSpans[0].getEndLine(def);
for (int i = 1; i < mSpanCount; i++) {
@@ -1853,6 +1937,10 @@
/**
* LayoutParams used by StaggeredGridLayoutManager.
+ * <p>
+ * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
+ * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
+ * expected to fill all of the space given to it.
*/
public static class LayoutParams extends RecyclerView.LayoutParams {
@@ -1953,7 +2041,7 @@
mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
if (lp.mFullSpan) {
LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
- .getFullSpanItem(lp.getViewPosition());
+ .getFullSpanItem(lp.getViewLayoutPosition());
if (fsi != null && fsi.mGapDir == LAYOUT_START) {
mCachedStart -= fsi.getGapForSpan(mIndex);
}
@@ -1987,7 +2075,7 @@
mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
if (lp.mFullSpan) {
LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
- .getFullSpanItem(lp.getViewPosition());
+ .getFullSpanItem(lp.getViewLayoutPosition());
if (fsi != null && fsi.mGapDir == LAYOUT_END) {
mCachedEnd += fsi.getGapForSpan(mIndex);
}
@@ -2042,7 +2130,7 @@
return;
}
if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) ||
- (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding()) ) {
+ (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding())) {
return;
}
if (offset != INVALID_OFFSET) {
@@ -2151,26 +2239,26 @@
public int findFirstVisibleItemPosition() {
return mReverseLayout
- ? findOneVisibleChild(mViews.size() -1, -1, false)
+ ? findOneVisibleChild(mViews.size() - 1, -1, false)
: findOneVisibleChild(0, mViews.size(), false);
}
public int findFirstCompletelyVisibleItemPosition() {
return mReverseLayout
- ? findOneVisibleChild(mViews.size() -1, -1, true)
+ ? findOneVisibleChild(mViews.size() - 1, -1, true)
: findOneVisibleChild(0, mViews.size(), true);
}
public int findLastVisibleItemPosition() {
return mReverseLayout
? findOneVisibleChild(0, mViews.size(), false)
- : findOneVisibleChild(mViews.size() -1, -1, false);
+ : findOneVisibleChild(mViews.size() - 1, -1, false);
}
public int findLastCompletelyVisibleItemPosition() {
return mReverseLayout
? findOneVisibleChild(0, mViews.size(), true)
- : findOneVisibleChild(mViews.size() -1, -1, true);
+ : findOneVisibleChild(mViews.size() - 1, -1, true);
}
int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
@@ -2203,7 +2291,6 @@
private static final int MIN_SIZE = 10;
int[] mData;
- int mAdapterSize; // we don't want to grow beyond that, unless it grows
List<FullSpanItem> mFullSpanItems;
@@ -2261,9 +2348,6 @@
while (len <= position) {
len *= 2;
}
- if (len > mAdapterSize) {
- len = mAdapterSize;
- }
return len;
}
@@ -2411,17 +2495,23 @@
* @param minPos inclusive
* @param maxPos exclusive
* @param gapDir if not 0, returns FSIs on in that direction
+ * @param hasUnwantedGapAfter If true, when full span item has unwanted gaps, it will be
+ * returned even if its gap direction does not match.
*/
- public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir) {
+ public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir,
+ boolean hasUnwantedGapAfter) {
if (mFullSpanItems == null) {
return null;
}
- for (int i = 0; i < mFullSpanItems.size(); i++) {
+ final int limit = mFullSpanItems.size();
+ for (int i = 0; i < limit; i++) {
FullSpanItem fsi = mFullSpanItems.get(i);
if (fsi.mPosition >= maxPos) {
return null;
}
- if (fsi.mPosition >= minPos && (gapDir == 0 || fsi.mGapDir == gapDir)) {
+ if (fsi.mPosition >= minPos
+ && (gapDir == 0 || fsi.mGapDir == gapDir ||
+ (hasUnwantedGapAfter && fsi.mHasUnwantedGapAfter))) {
return fsi;
}
}
@@ -2436,10 +2526,15 @@
int mPosition;
int mGapDir;
int[] mGapPerSpan;
+ // A full span may be laid out in primary direction but may have gaps due to
+ // invalidation of views after it. This is recorded during a reverse scroll and if
+ // view is still on the screen after scroll stops, we have to recalculate layout
+ boolean mHasUnwantedGapAfter;
public FullSpanItem(Parcel in) {
mPosition = in.readInt();
mGapDir = in.readInt();
+ mHasUnwantedGapAfter = in.readInt() == 1;
int spanCount = in.readInt();
if (spanCount > 0) {
mGapPerSpan = new int[spanCount];
@@ -2467,6 +2562,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mPosition);
dest.writeInt(mGapDir);
+ dest.writeInt(mHasUnwantedGapAfter ? 1 : 0);
if (mGapPerSpan != null && mGapPerSpan.length > 0) {
dest.writeInt(mGapPerSpan.length);
dest.writeIntArray(mGapPerSpan);
@@ -2480,6 +2576,7 @@
return "FullSpanItem{" +
"mPosition=" + mPosition +
", mGapDir=" + mGapDir +
+ ", mHasUnwantedGapAfter=" + mHasUnwantedGapAfter +
", mGapPerSpan=" + Arrays.toString(mGapPerSpan) +
'}';
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
index c7cb3ef..6ca7fd6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
@@ -19,6 +19,7 @@
import junit.framework.AssertionFailedError;
import junit.framework.TestResult;
+import android.os.Debug;
import android.test.AndroidTestCase;
import android.util.Log;
import android.widget.TextView;
@@ -40,7 +41,7 @@
private static final String TAG = "AHT";
- List<ViewHolder> mViewHolders;
+ List<RecyclerViewBasicTest.MockViewHolder> mViewHolders;
AdapterHelper mAdapterHelper;
@@ -70,7 +71,7 @@
private void cleanState() {
mLog.setLength(0);
mPreLayoutItems = new ArrayList<TestAdapter.Item>();
- mViewHolders = new ArrayList<ViewHolder>();
+ mViewHolders = new ArrayList<RecyclerViewBasicTest.MockViewHolder>();
mFirstPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
mSecondPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
mPreProcessClone = null;
@@ -129,7 +130,7 @@
for (int i = 0; i < updateOp.itemCount; i ++) {
// events are dispatched before view holders are updated for consistency
assertFalse("update op should not match any existing view holders",
- viewHolder.getPosition() == updateOp.positionStart + i);
+ viewHolder.getLayoutPosition() == updateOp.positionStart + i);
}
}
@@ -198,10 +199,11 @@
mPreProcessClone = mTestAdapter.createCopy();
}
- private void addViewHolder(int posiiton) {
- ViewHolder viewHolder = new RecyclerViewBasicTest.MockViewHolder(
+ private void addViewHolder(int position) {
+ RecyclerViewBasicTest.MockViewHolder viewHolder = new RecyclerViewBasicTest.MockViewHolder(
new TextView(getContext()));
- viewHolder.mPosition = posiiton;
+ viewHolder.mPosition = position;
+ viewHolder.mItem = mTestAdapter.mItems.get(position);
mViewHolders.add(viewHolder);
}
@@ -756,7 +758,7 @@
public void testRandom() throws Throwable {
mCollectLogs = true;
Random random = new Random(System.nanoTime());
- for (int i = 0; i < 250; i++) {
+ for (int i = 0; i < 100; i++) {
try {
Log.d(TAG, "running random test " + i);
randomTest(random, Math.max(40, 10 + nextInt(random, i)));
@@ -835,6 +837,11 @@
}
void preProcess() {
+ for (RecyclerViewBasicTest.MockViewHolder vh : mViewHolders) {
+ final int ind = mTestAdapter.mItems.indexOf(vh.mItem);
+ assertEquals("actual adapter position should match", ind,
+ mAdapterHelper.applyPendingUpdatesToPosition(vh.mPosition));
+ }
mAdapterHelper.preProcess();
for (int i = 0; i < mPreProcessClone.mItems.size(); i++) {
TestAdapter.Item item = mPreProcessClone.mItems.get(i);
@@ -853,10 +860,10 @@
}
if (mViewHolders.size() > 0) {
final String vhLog = vhLogBuilder.toString();
- final int start = mViewHolders.get(0).getPosition();
+ final int start = mViewHolders.get(0).getLayoutPosition();
for (int i = 1; i < mViewHolders.size(); i++) {
assertEquals("view holder positions should be continious in pre-layout" + vhLog,
- start + i, mViewHolders.get(i).getPosition());
+ start + i, mViewHolders.get(i).getLayoutPosition());
}
}
mAdapterHelper.consumePostponedUpdates();
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 204d965..e96a782 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -18,6 +18,7 @@
import android.graphics.Rect;
import android.os.Looper;
+import android.support.v4.view.ViewCompat;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.View;
@@ -26,7 +27,9 @@
import android.widget.TextView;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -59,6 +62,19 @@
}
}
+ void setHasTransientState(final View view, final boolean value) {
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.setHasTransientState(view, value);
+ }
+ });
+ } catch (Throwable throwable) {
+ Log.e(TAG, "", throwable);
+ }
+ }
+
void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
@@ -73,18 +89,21 @@
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
+ try {
+ mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
}
});
+ checkForMainThreadException();
}
void postExceptionToInstrumentation(Throwable t) {
- if (mDebug) {
- Log.e(TAG, "captured exception on main thread", t);
- }
if (mainThreadException != null) {
Log.e(TAG, "receiving another main thread exception. dropping.", t);
} else {
+ Log.e(TAG, "captured exception on main thread", t);
mainThreadException = t;
}
@@ -108,14 +127,15 @@
}
}
getInstrumentation().waitForIdleSync();
+ super.tearDown();
+
try {
checkForMainThreadException();
} catch (Exception e) {
throw e;
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ throw new Exception(throwable);
}
- super.tearDown();
}
public Rect getDecoratedRecyclerViewBounds() {
@@ -131,13 +151,25 @@
if (mRecyclerView == null) {
return;
}
- mRecyclerView = null;
+ if (!isMainThread()) {
+ getInstrumentation().waitForIdleSync();
+ }
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- getActivity().mContainer.removeAllViews();
+ try {
+ final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
+ if (adapter instanceof AttachDetachCountingAdapter) {
+ ((AttachDetachCountingAdapter) adapter).getCounter()
+ .validateRemaining(mRecyclerView);
+ }
+ getActivity().mContainer.removeAllViews();
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
}
});
+ mRecyclerView = null;
}
void waitForAnimations(int seconds) throws InterruptedException {
@@ -159,6 +191,11 @@
}
public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
throws Throwable {
+ setRecyclerView(recyclerView, true, true);
+ }
+ public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool,
+ boolean addPositionCheckItemAnimator)
+ throws Throwable {
mRecyclerView = recyclerView;
if (assignDummyPool) {
RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
@@ -182,6 +219,20 @@
};
mRecyclerView.setRecycledViewPool(pool);
}
+ if (addPositionCheckItemAnimator) {
+ mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ RecyclerView.ViewHolder vh = parent.getChildViewHolder(view);
+ if (!vh.isRemoved()) {
+ assertNotSame("If getItemOffsets is called, child should have a valid"
+ + " adapter position unless it is removed",
+ vh.getAdapterPosition(), RecyclerView.NO_POSITION);
+ }
+ }
+ });
+ }
mAdapterHelper = recyclerView.mAdapterHelper;
runTestOnUiThread(new Runnable() {
@Override
@@ -195,27 +246,35 @@
return getActivity().mContainer;
}
- public void requestLayoutOnUIThread(final View view) throws Throwable {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- view.requestLayout();
- }
- });
+ public void requestLayoutOnUIThread(final View view) {
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.requestLayout();
+ }
+ });
+ } catch (Throwable throwable) {
+ Log.e(TAG, "", throwable);
+ }
}
- public void scrollBy(final int dt) throws Throwable {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
- mRecyclerView.scrollBy(dt, 0);
- } else {
- mRecyclerView.scrollBy(0, dt);
- }
+ public void scrollBy(final int dt) {
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
+ mRecyclerView.scrollBy(dt, 0);
+ } else {
+ mRecyclerView.scrollBy(0, dt);
+ }
- }
- });
+ }
+ });
+ } catch (Throwable throwable) {
+ Log.e(TAG, "", throwable);
+ }
}
void scrollToPosition(final int position) throws Throwable {
@@ -249,7 +308,7 @@
class TestViewHolder extends RecyclerView.ViewHolder {
- Item mBindedItem;
+ Item mBoundItem;
public TestViewHolder(View itemView) {
super(itemView);
@@ -258,7 +317,7 @@
@Override
public String toString() {
- return super.toString() + " item:" + mBindedItem;
+ return super.toString() + " item:" + mBoundItem;
}
}
@@ -303,7 +362,7 @@
while (i-- > 0) {
View view = getChildAt(i);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
- Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
+ Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
if (mDebug) {
Log.d(TAG, "testing item " + i);
}
@@ -407,8 +466,10 @@
}
}
- class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+ class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
+ implements AttachDetachCountingAdapter {
+ ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
List<Item> mItems;
TestAdapter(int count) {
@@ -419,6 +480,30 @@
}
@Override
+ public void onViewAttachedToWindow(TestViewHolder holder) {
+ super.onViewAttachedToWindow(holder);
+ mAttachmentCounter.onViewAttached(holder);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(TestViewHolder holder) {
+ super.onViewDetachedFromWindow(holder);
+ mAttachmentCounter.onViewDetached(holder);
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ super.onAttachedToRecyclerView(recyclerView);
+ mAttachmentCounter.onAttached(recyclerView);
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ super.onDetachedFromRecyclerView(recyclerView);
+ mAttachmentCounter.onDetached(recyclerView);
+ }
+
+ @Override
public TestViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
return new TestViewHolder(new TextView(parent.getContext()));
@@ -428,7 +513,7 @@
public void onBindViewHolder(TestViewHolder holder, int position) {
final Item item = mItems.get(position);
((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
- holder.mBindedItem = item;
+ holder.mBoundItem = item;
}
public void deleteAndNotify(final int start, final int count) throws Throwable {
@@ -525,6 +610,9 @@
return mItems.size();
}
+ /**
+ * Uses notifyDataSetChanged
+ */
public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
for (int i = 0; i < fromToTuples.length; i += 1) {
int[] tuple = fromToTuples[i];
@@ -535,6 +623,9 @@
}
}
+ /**
+ * Uses notifyDataSetChanged
+ */
public void moveItem(final int from, final int to, final boolean notifyChange)
throws Throwable {
runTestOnUiThread(new Runnable() {
@@ -551,6 +642,29 @@
});
}
+ /**
+ * Uses notifyItemMoved
+ */
+ public void moveAndNotify(final int from, final int to) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Item item = mItems.remove(from);
+ mItems.add(to, item);
+ offsetOriginalIndices(from, to - 1);
+ item.mAdapterIndex = to;
+ notifyItemMoved(from, to);
+ }
+ });
+ }
+
+
+
+ @Override
+ public ViewAttachDetachCounter getCounter() {
+ return mAttachmentCounter;
+ }
+
private class AddRemoveRunnable implements Runnable {
final int[][] mStartCountTuples;
@@ -598,6 +712,10 @@
}
}
+ public boolean isMainThread() {
+ return Looper.myLooper() == Looper.getMainLooper();
+ }
+
@Override
public void runTestOnUiThread(Runnable r) throws Throwable {
if (Looper.myLooper() == Looper.getMainLooper()) {
@@ -626,4 +744,58 @@
'}';
}
}
+
+ public interface AttachDetachCountingAdapter {
+
+ ViewAttachDetachCounter getCounter();
+ }
+
+ public class ViewAttachDetachCounter {
+
+ Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
+
+ public void validateRemaining(RecyclerView recyclerView) {
+ final int childCount = recyclerView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = recyclerView.getChildAt(i);
+ RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
+ assertTrue("remaining view should be in attached set " + vh,
+ mAttachedSet.contains(vh));
+ }
+ assertEquals("there should not be any views left in attached set",
+ childCount, mAttachedSet.size());
+ }
+
+ public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
+ try {
+ assertTrue("view holder should be in attached set",
+ mAttachedSet.remove(viewHolder));
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+
+ public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
+ try {
+ assertTrue("view holder should not be in attached set",
+ mAttachedSet.add(viewHolder));
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+
+ public void onAttached(RecyclerView recyclerView) {
+ // when a new RV is attached, clear the set and add all view holders
+ mAttachedSet.clear();
+ final int childCount = recyclerView.getChildCount();
+ for (int i = 0; i < childCount; i ++) {
+ View view = recyclerView.getChildAt(i);
+ mAttachedSet.add(recyclerView.getChildViewHolder(view));
+ }
+ }
+
+ public void onDetached(RecyclerView recyclerView) {
+ validateRemaining(recyclerView);
+ }
+ }
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
index a453e6e..46833ca 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
@@ -16,30 +16,41 @@
package android.support.v7.widget;
+import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
public class DefaultItemAnimatorTest extends ActivityInstrumentationTestCase2<TestActivity> {
+ private static final String TAG = "DefaultItemAnimatorTest";
+ Throwable mainThreadException;
+
DefaultItemAnimator mAnimator;
Adapter mAdapter;
ViewGroup mDummyParent;
- CountDownLatch mExpectedItems;
+ List<RecyclerView.ViewHolder> mExpectedItems = new ArrayList<RecyclerView.ViewHolder>();
Set<RecyclerView.ViewHolder> mRemoveFinished = new HashSet<RecyclerView.ViewHolder>();
Set<RecyclerView.ViewHolder> mAddFinished = new HashSet<RecyclerView.ViewHolder>();
Set<RecyclerView.ViewHolder> mMoveFinished = new HashSet<RecyclerView.ViewHolder>();
Set<RecyclerView.ViewHolder> mChangeFinished = new HashSet<RecyclerView.ViewHolder>();
+ Semaphore mExpectedItemCount = new Semaphore(0);
+
public DefaultItemAnimatorTest() {
super("android.support.v7.recyclerview", TestActivity.class);
}
@@ -53,86 +64,254 @@
mAnimator.setListener(new RecyclerView.ItemAnimator.ItemAnimatorListener() {
@Override
public void onRemoveFinished(RecyclerView.ViewHolder item) {
- assertTrue(mRemoveFinished.add(item));
- onFinished();
+ try {
+ assertTrue(mRemoveFinished.add(item));
+ onFinished(item);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
}
@Override
public void onAddFinished(RecyclerView.ViewHolder item) {
- assertTrue(mAddFinished.add(item));
- onFinished();
+ try {
+ assertTrue(mAddFinished.add(item));
+ onFinished(item);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
}
@Override
public void onMoveFinished(RecyclerView.ViewHolder item) {
- assertTrue(mMoveFinished.add(item));
- onFinished();
+ try {
+ assertTrue(mMoveFinished.add(item));
+ onFinished(item);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
}
@Override
public void onChangeFinished(RecyclerView.ViewHolder item) {
- assertTrue(mChangeFinished.add(item));
- onFinished();
+ try {
+ assertTrue(mChangeFinished.add(item));
+ onFinished(item);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
}
- private void onFinished() {
- if (mExpectedItems != null) {
- mExpectedItems.countDown();
- }
+ private void onFinished(RecyclerView.ViewHolder item) {
+ assertNotNull(mExpectedItems.remove(item));
+ mExpectedItemCount.release(1);
}
});
}
- void expectItems(int count) {
- mExpectedItems = new CountDownLatch(count);
+ void checkForMainThreadException() throws Throwable {
+ if (mainThreadException != null) {
+ throw mainThreadException;
+ }
}
- void runAndWait(int seconds) throws Throwable {
+ @Override
+ protected void tearDown() throws Exception {
+ getInstrumentation().waitForIdleSync();
+ super.tearDown();
+ try {
+ checkForMainThreadException();
+ } catch (Exception e) {
+ throw e;
+ } catch (Throwable throwable) {
+ throw new Exception(throwable);
+ }
+ }
+
+ void expectItems(RecyclerView.ViewHolder... viewHolders) {
+ mExpectedItems.addAll(Arrays.asList(viewHolders));
+ }
+
+ void runAndWait(int itemCount, int seconds) throws Throwable {
+ runAndWait(itemCount, seconds, null);
+ }
+
+ void runAndWait(int itemCount, int seconds, final ThrowingRunnable postRun) throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mAnimator.runPendingAnimations();
+ if (postRun != null) {
+ try {
+ postRun.run();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
}
});
- waitForItems(seconds);
+ waitForItems(itemCount, seconds);
+ checkForMainThreadException();
}
- void waitForItems(int seconds) throws InterruptedException {
- mExpectedItems.await(seconds, TimeUnit.SECONDS);
- assertEquals("all expected finish events should happen", 0, mExpectedItems.getCount());
+ void waitForItems(int itemCount, int seconds) throws InterruptedException {
+ assertTrue("all vh animations should end",
+ mExpectedItemCount.tryAcquire(itemCount, seconds, TimeUnit.SECONDS));
+ assertEquals("all expected finish events should happen", 0, mExpectedItems.size());
+ // wait one more second for unwanted
+ assertFalse("should not receive any more permits",
+ mExpectedItemCount.tryAcquire(1, 2, TimeUnit.SECONDS));
}
public void testAnimateAdd() throws Throwable {
ViewHolder vh = createViewHolder(1);
- expectItems(1);
+ expectItems(vh);
assertTrue(animateAdd(vh));
assertTrue(mAnimator.isRunning());
- runAndWait(1);
+ runAndWait(1, 1);
}
public void testAnimateRemove() throws Throwable {
ViewHolder vh = createViewHolder(1);
- expectItems(1);
+ expectItems(vh);
assertTrue(animateRemove(vh));
assertTrue(mAnimator.isRunning());
- runAndWait(1);
+ runAndWait(1, 1);
}
public void testAnimateMove() throws Throwable {
ViewHolder vh = createViewHolder(1);
- expectItems(1);
+ expectItems(vh);
assertTrue(animateMove(vh, 0, 0, 100, 100));
assertTrue(mAnimator.isRunning());
- runAndWait(1);
+ runAndWait(1, 1);
}
public void testAnimateChange() throws Throwable {
ViewHolder vh = createViewHolder(1);
ViewHolder vh2 = createViewHolder(2);
- expectItems(2);
+ expectItems(vh, vh2);
assertTrue(animateChange(vh, vh2, 0, 0, 100, 100));
assertTrue(mAnimator.isRunning());
- runAndWait(1);
+ runAndWait(2, 1);
+ }
+
+ public void cancelBefore(int count, final RecyclerView.ViewHolder... toCancel)
+ throws Throwable {
+ cancelTest(true, count, toCancel);
+ }
+
+ public void cancelAfter(int count, final RecyclerView.ViewHolder... toCancel)
+ throws Throwable {
+ cancelTest(false, count, toCancel);
+ }
+
+ public void cancelTest(boolean before, int count, final RecyclerView.ViewHolder... toCancel) throws Throwable {
+ if (before) {
+ endAnimations(toCancel);
+ runAndWait(count, 1);
+ } else {
+ runAndWait(count, 1, new ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ endAnimations(toCancel);
+ }
+ });
+ }
+ }
+
+ public void testCancelAddBefore() throws Throwable {
+ final ViewHolder vh = createViewHolder(1);
+ expectItems(vh);
+ assertTrue(animateAdd(vh));
+ cancelBefore(1, vh);
+ }
+
+ public void testCancelAddAfter() throws Throwable {
+ final ViewHolder vh = createViewHolder(1);
+ expectItems(vh);
+ assertTrue(animateAdd(vh));
+ cancelAfter(1, vh);
+ }
+
+ public void testCancelMoveBefore() throws Throwable {
+ ViewHolder vh = createViewHolder(1);
+ expectItems(vh);
+ assertTrue(animateMove(vh, 10, 10, 100, 100));
+ cancelBefore(1, vh);
+ }
+
+ public void testCancelMoveAfter() throws Throwable {
+ ViewHolder vh = createViewHolder(1);
+ expectItems(vh);
+ assertTrue(animateMove(vh, 10, 10, 100, 100));
+ cancelAfter(1, vh);
+ }
+
+ public void testCancelRemove() throws Throwable {
+ ViewHolder vh = createViewHolder(1);
+ expectItems(vh);
+ assertTrue(animateRemove(vh));
+ endAnimations(vh);
+ runAndWait(1, 1);
+ }
+
+ public void testCancelChangeOldBefore() throws Throwable {
+ cancelChangeOldTest(true);
+ }
+ public void testCancelChangeOldAfter() throws Throwable {
+ cancelChangeOldTest(false);
+ }
+
+ public void cancelChangeOldTest(boolean before) throws Throwable {
+ ViewHolder vh = createViewHolder(1);
+ ViewHolder vh2 = createViewHolder(1);
+ expectItems(vh, vh2);
+ assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
+ cancelTest(before, 2, vh);
+ }
+
+ public void testCancelChangeNewBefore() throws Throwable {
+ cancelChangeNewTest(true);
+ }
+
+ public void testCancelChangeNewAfter() throws Throwable {
+ cancelChangeNewTest(false);
+ }
+
+ public void cancelChangeNewTest(boolean before) throws Throwable {
+ ViewHolder vh = createViewHolder(1);
+ ViewHolder vh2 = createViewHolder(1);
+ expectItems(vh, vh2);
+ assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
+ cancelTest(before, 2, vh2);
+ }
+
+ public void testCancelChangeBothBefore() throws Throwable {
+ cancelChangeBothTest(true);
+ }
+
+ public void testCancelChangeBothAfter() throws Throwable {
+ cancelChangeBothTest(false);
+ }
+
+ public void cancelChangeBothTest(boolean before) throws Throwable {
+ ViewHolder vh = createViewHolder(1);
+ ViewHolder vh2 = createViewHolder(1);
+ expectItems(vh, vh2);
+ assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
+ cancelTest(before, 2, vh, vh2);
+ }
+
+ void endAnimations(final RecyclerView.ViewHolder... vhs) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ for (RecyclerView.ViewHolder vh : vhs) {
+ mAnimator.endAnimation(vh);
+ }
+ }
+ });
}
boolean animateAdd(final RecyclerView.ViewHolder vh) throws Throwable {
@@ -195,6 +374,14 @@
return vh;
}
+ void postExceptionToInstrumentation(Throwable t) {
+ if (mainThreadException == null) {
+ mainThreadException = t;
+ } else {
+ Log.e(TAG, "skipping secondary main thread exception", t);
+ }
+ }
+
private class Adapter extends RecyclerView.Adapter<ViewHolder> {
@@ -236,4 +423,17 @@
((TextView) itemView).setText(text);
}
}
+
+ private interface ThrowingRunnable {
+ public void run() throws Throwable;
+ }
+
+ @Override
+ public void runTestOnUiThread(Runnable r) throws Throwable {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ r.run();
+ } else {
+ super.runTestOnUiThread(r);
+ }
+ }
}
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 a5c39d1..c02a0f6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -82,6 +82,51 @@
mGlm.waitForLayout(2);
}
+ public void testCustomWidthInHorizontal() throws Throwable {
+ customSizeInScrollDirectionTest(new Config(3, HORIZONTAL, false));
+ }
+
+ public void testCustomHeightInVertical() throws Throwable {
+ customSizeInScrollDirectionTest(new Config(3, VERTICAL, false));
+ }
+
+ public void customSizeInScrollDirectionTest(final Config config) throws Throwable {
+ final int[] sizePerPosition = new int[]{3, 5, 9, 21, 3, 5, 9, 6, 9, 1};
+ final int[] expectedSizePerPosition = new int[]{9, 9, 9, 21, 3, 5, 9, 9, 9, 1};
+ final GridTestAdapter testAdapter = new GridTestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
+ if (layoutParams == null) {
+ layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ holder.itemView.setLayoutParams(layoutParams);
+ }
+ final int size = sizePerPosition[position];
+ if (config.mOrientation == HORIZONTAL) {
+ layoutParams.width = size;
+ } else {
+ layoutParams.height = size;
+ }
+ }
+ };
+ testAdapter.setFullSpan(3, 5);
+ final RecyclerView rv = setupBasic(config, testAdapter);
+ waitForFirstLayout(rv);
+
+ assertTrue("[test sanity] some views should be laid out", mRecyclerView.getChildCount() > 0);
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+ View child = mRecyclerView.getChildAt(i);
+ final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
+ : child.getHeight();
+ assertEquals("child " + i + " should have the size specified in its layout params",
+ expectedSizePerPosition[i], size);
+ }
+ checkForMainThreadException();
+ }
+
public void testLayoutParams() throws Throwable {
layoutParamsTest(GridLayoutManager.HORIZONTAL);
removeRecyclerView();
@@ -103,7 +148,7 @@
.getCompatAccessibilityDelegate().getItemDelegate();
final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
final View chosen = recyclerView.getChildAt(recyclerView.getChildCount() - 2);
- final int position = recyclerView.getChildPosition(chosen);
+ final int position = recyclerView.getChildLayoutPosition(chosen);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
@@ -178,17 +223,23 @@
waitForFirstLayout(rv);
final OrientationHelper helper = mGlm.mOrientationHelper;
final int firstRowSize = Math.max(30, getSize(mGlm.findViewByPosition(2)));
- assertEquals(firstRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(0)));
- assertEquals(firstRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(1)));
- assertEquals(firstRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(2)));
+ assertEquals(firstRowSize,
+ helper.getDecoratedMeasurement(mGlm.findViewByPosition(0)));
+ assertEquals(firstRowSize,
+ helper.getDecoratedMeasurement(mGlm.findViewByPosition(1)));
+ assertEquals(firstRowSize,
+ helper.getDecoratedMeasurement(mGlm.findViewByPosition(2)));
assertEquals(firstRowSize, getSize(mGlm.findViewByPosition(0)));
assertEquals(firstRowSize, getSize(mGlm.findViewByPosition(1)));
assertEquals(firstRowSize, getSize(mGlm.findViewByPosition(2)));
final int secondRowSize = Math.max(200, getSize(mGlm.findViewByPosition(3)));
- assertEquals(secondRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(3)));
- assertEquals(secondRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(4)));
- assertEquals(secondRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(5)));
+ assertEquals(secondRowSize,
+ helper.getDecoratedMeasurement(mGlm.findViewByPosition(3)));
+ assertEquals(secondRowSize,
+ helper.getDecoratedMeasurement(mGlm.findViewByPosition(4)));
+ assertEquals(secondRowSize,
+ helper.getDecoratedMeasurement(mGlm.findViewByPosition(5)));
assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(3)));
assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(4)));
assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(5)));
@@ -456,7 +507,7 @@
while (visited < mAdapter.getItemCount()) {
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
View child = mRecyclerView.getChildAt(i);
- final int pos = mRecyclerView.getChildPosition(child);
+ final int pos = mRecyclerView.getChildLayoutPosition(child);
if (globalPositions[pos] != Integer.MIN_VALUE) {
continue;
}
@@ -503,7 +554,7 @@
while (!shouldTest.isEmpty() && scrollAmount != 0) {
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
View child = mRecyclerView.getChildAt(i);
- int pos = mRecyclerView.getChildPosition(child);
+ int pos = mRecyclerView.getChildLayoutPosition(child);
if (!shouldTest.get(pos)) {
continue;
}
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 8adbedf..fed7a1a 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -109,7 +109,7 @@
setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
int center = (mLayoutManager.findLastVisibleItemPosition()
- mLayoutManager.findFirstVisibleItemPosition()) / 2;
- final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(center);
+ final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center);
final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
runTestOnUiThread(new Runnable() {
@Override
@@ -125,7 +125,7 @@
center += childCountToAdd; // offset item
mLayoutManager.waitForLayout(2);
mLayoutManager.waitForAnimationsToEnd(20);
- final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForPosition(center);
+ final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center);
assertNotNull("focused child should stay in layout", postVH);
assertSame("same view holder should be kept for unchanged child", vh, postVH);
assertEquals("focused child's screen position should stay unchanged", top,
@@ -167,7 +167,7 @@
while (testCount-- > 0) {
// get middle child
final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
- final int position = mRecyclerView.getChildPosition(child);
+ final int position = mRecyclerView.getChildLayoutPosition(child);
final int startOffset = config.mReverseLayout ?
orientationHelper.getEndAfterPadding() - orientationHelper
.getDecoratedEnd(child)
@@ -232,7 +232,7 @@
int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
View child = mLayoutManager.getChildAt(i);
- int position = mRecyclerView.getChildPosition(child);
+ int position = mRecyclerView.getChildLayoutPosition(child);
if (position < minPosition) {
minPosition = position;
}
@@ -329,7 +329,7 @@
}
} else {
RecyclerView.ViewHolder vh =
- mRecyclerView.findViewHolderForPosition(scrollPosition);
+ mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
assertNotNull("scroll to position should work", vh);
if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
assertEquals("scroll offset should be applied properly",
@@ -1059,7 +1059,7 @@
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
.getLayoutParams();
TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
- items.put(vh.mBindedItem, getViewBounds(child));
+ items.put(vh.mBoundItem, getViewBounds(child));
}
}
});
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java
index 5051f57..ca57ce1 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java
@@ -154,7 +154,7 @@
}
public void testRandom() throws Exception {
- for (int i = 0; i < 250; i++) {
+ for (int i = 0; i < 150; i++) {
try {
cleanState();
setup(50);
@@ -170,12 +170,11 @@
}
public void testRandomMoveRemove() throws Exception {
- for (int i = 0; i < 10000; i++) {
+ for (int i = 0; i < 1000; i++) {
try {
cleanState();
setup(5);
orderedRandom(MOVE, REMOVE);
- Log.d(TAG, "running random move remove test " + i);
process();
} catch (Throwable t) {
throw new Exception(t.getMessage() + "\n" + opsToString(mUpdateOps));
@@ -184,12 +183,11 @@
}
public void testRandomMoveAdd() throws Exception {
- for (int i = 0; i < 10000; i++) {
+ for (int i = 0; i < 1000; i++) {
try {
cleanState();
setup(5);
orderedRandom(MOVE, ADD);
- Log.d(TAG, "running random move add test " + i);
process();
} catch (Throwable t) {
throw new Exception(t.getMessage() + "\n" + opsToString(mUpdateOps));
@@ -198,12 +196,11 @@
}
public void testRandomMoveUpdate() throws Exception {
- for (int i = 0; i < 10000; i++) {
+ for (int i = 0; i < 1000; i++) {
try {
cleanState();
setup(5);
orderedRandom(MOVE, UPDATE);
- Log.d(TAG, "running random move update test " + i);
process();
} catch (Throwable t) {
throw new Exception(t.getMessage() + "\n" + opsToString(mUpdateOps));
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
index 0c08775..e09dafb 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -31,7 +31,6 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class RecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
@@ -278,13 +277,14 @@
mRecyclerView.getItemAnimator().setSupportsChangeAnimations(supportsChangeAnim);
final RecyclerView.ViewHolder toBeChangedVH =
- mRecyclerView.findViewHolderForPosition(changedIndex);
+ mRecyclerView.findViewHolderForLayoutPosition(changedIndex);
mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
@Override
void afterPreLayout(RecyclerView.Recycler recycler,
AnimationLayoutManager layoutManager,
RecyclerView.State state) {
- RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(changedIndex);
+ RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(
+ changedIndex);
if (supportsChangeAnim) {
assertTrue(logPrefix + " changed view holder should have correct flag"
, vh.isChanged());
@@ -297,7 +297,8 @@
@Override
void afterPostLayout(RecyclerView.Recycler recycler,
AnimationLayoutManager layoutManager, RecyclerView.State state) {
- RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(changedIndex);
+ RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(
+ changedIndex);
assertFalse(logPrefix + "VH should not be marked as changed", vh.isChanged());
if (supportsChangeAnim) {
assertNotSame(logPrefix + "a new VH should be given if change is supported",
@@ -500,6 +501,7 @@
int targetItemCount = mTestAdapter.getItemCount();
for (int i = 0; i < 100; i++) {
mTestAdapter.deleteAndNotify(new int[]{0, 1}, new int[]{7, 1});
+ checkForMainThreadException();
targetItemCount -= 2;
}
// wait until main thread runnables are consumed
@@ -593,6 +595,40 @@
});
}
+ public void testNotifyDataSetChangedDuringScroll() throws Throwable {
+ setupBasic(10);
+ final AtomicInteger onLayoutItemCount = new AtomicInteger(0);
+ final AtomicInteger onScrollItemCount = new AtomicInteger(0);
+
+ mLayoutManager.setOnLayoutCallbacks(new OnLayoutCallbacks() {
+ @Override
+ void onLayoutChildren(RecyclerView.Recycler recycler,
+ AnimationLayoutManager lm, RecyclerView.State state) {
+ onLayoutItemCount.set(state.getItemCount());
+ super.onLayoutChildren(recycler, lm, state);
+ }
+
+ @Override
+ public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
+ onScrollItemCount.set(state.getItemCount());
+ super.onScroll(dx, recycler, state);
+ }
+ });
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTestAdapter.mItems.remove(5);
+ mTestAdapter.notifyDataSetChanged();
+ mRecyclerView.scrollBy(0, 100);
+ assertTrue("scrolling while there are pending adapter updates should "
+ + "trigger a layout", mLayoutManager.mOnLayoutCallbacks.mLayoutCount > 0);
+ assertEquals("scroll by should be called w/ updated adapter count",
+ mTestAdapter.mItems.size(), onScrollItemCount.get());
+
+ }
+ });
+ }
+
public void testAddInvisibleAndVisible() throws Throwable {
setupBasic(10, 1, 7);
mLayoutManager.expectLayouts(2);
@@ -648,6 +684,17 @@
public void testFindPositionOffset() throws Throwable {
setupBasic(10);
+ mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
+ @Override
+ void beforePreLayout(RecyclerView.Recycler recycler,
+ AnimationLayoutManager lm, RecyclerView.State state) {
+ super.beforePreLayout(recycler, lm, state);
+ // [0,2,4]
+ assertEquals("offset check", 0, mAdapterHelper.findPositionOffset(0));
+ assertEquals("offset check", 1, mAdapterHelper.findPositionOffset(2));
+ assertEquals("offset check", 2, mAdapterHelper.findPositionOffset(4));
+ }
+ };
runTestOnUiThread(new Runnable() {
@Override
public void run() {
@@ -656,14 +703,9 @@
mTestAdapter.notifyItemRangeRemoved(1, 1);
// delete 3
mTestAdapter.notifyItemRangeRemoved(2, 1);
- mAdapterHelper.preProcess();
- // [0,2,4]
- assertEquals("offset check", 0, mAdapterHelper.findPositionOffset(0));
- assertEquals("offset check", 1, mAdapterHelper.findPositionOffset(2));
- assertEquals("offset check", 2, mAdapterHelper.findPositionOffset(4));
-
}
});
+ mLayoutManager.waitForLayout(2);
}
private void setLayoutRange(int start, int count) {
@@ -1136,7 +1178,7 @@
for (int i = 0; i < childCount; i++) {
ViewHolder vh = getChildViewHolderInt(getChildAt(i));
TestViewHolder tvh = (TestViewHolder) vh;
- log.append(tvh.mBindedItem).append(vh)
+ log.append(tvh.mBoundItem).append(vh)
.append(" hidden:")
.append(mChildHelper.mHiddenViews.contains(vh.itemView))
.append("\n");
@@ -1146,14 +1188,14 @@
if (vh.isInvalid()) {
continue;
}
- if (vh.getPosition() < 0) {
+ if (vh.getLayoutPosition() < 0) {
LayoutManager lm = getLayoutManager();
for (int j = 0; j < lm.getChildCount(); j ++) {
assertNotSame("removed view holder should not be in LM's child list",
vh.itemView, lm.getChildAt(j));
}
} else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
- if (!existingOffsets.add(vh.getPosition())) {
+ if (!existingOffsets.add(vh.getLayoutPosition())) {
throw new IllegalStateException("view holder position conflict for "
+ "existing views " + vh + "\n" + log);
}
@@ -1344,7 +1386,7 @@
viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
viewHolder.mPreLayoutPosition);
assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
- viewHolder.getPosition());
+ viewHolder.getLayoutPosition());
if (mType == Type.scrap) {
assertEquals(this + ": old position should match\n" + log, mOldPos,
result.scrapResult.getOldPosition());
@@ -1352,7 +1394,7 @@
} else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
.isRemoved()) {
assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
- + viewHolder, mPostLayoutPos, viewHolder.getPosition());
+ + viewHolder, mPostLayoutPos, viewHolder.getLayoutPosition());
}
}
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index 86e6dda..750d50f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.test.AndroidTestCase;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -36,14 +37,7 @@
}
public void testMeasureWithoutLayoutManager() {
- Throwable measureThrowable = null;
- try {
- measure();
- } catch (Throwable throwable) {
- measureThrowable = throwable;
- }
- assertTrue("Calling measure without a layout manager should throw exception"
- , measureThrowable instanceof NullPointerException);
+ measure();
}
private void measure() {
@@ -54,20 +48,57 @@
mRecyclerView.layout(0, 0, 320, 320);
}
- private void safeLayout() {
- try {
- layout();
- } catch (Throwable t) {
+ private void focusSearch() {
+ mRecyclerView.focusSearch(1);
+ }
- }
+ public void testLayoutWithoutAdapter() throws InterruptedException {
+ MockLayoutManager layoutManager = new MockLayoutManager();
+ mRecyclerView.setLayoutManager(layoutManager);
+ layout();
+ assertEquals("layout manager should not be called if there is no adapter attached",
+ 0, layoutManager.mLayoutCount);
}
public void testLayoutWithoutLayoutManager() throws InterruptedException {
- MockLayoutManager layoutManager = new MockLayoutManager();
- mRecyclerView.setLayoutManager(layoutManager);
- safeLayout();
- assertEquals("layout manager should not be called if there is no adapter attached",
- 0, layoutManager.mLayoutCount);
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ measure();
+ layout();
+ }
+
+ public void testFocusWithoutLayoutManager() throws InterruptedException {
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ measure();
+ layout();
+ focusSearch();
+ }
+
+ public void testScrollWithoutLayoutManager() throws InterruptedException {
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ measure();
+ layout();
+ mRecyclerView.scrollBy(10, 10);
+ }
+
+ public void testSmoothScrollWithoutLayoutManager() throws InterruptedException {
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ measure();
+ layout();
+ mRecyclerView.smoothScrollBy(10, 10);
+ }
+
+ public void testScrollToPositionWithoutLayoutManager() throws InterruptedException {
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ measure();
+ layout();
+ mRecyclerView.scrollToPosition(5);
+ }
+
+ public void testSmoothScrollToPositionWithoutLayoutManager() throws InterruptedException {
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ measure();
+ layout();
+ mRecyclerView.smoothScrollToPosition(5);
}
public void testLayout() throws InterruptedException {
@@ -329,7 +360,7 @@
}
static class MockViewHolder extends RecyclerView.ViewHolder {
-
+ public Object mItem;
public MockViewHolder(View itemView) {
super(itemView);
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 62ede3a..2e430e5 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -28,6 +28,7 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.widget.TextView;
import java.util.ArrayList;
@@ -38,6 +39,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
@@ -52,6 +54,479 @@
super(DEBUG);
}
+ public void testScrollToPositionCallback() throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ TestLayoutManager tlm = new TestLayoutManager() {
+ int scrollPos = RecyclerView.NO_POSITION;
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ layoutLatch.countDown();
+ if (scrollPos == RecyclerView.NO_POSITION) {
+ layoutRange(recycler, 0, 10);
+ } else {
+ layoutRange(recycler, scrollPos, scrollPos + 10);
+ }
+ }
+ @Override
+ public void scrollToPosition(int position) {
+ scrollPos = position;
+ requestLayout();
+ }
+ };
+ recyclerView.setLayoutManager(tlm);
+ TestAdapter adapter = new TestAdapter(100);
+ recyclerView.setAdapter(adapter);
+ final AtomicInteger rvCounter = new AtomicInteger(0);
+ final AtomicInteger viewGroupCounter = new AtomicInteger(0);
+ recyclerView.getViewTreeObserver().addOnScrollChangedListener(
+ new ViewTreeObserver.OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ viewGroupCounter.incrementAndGet();
+ }
+ });
+ recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ rvCounter.incrementAndGet();
+ super.onScrolled(recyclerView, dx, dy);
+ }
+ });
+ tlm.expectLayouts(1);
+
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(2);
+ // wait for draw :/
+ Thread.sleep(1000);
+
+ assertEquals("RV on scroll should be called for initialization", 1, rvCounter.get());
+ assertEquals("VTO on scroll should be called for initialization", 1,
+ viewGroupCounter.get());
+ tlm.expectLayouts(1);
+ scrollToPosition(3);
+ tlm.waitForLayout(2);
+ assertEquals("RV on scroll should be called", 2, rvCounter.get());
+ assertEquals("VTO on scroll should be called", 2, viewGroupCounter.get());
+ tlm.expectLayouts(1);
+ requestLayoutOnUIThread(recyclerView);
+ tlm.waitForLayout(2);
+ // wait for draw :/
+ Thread.sleep(1000);
+ assertEquals("on scroll should NOT be called", 2, rvCounter.get());
+ assertEquals("on scroll should NOT be called", 2, viewGroupCounter.get());
+
+ }
+
+ public void testScrollInBothDirectionEqual() throws Throwable {
+ scrollInBothDirection(3, 3, 1000, 1000);
+ }
+
+ public void testScrollInBothDirectionMoreVertical() throws Throwable {
+ scrollInBothDirection(2, 3, 1000, 1000);
+ }
+
+ public void testScrollInBothDirectionMoreHorizontal() throws Throwable {
+ scrollInBothDirection(3, 2, 1000, 1000);
+ }
+
+ public void testScrollHorizontalOnly() throws Throwable {
+ scrollInBothDirection(3, 0, 1000, 0);
+ }
+
+ public void testScrollVerticalOnly() throws Throwable {
+ scrollInBothDirection(0, 3, 0, 1000);
+ }
+
+ public void testScrollInBothDirectionEqualReverse() throws Throwable {
+ scrollInBothDirection(3, 3, -1000, -1000);
+ }
+
+ public void testScrollInBothDirectionMoreVerticalReverse() throws Throwable {
+ scrollInBothDirection(2, 3, -1000, -1000);
+ }
+
+ public void testScrollInBothDirectionMoreHorizontalReverse() throws Throwable {
+ scrollInBothDirection(3, 2, -1000, -1000);
+ }
+
+ public void testScrollHorizontalOnlyReverse() throws Throwable {
+ scrollInBothDirection(3, 0, -1000, 0);
+ }
+
+ public void testScrollVerticalOnlyReverse() throws Throwable {
+ scrollInBothDirection(0, 3, 0, -1000);
+ }
+
+ public void scrollInBothDirection(int horizontalScrollCount, int verticalScrollCount,
+ int horizontalVelocity, int verticalVelocity)
+ throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ final AtomicInteger horizontalCounter = new AtomicInteger(horizontalScrollCount);
+ final AtomicInteger verticalCounter = new AtomicInteger(verticalScrollCount);
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public boolean canScrollHorizontally() {
+ return true;
+ }
+
+ @Override
+ public boolean canScrollVertically() {
+ return true;
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ layoutRange(recycler, 0, 10);
+ layoutLatch.countDown();
+ }
+
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (verticalCounter.get() > 0) {
+ verticalCounter.decrementAndGet();
+ return dy;
+ }
+ return 0;
+ }
+
+ @Override
+ public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (horizontalCounter.get() > 0) {
+ horizontalCounter.decrementAndGet();
+ return dx;
+ }
+ return 0;
+ }
+ };
+ TestAdapter adapter = new TestAdapter(100);
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutManager(tlm);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(2);
+ assertTrue("test sanity, fling must run", fling(horizontalVelocity, verticalVelocity));
+ assertEquals("rv's horizontal scroll cb must run " + horizontalScrollCount + " times'", 0,
+ horizontalCounter.get());
+ assertEquals("rv's vertical scroll cb must run " + verticalScrollCount + " times'", 0,
+ verticalCounter.get());
+ }
+
+ public void testDraglHorizontal() throws Throwable {
+ scrollInOtherOrientationTest(true, true);
+ }
+
+ public void testDragVertical() throws Throwable {
+ scrollInOtherOrientationTest(false, true);
+ }
+
+ public void testFlingHorizontal() throws Throwable {
+ scrollInOtherOrientationTest(true, false);
+ }
+
+ public void testFlingVertical() throws Throwable {
+ scrollInOtherOrientationTest(false, false);
+ }
+
+
+ public void scrollInOtherOrientationTest(final boolean horizontal, final boolean drag)
+ throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ final AtomicBoolean scrolledHorizontal = new AtomicBoolean(false);
+ final AtomicBoolean scrolledVertical = new AtomicBoolean(false);
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public boolean canScrollHorizontally() {
+ return horizontal;
+ }
+
+ @Override
+ public boolean canScrollVertically() {
+ return !horizontal;
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ layoutRange(recycler, 0, 10);
+ layoutLatch.countDown();
+ }
+
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ scrolledVertical.set(true);
+ return dy;
+ }
+
+ @Override
+ public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ scrolledHorizontal.set(true);
+ return dx;
+ }
+ };
+ TestAdapter adapter = new TestAdapter(100);
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutManager(tlm);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(2);
+ if (drag) {
+ TouchUtils.dragViewTo(this, mRecyclerView, Gravity.LEFT | Gravity.TOP, 200, 200);
+ } else {// fling
+ assertTrue("test sanity, fling must run", fling(600, 600));
+ }
+ assertEquals("horizontal scroll", horizontal, scrolledHorizontal.get());
+ assertEquals("vertical scroll",!horizontal, scrolledVertical.get());
+ }
+
+ private boolean fling(final int velocityX, final int velocityY) throws Throwable {
+ final AtomicBoolean didStart = new AtomicBoolean(false);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean result = mRecyclerView.fling(velocityX, velocityY);
+ didStart.set(result);
+ }
+ });
+ if (!didStart.get()) {
+ return false;
+ }
+ // cannot set scroll listener in case it is subject to some test so instead doing a busy
+ // loop until state goes idle
+ while (mRecyclerView.getScrollState() != SCROLL_STATE_IDLE) {
+ getInstrumentation().waitForIdleSync();
+ }
+ return true;
+ }
+
+ public void testTransientStateRecycleViaAdapter() throws Throwable {
+ transientStateRecycleTest(true, false);
+ }
+
+ public void testTransientStateRecycleViaTransientStateCleanup() throws Throwable {
+ transientStateRecycleTest(false, true);
+ }
+
+ public void testTransientStateDontRecycle() throws Throwable {
+ transientStateRecycleTest(false, false);
+ }
+
+ public void transientStateRecycleTest(final boolean succeed, final boolean unsetTransientState)
+ throws Throwable {
+ final List<View> failedToRecycle = new ArrayList<View>();
+ final List<View> recycled = new ArrayList<View>();
+ TestAdapter testAdapter = new TestAdapter(10) {
+ @Override
+ public boolean onFailedToRecycleView(
+ TestViewHolder holder) {
+ failedToRecycle.add(holder.itemView);
+ if (unsetTransientState) {
+ setHasTransientState(holder.itemView, false);
+ }
+ return succeed;
+ }
+
+ @Override
+ public void onViewRecycled(TestViewHolder holder) {
+ recycled.add(holder.itemView);
+ super.onViewRecycled(holder);
+ }
+ };
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, 5);
+ } else {
+ removeAndRecycleAllViews(recycler);
+ }
+ if (layoutLatch != null) {
+ layoutLatch.countDown();
+ }
+ }
+ };
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setAdapter(testAdapter);
+ recyclerView.setLayoutManager(tlm);
+ recyclerView.setItemAnimator(null);
+ setRecyclerView(recyclerView);
+ getInstrumentation().waitForIdleSync();
+ // make sure we have enough views after this position so that we'll receive the on recycled
+ // callback
+ View view = recyclerView.getChildAt(3);//this has to be greater than def cache size.
+ setHasTransientState(view, true);
+ tlm.expectLayouts(1);
+ requestLayoutOnUIThread(recyclerView);
+ tlm.waitForLayout(2);
+
+ assertTrue(failedToRecycle.contains(view));
+ assertEquals(succeed || unsetTransientState, recycled.contains(view));
+ }
+
+ public void testAdapterPositionInvalidation() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ final TestAdapter adapter = new TestAdapter(10);
+ final TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ layoutRange(recycler, 0, state.getItemCount());
+ layoutLatch.countDown();
+ }
+ };
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutManager(tlm);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i < tlm.getChildCount(); i ++) {
+ assertNotSame("adapter positions should not be undefined",
+ recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
+ RecyclerView.NO_POSITION);
+ }
+ adapter.notifyDataSetChanged();
+ for (int i = 0; i < tlm.getChildCount(); i ++) {
+ assertSame("adapter positions should be undefined",
+ recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
+ RecyclerView.NO_POSITION);
+ }
+ }
+ });
+ }
+
+ public void testAdapterPositionsBasic() throws Throwable {
+ adapterPositionsTest(null);
+ }
+
+ public void testAdapterPositionsRemoveItems() throws Throwable {
+ adapterPositionsTest(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.deleteAndNotify(3, 4);
+ }
+ });
+ }
+
+ public void testAdapterPositionsRemoveItemsBefore() throws Throwable {
+ adapterPositionsTest(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.deleteAndNotify(0, 1);
+ }
+ });
+ }
+
+ public void testAdapterPositionsAddItemsBefore() throws Throwable {
+ adapterPositionsTest(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.addAndNotify(0, 5);
+ }
+ });
+ }
+
+ public void testAdapterPositionsAddItemsInside() throws Throwable {
+ adapterPositionsTest(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.addAndNotify(3, 2);
+ }
+ });
+ }
+
+ public void testAdapterPositionsMoveItems() throws Throwable {
+ adapterPositionsTest(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.moveAndNotify(3, 5);
+ }
+ });
+ }
+
+ public void testAdapterPositionsNotifyDataSetChanged() throws Throwable {
+ adapterPositionsTest(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.mItems.clear();
+ for (int i = 0; i < 20; i ++) {
+ adapter.mItems.add(new Item(i, "added item"));
+ }
+ adapter.notifyDataSetChanged();
+ }
+ });
+ }
+
+ public void adapterPositionsTest(final AdapterRunnable adapterChanges) throws Throwable {
+ final TestAdapter testAdapter = new TestAdapter(10);
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ layoutRange(recycler, Math.min(state.getItemCount(), 2)
+ , Math.min(state.getItemCount(), 7));
+ layoutLatch.countDown();
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+ };
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setLayoutManager(tlm);
+ recyclerView.setAdapter(testAdapter);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ final int count = recyclerView.getChildCount();
+ Map<View, Integer> layoutPositions = new HashMap<View, Integer>();
+ assertTrue("test sanity", count > 0);
+ for (int i = 0; i < count; i ++) {
+ View view = recyclerView.getChildAt(i);
+ TestViewHolder vh = (TestViewHolder) recyclerView.getChildViewHolder(view);
+ int index = testAdapter.mItems.indexOf(vh.mBoundItem);
+ assertEquals("should be able to find VH with adapter position " + index, vh,
+ recyclerView.findViewHolderForAdapterPosition(index));
+ assertEquals("get adapter position should return correct index", index,
+ vh.getAdapterPosition());
+ layoutPositions.put(view, vh.mPosition);
+ }
+ if (adapterChanges != null) {
+ adapterChanges.run(testAdapter);
+ for (int i = 0; i < count; i++) {
+ View view = recyclerView.getChildAt(i);
+ TestViewHolder vh = (TestViewHolder) recyclerView
+ .getChildViewHolder(view);
+ int index = testAdapter.mItems.indexOf(vh.mBoundItem);
+ if (index >= 0) {
+ assertEquals("should be able to find VH with adapter position "
+ + index, vh,
+ recyclerView.findViewHolderForAdapterPosition(index));
+ }
+ assertSame("get adapter position should return correct index", index,
+ vh.getAdapterPosition());
+ assertSame("should be able to find view with layout position",
+ vh, mRecyclerView.findViewHolderForLayoutPosition(
+ layoutPositions.get(view)));
+ }
+
+ }
+
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+ });
+ checkForMainThreadException();
+ }
+
public void testScrollStateForSmoothScroll() throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager();
@@ -415,6 +890,7 @@
if (removeItem) {
final int newTarget = targetPosition - 10;
testAdapter.deleteAndNotify(newTarget + 1, testAdapter.getItemCount() - newTarget - 1);
+ final CountDownLatch targetCheck = new CountDownLatch(1);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
@@ -427,15 +903,18 @@
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
+ targetCheck.countDown();
}
- }, 200);
+ }, 50);
}
});
+ assertTrue("target position should be checked on time ",
+ targetCheck.await(10, TimeUnit.SECONDS));
checkForMainThreadException();
assertTrue("on stop should be called", calledOnStop.await(30, TimeUnit.SECONDS));
checkForMainThreadException();
assertNotNull("should scroll to new target " + newTarget
- , rv.findViewHolderForPosition(newTarget));
+ , rv.findViewHolderForLayoutPosition(newTarget));
if (DEBUG) {
Log.d(TAG, "on stop has been called on time");
}
@@ -443,11 +922,66 @@
assertTrue("on stop should be called eventually",
calledOnStop.await(30, TimeUnit.SECONDS));
assertNotNull("scroll to position should succeed",
- rv.findViewHolderForPosition(targetPosition));
+ rv.findViewHolderForLayoutPosition(targetPosition));
}
checkForMainThreadException();
}
+ public void testConsecutiveSmoothScroll() throws Throwable {
+ final AtomicInteger visibleChildCount = new AtomicInteger(10);
+ final AtomicInteger totalScrolled = new AtomicInteger(0);
+ final TestLayoutManager lm = new TestLayoutManager() {
+ int start = 0;
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ super.onLayoutChildren(recycler, state);
+ layoutRange(recycler, start, visibleChildCount.get());
+ layoutLatch.countDown();
+ }
+
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ totalScrolled.set(totalScrolled.get() + dy);
+ return dy;
+ }
+
+ @Override
+ public boolean canScrollVertically() {
+ return true;
+ }
+ };
+ final RecyclerView rv = new RecyclerView(getActivity());
+ TestAdapter testAdapter = new TestAdapter(500);
+ rv.setLayoutManager(lm);
+ rv.setAdapter(testAdapter);
+ lm.expectLayouts(1);
+ setRecyclerView(rv);
+ lm.waitForLayout(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ rv.smoothScrollBy(0, 2000);
+ }
+ });
+ Thread.sleep(250);
+ final AtomicInteger scrollAmt = new AtomicInteger();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int soFar = totalScrolled.get();
+ scrollAmt.set(soFar);
+ rv.smoothScrollBy(0, 5000 - soFar);
+ }
+ });
+ while(rv.getScrollState() != SCROLL_STATE_IDLE) {
+ Thread.sleep(100);
+ }
+ final int soFar = totalScrolled.get();
+ assertEquals("second scroll should be competed properly", 5000, soFar);
+ }
+
public void accessRecyclerOnOnMeasureTest(final boolean enablePredictiveAnimations)
throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
@@ -478,7 +1012,7 @@
}
assertEquals(state.toString(),
expectedOnMeasureStateCount.get(), state.getItemCount());
- } catch(Throwable t) {
+ } catch (Throwable t) {
postExceptionToInstrumentation(t);
}
super.onMeasure(recycler, state, widthSpec, heightSpec);
@@ -524,7 +1058,6 @@
TestLayoutManager lm = new TestLayoutManager() {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
- super.onLayoutChildren(recycler, state);
try {
layoutRange(recycler, 0, state.getItemCount());
layoutLatch.countDown();
@@ -538,7 +1071,6 @@
RecyclerView recyclerView = new RecyclerView(getActivity());
recyclerView.setLayoutManager(lm);
recyclerView.setAdapter(testAdapter);
- recyclerView.setLayoutManager(lm);
recyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() {
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
@@ -689,7 +1221,7 @@
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
// test
- for (int i = 0; i < getChildCount(); i ++) {
+ for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
child.getLayoutParams();
@@ -723,7 +1255,7 @@
recyclerView.setItemViewCacheSize(5);
recyclerView.setLayoutManager(testLayoutManager);
testLayoutManager.expectLayouts(1);
- setRecyclerView(recyclerView);
+ setRecyclerView(recyclerView, true, false);
testLayoutManager.waitForLayout(2);
checkForMainThreadException();
@@ -735,6 +1267,7 @@
checkForMainThreadException();
// invalidate w/o an item decorator
+
invalidateDecorOffsets(recyclerView);
testLayoutManager.expectLayouts(1);
invalidateDecorOffsets(recyclerView);
@@ -788,7 +1321,7 @@
}
public void addItemDecoration(final RecyclerView recyclerView, final
- RecyclerView.ItemDecoration itemDecoration) throws Throwable {
+ RecyclerView.ItemDecoration itemDecoration) throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
@@ -830,7 +1363,7 @@
try {
if (changes.size() > 0) {
// test
- for (int i = 0; i < getChildCount(); i ++) {
+ for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
child.getLayoutParams();
@@ -864,10 +1397,10 @@
testLayoutManager.waitForLayout(2);
int itemAddedTo = 5;
for (int i = 0; i < itemAddedTo; i++) {
- changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), false);
+ changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
}
for (int i = itemAddedTo; i < mRecyclerView.getChildCount(); i++) {
- changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), true);
+ changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
}
testLayoutManager.expectLayouts(1);
adapter.addAndNotify(5, 1);
@@ -876,19 +1409,19 @@
changes.clear();
int[] changedItems = new int[]{3, 5, 6};
- for (int i = 0; i < adapter.getItemCount(); i ++) {
- changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), false);
+ for (int i = 0; i < adapter.getItemCount(); i++) {
+ changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
}
- for (int i = 0; i < changedItems.length; i ++) {
- changes.put(mRecyclerView.findViewHolderForPosition(changedItems[i]).getItemId(), true);
+ for (int i = 0; i < changedItems.length; i++) {
+ changes.put(mRecyclerView.findViewHolderForLayoutPosition(changedItems[i]).getItemId(), true);
}
testLayoutManager.expectLayouts(1);
adapter.changePositionsAndNotify(changedItems);
testLayoutManager.waitForLayout(2);
checkForMainThreadException();
- for (int i = 0; i < adapter.getItemCount(); i ++) {
- changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), true);
+ for (int i = 0; i < adapter.getItemCount(); i++) {
+ changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
}
testLayoutManager.expectLayouts(1);
adapter.dispatchDataSetChanged();
@@ -1214,10 +1747,11 @@
adapter.addAndNotify(4, 5);
removeRecyclerView();
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ postExceptionToInstrumentation(throwable);
}
}
});
+ checkForMainThreadException();
lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
assertEquals("No extra layout should happen when detached", prevLayoutCount,
@@ -1283,10 +1817,10 @@
View view = getChildAt(i);
TestViewHolder tvh = (TestViewHolder) mRecyclerView
.getChildViewHolder(view);
- final int oldPos = previousItems.indexOf(tvh.mBindedItem);
+ final int oldPos = previousItems.indexOf(tvh.mBoundItem);
assertEquals("view holder's position should be correct",
oldPositionToNewPositionMapping.get(oldPos).intValue(),
- tvh.getPosition());
+ tvh.getLayoutPosition());
;
}
}
@@ -1335,6 +1869,92 @@
checkForMainThreadException();
}
+ public void testCallbacksDuringAdapterSwap() throws Throwable {
+ callbacksDuringAdapterChange(true);
+ }
+
+ public void testCallbacksDuringAdapterSet() throws Throwable {
+ callbacksDuringAdapterChange(false);
+ }
+
+ public void callbacksDuringAdapterChange(boolean swap) throws Throwable {
+ final TestAdapter2 adapter1 = swap ? createBinderCheckingAdapter()
+ : createOwnerCheckingAdapter();
+ final TestAdapter2 adapter2 = swap ? createBinderCheckingAdapter()
+ : createOwnerCheckingAdapter();
+
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ layoutRange(recycler, 0, state.getItemCount());
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ layoutLatch.countDown();
+ }
+ };
+ RecyclerView rv = new RecyclerView(getActivity());
+ rv.setAdapter(adapter1);
+ rv.setLayoutManager(tlm);
+ tlm.expectLayouts(1);
+ setRecyclerView(rv);
+ tlm.waitForLayout(1);
+ checkForMainThreadException();
+ tlm.expectLayouts(1);
+ if (swap) {
+ swapAdapter(adapter2, true);
+ } else {
+ setAdapter(adapter2);
+ }
+ checkForMainThreadException();
+ tlm.waitForLayout(1);
+ checkForMainThreadException();
+ }
+
+ private TestAdapter2 createOwnerCheckingAdapter() {
+ return new TestAdapter2(10) {
+ @Override
+ public void onViewRecycled(TestViewHolder2 holder) {
+ assertSame("on recycled should be called w/ the creator adapter", this,
+ holder.mData);
+ super.onViewRecycled(holder);
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder2 holder, int position) {
+ super.onBindViewHolder(holder, position);
+ assertSame("on bind should be called w/ the creator adapter", this, holder.mData);
+ }
+
+ @Override
+ public TestViewHolder2 onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ final TestViewHolder2 vh = super.onCreateViewHolder(parent, viewType);
+ vh.mData = this;
+ return vh;
+ }
+ };
+ }
+
+ private TestAdapter2 createBinderCheckingAdapter() {
+ return new TestAdapter2(10) {
+ @Override
+ public void onViewRecycled(TestViewHolder2 holder) {
+ assertSame("on recycled should be called w/ the creator adapter", this,
+ holder.mData);
+ holder.mData = null;
+ super.onViewRecycled(holder);
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder2 holder, int position) {
+ super.onBindViewHolder(holder, position);
+ holder.mData = this;
+ }
+ };
+ }
+
public void testFindViewById() throws Throwable {
findViewByIdTest(false);
removeRecyclerView();
@@ -1386,7 +2006,7 @@
TestViewHolder viewHolder =
(TestViewHolder) mRecyclerView.getChildViewHolder(view);
assertSame("should be the correct item " + viewHolder
- , viewHolder.mBindedItem,
+ , viewHolder.mBoundItem,
adapter.mItems.get(viewHolder.mPosition));
assertFalse("view should not be marked as removed",
((RecyclerView.LayoutParams) view.getLayoutParams())
@@ -1465,7 +2085,7 @@
@Override
public void run() {
for (int i = 2; i < 4; i++) {
- RecyclerView.ViewHolder vh = recyclerView.findViewHolderForPosition(i);
+ RecyclerView.ViewHolder vh = recyclerView.findViewHolderForLayoutPosition(i);
assertEquals("View holder's type should match latest type", viewType.get(),
vh.getItemViewType());
}
@@ -1590,13 +2210,33 @@
structureChanged.get());
}
+ public void testDetachWithoutLayoutManager() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ setRecyclerView(recyclerView);
+ removeRecyclerView();
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+ });
+ checkForMainThreadException();
+ }
+
private static class TestViewHolder2 extends RecyclerView.ViewHolder {
+
+ Object mData;
+
public TestViewHolder2(View itemView) {
super(itemView);
}
}
private static class TestAdapter2 extends RecyclerView.Adapter<TestViewHolder2> {
+
List<Item> mItems;
private TestAdapter2(int count) {
@@ -1624,4 +2264,8 @@
}
}
+ private static interface AdapterRunnable {
+ public void run(TestAdapter adapter) throws Throwable;
+ }
+
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
index bc80da0..e23a114 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
+import android.os.Debug;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,6 +34,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -84,6 +86,140 @@
mLayoutManager.setGapStrategy(config.mGapStrategy);
mLayoutManager.setReverseLayout(config.mReverseLayout);
mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ try {
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ assertNotNull("view should have layout params assigned", lp);
+ assertNotNull("when item offsets are requested, view should have a valid span",
+ lp.mSpan);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+ });
+ }
+
+ public void testAreAllStartsTheSame() throws Throwable {
+ setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE).itemCount(300));
+ waitFirstLayout();
+ smoothScrollToPosition(100);
+ mLayoutManager.expectLayouts(1);
+ mAdapter.deleteAndNotify(0, 2);
+ mLayoutManager.waitForLayout(2);
+ smoothScrollToPosition(0);
+ assertFalse("all starts should not be the same", mLayoutManager.areAllStartsEqual());
+ }
+
+ public void testAreAllEndsTheSame() throws Throwable {
+ setupByConfig(new Config(VERTICAL, true, 3, GAP_HANDLING_NONE).itemCount(300));
+ waitFirstLayout();
+ smoothScrollToPosition(100);
+ mLayoutManager.expectLayouts(1);
+ mAdapter.deleteAndNotify(0, 2);
+ mLayoutManager.waitForLayout(2);
+ smoothScrollToPosition(0);
+ assertFalse("all ends should not be the same", mLayoutManager.areAllEndsEqual());
+ }
+
+ public void testFindLastInUnevenDistribution() throws Throwable {
+ setupByConfig(new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)
+ .itemCount(5));
+ mAdapter.mOnBindHandler = new OnBindHandler() {
+ @Override
+ void onBoundItem(TestViewHolder vh, int position) {
+ LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
+ if (position == 1) {
+ lp.height = mRecyclerView.getHeight() - 10;
+ } else {
+ lp.height = 5;
+ }
+ }
+ };
+ waitFirstLayout();
+ int[] into = new int[2];
+ mLayoutManager.findFirstCompletelyVisibleItemPositions(into);
+ assertEquals("first completely visible item from span 0 should be 0", 0, into[0]);
+ assertEquals("first completely visible item from span 1 should be 1", 1, into[1]);
+ mLayoutManager.findLastCompletelyVisibleItemPositions(into);
+ assertEquals("last completely visible item from span 0 should be 4", 4, into[0]);
+ assertEquals("last completely visible item from span 1 should be 1", 1, into[1]);
+ assertEquals("first fully visible child should be at position",
+ 0, mRecyclerView.getChildViewHolder(mLayoutManager.
+ findFirstVisibleItemClosestToStart(true, true)).getPosition());
+ assertEquals("last fully visible child should be at position",
+ 4, mRecyclerView.getChildViewHolder(mLayoutManager.
+ findFirstVisibleItemClosestToEnd(true, true)).getPosition());
+
+ assertEquals("first visible child should be at position",
+ 0, mRecyclerView.getChildViewHolder(mLayoutManager.
+ findFirstVisibleItemClosestToStart(false, true)).getPosition());
+ assertEquals("last visible child should be at position",
+ 4, mRecyclerView.getChildViewHolder(mLayoutManager.
+ findFirstVisibleItemClosestToEnd(false, true)).getPosition());
+
+ }
+
+ public void testCustomWidthInHorizontal() throws Throwable {
+ customSizeInScrollDirectionTest(
+ new Config(HORIZONTAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+ }
+
+ public void testCustomHeightInVertical() throws Throwable {
+ customSizeInScrollDirectionTest(
+ new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+ }
+
+ public void customSizeInScrollDirectionTest(final Config config) throws Throwable {
+ setupByConfig(config);
+ final Map<View, Integer> sizeMap = new HashMap<View, Integer>();
+ mAdapter.mOnBindHandler = new OnBindHandler() {
+ @Override
+ void onBoundItem(TestViewHolder vh, int position) {
+ final ViewGroup.LayoutParams layoutParams = vh.itemView.getLayoutParams();
+ final int size = 1 + position * 5;
+ if (config.mOrientation == HORIZONTAL) {
+ layoutParams.width = size;
+ } else {
+ layoutParams.height = size;
+ }
+ sizeMap.put(vh.itemView, size);
+ if (position == 3) {
+ getLp(vh.itemView).setFullSpan(true);
+ }
+ }
+
+ @Override
+ boolean assignRandomSize() {
+ return false;
+ }
+ };
+ waitFirstLayout();
+ assertTrue("[test sanity] some views should be laid out", sizeMap.size() > 0);
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+ View child = mRecyclerView.getChildAt(i);
+ final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
+ : child.getHeight();
+ assertEquals("child " + i + " should have the size specified in its layout params",
+ sizeMap.get(child).intValue(), size);
+ }
+ checkForMainThreadException();
+ }
+
+ public void testGrowLookup() throws Throwable {
+ setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+ waitFirstLayout();
+ mLayoutManager.expectLayouts(1);
+ mAdapter.mItems.clear();
+ mAdapter.dispatchDataSetChanged();
+ mLayoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ mLayoutManager.expectLayouts(2);
+ mAdapter.addAndNotify(0, 30);
+ mLayoutManager.waitForLayout(2);
+ checkForMainThreadException();
}
public void testRTL() throws Throwable {
@@ -115,7 +251,7 @@
OrientationHelper helper = OrientationHelper.createHorizontalHelper(mLayoutManager);
View child0 = mLayoutManager.findViewByPosition(0);
View child1 = mLayoutManager.findViewByPosition(config.mOrientation == VERTICAL ? 1
- : config.mSpanCount);
+ : config.mSpanCount);
assertNotNull(logPrefix + " child position 0 should be laid out", child0);
assertNotNull(logPrefix + " child position 0 should be laid out", child1);
if (config.mOrientation == VERTICAL || !config.mReverseLayout) {
@@ -141,14 +277,15 @@
}
}
- public void scrollBackAndPreservePositionsTest(final Config config, final boolean saveRestoreInBetween)
+ public void scrollBackAndPreservePositionsTest(final Config config,
+ final boolean saveRestoreInBetween)
throws Throwable {
setupByConfig(config);
mAdapter.mOnBindHandler = new OnBindHandler() {
@Override
- public void onBoundItem(TestViewHolder vh, int postion) {
+ public void onBoundItem(TestViewHolder vh, int position) {
LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
- lp.setFullSpan((postion * 7) % (config.mSpanCount + 1) == 0);
+ lp.setFullSpan((position * 7) % (config.mSpanCount + 1) == 0);
}
};
waitFirstLayout();
@@ -157,7 +294,6 @@
final int scrollStep = (mLayoutManager.mPrimaryOrientation.getTotalSpace() / 10)
* (config.mReverseLayout ? -1 : 1);
-
final int[] globalPos = new int[1];
runTestOnUiThread(new Runnable() {
@Override
@@ -166,7 +302,7 @@
while (globalPositions[mAdapter.getItemCount() - 1] == Integer.MIN_VALUE) {
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
View child = mRecyclerView.getChildAt(i);
- final int pos = mRecyclerView.getChildPosition(child);
+ final int pos = mRecyclerView.getChildLayoutPosition(child);
if (globalPositions[pos] != Integer.MIN_VALUE) {
continue;
}
@@ -207,7 +343,7 @@
while (!shouldTest.isEmpty() && scrollAmount != 0) {
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
View child = mRecyclerView.getChildAt(i);
- int pos = mRecyclerView.getChildPosition(child);
+ int pos = mRecyclerView.getChildLayoutPosition(child);
if (!shouldTest.get(pos)) {
continue;
}
@@ -262,13 +398,13 @@
scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
}
} else {
- RecyclerView.ViewHolder vh = rv.findViewHolderForPosition(scrollPosition);
+ RecyclerView.ViewHolder vh = rv.findViewHolderForLayoutPosition(scrollPosition);
assertNotNull("scroll to position should work", vh);
if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
assertEquals("scroll offset should be applied properly",
mLayoutManager.getPaddingTop() + scrollOffset
+ ((RecyclerView.LayoutParams) vh.itemView
- .getLayoutParams()).topMargin,
+ .getLayoutParams()).topMargin,
mLayoutManager.getDecoratedTop(vh.itemView));
}
}
@@ -352,6 +488,7 @@
while (mLayoutManager.isSmoothScrolling() ||
mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
runTestOnUiThread(viewInBoundsTest);
+ checkForMainThreadException();
Thread.sleep(400);
}
// delete all items
@@ -383,6 +520,84 @@
});
mLayoutManager.waitForLayout(2);
runTestOnUiThread(viewInBoundsTest);
+ checkForMainThreadException();
+ }
+
+ public void testMoveGapHandling() throws Throwable {
+ Config config = new Config().spanCount(2).itemCount(40);
+ setupByConfig(config);
+ waitFirstLayout();
+ mLayoutManager.expectLayouts(2);
+ mAdapter.moveAndNotify(4, 1);
+ mLayoutManager.waitForLayout(2);
+ assertNull("moving item to upper should not cause gaps", mLayoutManager.hasGapsToFix());
+ }
+
+ public void testUpdateAfterFullSpan() throws Throwable {
+ updateAfterFullSpanGapHandlingTest(0);
+ }
+
+ public void testUpdateAfterFullSpan2() throws Throwable {
+ updateAfterFullSpanGapHandlingTest(20);
+ }
+
+ public void testTemporaryGapHandling() throws Throwable {
+ int fullSpanIndex = 200;
+ setupByConfig(new Config().spanCount(2).itemCount(500));
+ mAdapter.mFullSpanItems.add(fullSpanIndex);
+ waitFirstLayout();
+ smoothScrollToPosition(fullSpanIndex + 30);
+ mLayoutManager.expectLayouts(1);
+ mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
+ mLayoutManager.waitForLayout(1);
+ smoothScrollToPosition(0);
+ mLayoutManager.expectLayouts(1);
+ smoothScrollToPosition(fullSpanIndex + 5);
+ mLayoutManager.assertNoLayout("if an interim gap is fixed, it should not cause a "
+ + "relayout", 2);
+ View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
+
+ View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
+ View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
+
+ LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
+ LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
+ assertEquals("view 1 span index", 0, lp1.getSpanIndex());
+ assertEquals("view 2 span index", 1, lp2.getSpanIndex());
+ assertEquals("no gap between span and view 1",
+ mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+ mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
+ assertEquals("no gap between span and view 2",
+ mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+ mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
+ }
+
+ public void updateAfterFullSpanGapHandlingTest(int fullSpanIndex) throws Throwable {
+ setupByConfig(new Config().spanCount(2).itemCount(100));
+ mAdapter.mFullSpanItems.add(fullSpanIndex);
+ waitFirstLayout();
+ smoothScrollToPosition(fullSpanIndex + 30);
+ mLayoutManager.expectLayouts(1);
+ mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
+ mLayoutManager.waitForLayout(1);
+ smoothScrollToPosition(fullSpanIndex);
+ // give it some time to fix the gap
+ Thread.sleep(500);
+ View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
+
+ View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
+ View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
+
+ LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
+ LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
+ assertEquals("view 1 span index", 0, lp1.getSpanIndex());
+ assertEquals("view 2 span index", 1, lp2.getSpanIndex());
+ assertEquals("no gap between span and view 1",
+ mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+ mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
+ assertEquals("no gap between span and view 2",
+ mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+ mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
}
public void testInnerGapHandling() throws Throwable {
@@ -454,9 +669,9 @@
public void testGapAtTheBeginning() throws Throwable {
for (Config config : mBaseVariations) {
- for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount ++) {
+ for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount++) {
for (int deletePosition = config.mSpanCount - 1;
- deletePosition < config.mSpanCount + 2; deletePosition ++) {
+ deletePosition < config.mSpanCount + 2; deletePosition++) {
gapAtTheBeginningOfTheListTest(config, deletePosition, deleteCount);
removeRecyclerView();
}
@@ -481,7 +696,7 @@
smoothScrollToPosition(config.mItemCount / 2);
// assert to be deleted child is not visible
assertNull(logPrefix + " test sanity, to be deleted child should be invisible",
- mRecyclerView.findViewHolderForPosition(deletePosition));
+ mRecyclerView.findViewHolderForLayoutPosition(deletePosition));
// delete the child and notify
mAdapter.deleteAndNotify(deletePosition, deleteCount);
getInstrumentation().waitForIdleSync();
@@ -537,8 +752,9 @@
// Same as Arrays.copyOfRange but for API 7
private int[] copyOfRange(int[] original, int from, int to) {
int newLength = to - from;
- if (newLength < 0)
+ if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
+ }
int[] copy = new int[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
@@ -779,33 +995,44 @@
}
}
- private void saveRestore(Config config) throws Throwable {
- Parcelable savedState = mRecyclerView.onSaveInstanceState();
- // we append a suffix to the parcelable to test out of bounds
- String parcelSuffix = UUID.randomUUID().toString();
- Parcel parcel = Parcel.obtain();
- savedState.writeToParcel(parcel, 0);
- parcel.writeString(parcelSuffix);
- removeRecyclerView();
- // reset for reading
- parcel.setDataPosition(0);
- // re-create
- savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
- RecyclerView restored = new RecyclerView(getActivity());
- mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
- mLayoutManager.setGapStrategy(config.mGapStrategy);
- restored.setLayoutManager(mLayoutManager);
- // use the same adapter for Rect matching
- restored.setAdapter(mAdapter);
- restored.onRestoreInstanceState(savedState);
- if (Looper.myLooper() == Looper.getMainLooper()) {
- mLayoutManager.expectLayouts(1);
- setRecyclerView(restored);
- } else {
- mLayoutManager.expectLayouts(1);
- setRecyclerView(restored);
- mLayoutManager.waitForLayout(2);
- }
+ private void saveRestore(final Config config) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Parcelable savedState = mRecyclerView.onSaveInstanceState();
+ // we append a suffix to the parcelable to test out of bounds
+ String parcelSuffix = UUID.randomUUID().toString();
+ Parcel parcel = Parcel.obtain();
+ savedState.writeToParcel(parcel, 0);
+ parcel.writeString(parcelSuffix);
+ removeRecyclerView();
+ // reset for reading
+ parcel.setDataPosition(0);
+ // re-create
+ savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+ RecyclerView restored = new RecyclerView(getActivity());
+ mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
+ config.mOrientation);
+ mLayoutManager.setGapStrategy(config.mGapStrategy);
+ restored.setLayoutManager(mLayoutManager);
+ // use the same adapter for Rect matching
+ restored.setAdapter(mAdapter);
+ restored.onRestoreInstanceState(savedState);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(restored);
+ } else {
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(restored);
+ mLayoutManager.waitForLayout(2);
+ }
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+ });
+ checkForMainThreadException();
}
public void savedStateTest(Config config, boolean waitForLayout,
@@ -856,7 +1083,7 @@
assertEquals(config + " on saved state, gap strategy should be preserved",
config.mGapStrategy, mLayoutManager.getGapStrategy());
assertEquals(config + " on saved state, first completely visible child position should"
- + " be preserved", firstCompletelyVisiblePosition,
+ + " be preserved", firstCompletelyVisiblePosition,
mLayoutManager.findFirstVisibleItemPositionInt());
if (waitForLayout) {
assertRectSetsEqual(config + "\npost layout op:" + postLayoutOperations.describe()
@@ -890,7 +1117,7 @@
while (testCount-- > 0) {
// get middle child
final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
- final int position = mRecyclerView.getChildPosition(child);
+ final int position = mRecyclerView.getChildLayoutPosition(child);
final int startOffset = config.mReverseLayout ?
orientationHelper.getEndAfterPadding() - orientationHelper
.getDecoratedEnd(child)
@@ -958,7 +1185,7 @@
int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
View child = mLayoutManager.getChildAt(i);
- int position = mRecyclerView.getChildPosition(child);
+ int position = mRecyclerView.getChildLayoutPosition(child);
if (position < minPosition) {
minPosition = position;
}
@@ -996,12 +1223,12 @@
Rect bounds = mLayoutManager.getViewBounds(view);
if (layoutBounds.contains(bounds)) {
Map<Item, Rect> initialBounds = mLayoutManager.collectChildCoordinates();
- final int position = mRecyclerView.getChildPosition(view);
+ final int position = mRecyclerView.getChildLayoutPosition(view);
LayoutParams layoutParams
= (LayoutParams) (view.getLayoutParams());
TestViewHolder vh = (TestViewHolder) layoutParams.mViewHolder;
assertEquals("recycler view mPosition should match adapter mPosition", position,
- vh.mBindedItem.mAdapterIndex);
+ vh.mBoundItem.mAdapterIndex);
if (DEBUG) {
Log.d(TAG, "testing scroll to visible mPosition at " + position
+ " " + bounds + " inside " + layoutBounds);
@@ -1020,12 +1247,12 @@
config + "scroll to mPosition on fully visible child should be no-op",
initialBounds, mLayoutManager.collectChildCoordinates());
} else {
- final int position = mRecyclerView.getChildPosition(view);
+ final int position = mRecyclerView.getChildLayoutPosition(view);
if (DEBUG) {
Log.d(TAG,
"child(" + position + ") not fully visible " + bounds + " not inside "
+ layoutBounds
- + mRecyclerView.getChildPosition(view)
+ + mRecyclerView.getChildLayoutPosition(view)
);
}
mLayoutManager.expectLayouts(1);
@@ -1220,9 +1447,11 @@
final AccessibilityRecordCompat record = AccessibilityEventCompat
.asRecord(event);
final int start = mRecyclerView
- .getChildPosition(mLayoutManager.findFirstVisibleItemClosestToStart(false));
+ .getChildLayoutPosition(
+ mLayoutManager.findFirstVisibleItemClosestToStart(false, true));
final int end = mRecyclerView
- .getChildPosition(mLayoutManager.findFirstVisibleItemClosestToEnd(false));
+ .getChildLayoutPosition(
+ mLayoutManager.findFirstVisibleItemClosestToEnd(false, true));
assertEquals("first item position should match",
Math.min(start, end), record.getFromIndex());
assertEquals("last item position should match",
@@ -1332,8 +1561,12 @@
// test layout params assignment
static class OnLayoutListener {
- void before(RecyclerView.Recycler recycler, RecyclerView.State state){}
- void after(RecyclerView.Recycler recycler, RecyclerView.State state){}
+
+ void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+
+ void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
}
class WrappedLayoutManager extends StaggeredGridLayoutManager {
@@ -1495,7 +1728,7 @@
LayoutParams lp = (LayoutParams) child
.getLayoutParams();
TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
- items.put(vh.mBindedItem, getViewBounds(child));
+ items.put(vh.mBoundItem, getViewBounds(child));
}
}
});
@@ -1626,12 +1859,6 @@
int position) {
super.onBindViewHolder(holder, position);
Item item = mItems.get(position);
- final int minSize = mViewsHaveEqualSize ? 200 : 200 + 20 * (position % 10);
- if (mOrientation == OrientationHelper.HORIZONTAL) {
- holder.itemView.setMinimumWidth(minSize);
- } else {
- holder.itemView.setMinimumHeight(minSize);
- }
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
.getLayoutParams();
if (lp instanceof LayoutParams) {
@@ -1643,10 +1870,19 @@
slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
lp = slp;
}
- lp.topMargin = 3;
- lp.leftMargin = 5;
- lp.rightMargin = 7;
- lp.bottomMargin = 9;
+
+ if (mOnBindHandler == null || mOnBindHandler.assignRandomSize()) {
+ final int minSize = mViewsHaveEqualSize ? 200 : 200 + 20 * (position % 10);
+ if (mOrientation == OrientationHelper.HORIZONTAL) {
+ holder.itemView.setMinimumWidth(minSize);
+ } else {
+ holder.itemView.setMinimumHeight(minSize);
+ }
+ lp.topMargin = 3;
+ lp.leftMargin = 5;
+ lp.rightMargin = 7;
+ lp.bottomMargin = 9;
+ }
if (mOnBindHandler != null) {
mOnBindHandler.onBoundItem(holder, position);
@@ -1654,8 +1890,13 @@
}
}
- static interface OnBindHandler {
- void onBoundItem(TestViewHolder vh, int postion);
+ abstract static class OnBindHandler {
+
+ abstract void onBoundItem(TestViewHolder vh, int position);
+
+ boolean assignRandomSize() {
+ return true;
+ }
}
static class Config implements Cloneable {