Merge "MediaBrowserServiceCompat: Make SubscriptionCallback.onError be called" into nyc-support-25.2-dev
diff --git a/api/current.txt b/api/current.txt
index 917fd00..bc0e500 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2259,6 +2259,7 @@
     method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
     method public android.graphics.drawable.Drawable getBadgeDrawable();
     method public android.content.Intent getRecognizerIntent();
+    method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
     method public java.lang.String getTitle();
     method public static android.support.v17.leanback.app.SearchFragment newInstance(java.lang.String);
     method public void setBadgeDrawable(android.graphics.drawable.Drawable);
@@ -2288,6 +2289,7 @@
     method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
     method public android.graphics.drawable.Drawable getBadgeDrawable();
     method public android.content.Intent getRecognizerIntent();
+    method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
     method public java.lang.String getTitle();
     method public static android.support.v17.leanback.app.SearchSupportFragment newInstance(java.lang.String);
     method public void setBadgeDrawable(android.graphics.drawable.Drawable);
diff --git a/design/build.gradle b/design/build.gradle
index 37e6625..d5f83b8 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -73,6 +73,12 @@
     aaptOptions {
         additionalParameters "--no-version-vectors"
     }
+
+    buildTypes {
+        debug {
+            pseudoLocalesEnabled true
+        }
+    }
 }
 
 android.libraryVariants.all { variant ->
diff --git a/design/res/values/password_visibility.xml b/design/res/values/password_visibility.xml
index 23faec3..1c85c1a 100644
--- a/design/res/values/password_visibility.xml
+++ b/design/res/values/password_visibility.xml
@@ -18,10 +18,10 @@
 <resources>
 
     <!-- Resources used in the password visibility anim, see @drawable/design_password_eye -->
-    <string name="path_password_eye_mask_visible">M2,4.27 L2,4.27 L4.54,1.73 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>
-    <string name="path_password_eye_mask_strike_through">M2,4.27 L19.73,22 L22.27,19.46 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>
-    <string name="path_password_eye">M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z</string>
-    <string name="path_password_strike_through">M3.27,4.27 L19.74,20.74</string>
+    <string name="path_password_eye_mask_visible" translatable="false">M2,4.27 L2,4.27 L4.54,1.73 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>
+    <string name="path_password_eye_mask_strike_through" translatable="false">M2,4.27 L19.73,22 L22.27,19.46 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>
+    <string name="path_password_eye" translatable="false">M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z</string>
+    <string name="path_password_strike_through" translatable="false">M3.27,4.27 L19.74,20.74</string>
     <integer name="show_password_duration">200</integer>
     <integer name="hide_password_duration">320</integer>
 
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutPseudoLocaleTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutPseudoLocaleTest.java
new file mode 100755
index 0000000..7189f20
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutPseudoLocaleTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.typeText;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.support.design.test.R;
+import android.support.test.filters.SmallTest;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Locale;
+
+@SmallTest
+public class TextInputLayoutPseudoLocaleTest extends
+        BaseInstrumentationTestCase<TextInputLayoutActivity> {
+
+    private static final String ORIGINAL_LANGUAGE = Locale.getDefault().getLanguage();
+    private static final String ORIGINAL_COUNTRY = Locale.getDefault().getLanguage();
+
+    @BeforeClass
+    public static void setup() {
+        // Change language to pseudo locale.
+        setLocale("ar", "XB", getContext());
+    }
+
+    public TextInputLayoutPseudoLocaleTest() {
+        super(TextInputLayoutActivity.class);
+    }
+
+    private static void setLocale(String language,  String country,  Context context) {
+        context = context.getApplicationContext();
+        Resources resources = context.getResources();
+        Configuration configuration = resources.getConfiguration();
+        configuration.setLocale(new Locale(language, country));
+        resources.updateConfiguration(configuration, resources.getDisplayMetrics());
+    }
+
+    @Test
+    public void testSimpleEdit() {
+        // Type some text
+        onView(withId(R.id.textinput_edittext)).perform(typeText("123"));
+    }
+
+    @AfterClass
+    public static void  cleanup() {
+        setLocale(ORIGINAL_LANGUAGE, ORIGINAL_COUNTRY, getContext());
+    }
+}
diff --git a/media-compat/tests/src/android/support/v4/media/MediaItemTest.java b/media-compat/tests/src/android/support/v4/media/MediaItemTest.java
new file mode 100644
index 0000000..bd2565f
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/MediaItemTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+
+import org.junit.Test;
+
+/**
+ * Test {@link MediaBrowserCompat.MediaItem}.
+ */
+public class MediaItemTest {
+    private static final String DESCRIPTION = "test_description";
+    private static final String MEDIA_ID = "test_media_id";
+    private static final String TITLE = "test_title";
+    private static final String SUBTITLE = "test_subtitle";
+
+    @Test
+    @SmallTest
+    public void testBrowsableMediaItem() {
+        MediaDescriptionCompat description =
+                new MediaDescriptionCompat.Builder()
+                        .setDescription(DESCRIPTION)
+                        .setMediaId(MEDIA_ID)
+                        .setTitle(TITLE)
+                        .setSubtitle(SUBTITLE)
+                        .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
+        assertTrue(mediaItem.isBrowsable());
+        assertFalse(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+
+    @Test
+    @SmallTest
+    public void testPlayableMediaItem() {
+        MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
+                .setDescription(DESCRIPTION)
+                .setMediaId(MEDIA_ID)
+                .setTitle(TITLE)
+                .setSubtitle(SUBTITLE)
+                .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
+        assertFalse(mediaItem.isBrowsable());
+        assertTrue(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
index 9fac4ab..d23ce05 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
@@ -38,7 +38,9 @@
 import com.example.android.supportv4.R;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * A Fragment that lists all the various browsable queues available
@@ -66,6 +68,7 @@
     private final List<MediaBrowserCompat.MediaItem> mMediaItems = new ArrayList<>();
 
     private boolean mCanLoadNewPage;
+    private final Set<Integer> mSubscribedPages = new HashSet<Integer>();
     private MediaBrowserCompat mMediaBrowser;
     private BrowseAdapter mBrowserAdapter;
 
@@ -234,9 +237,15 @@
     public void onStop() {
         super.onStop();
         mMediaBrowser.disconnect();
+        mSubscribedPages.clear();
     }
 
     private void loadPage(int page) {
+        Integer pageInteger = Integer.valueOf(page);
+        if (mSubscribedPages.contains(pageInteger)) {
+            return;
+        }
+        mSubscribedPages.add(pageInteger);
         Bundle options = new Bundle();
         options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
         options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, PAGE_SIZE);
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 d2b9bb1..d65937c 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -417,6 +417,16 @@
         super.onDestroy();
     }
 
+    /**
+     * Returns RowsFragment that shows result rows. RowsFragment is initialized after
+     * SearchFragment.onCreateView().
+     *
+     * @return RowsFragment that shows result rows.
+     */
+    public RowsFragment getRowsFragment() {
+        return mRowsFragment;
+    }
+
     private void releaseRecognizer() {
         if (null != mSpeechRecognizer) {
             mSearchBar.setSpeechRecognizer(null);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
index b870d79..ae4c700 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -420,6 +420,16 @@
         super.onDestroy();
     }
 
+    /**
+     * Returns RowsSupportFragment that shows result rows. RowsSupportFragment is initialized after
+     * SearchSupportFragment.onCreateView().
+     *
+     * @return RowsSupportFragment that shows result rows.
+     */
+    public RowsSupportFragment getRowsSupportFragment() {
+        return mRowsSupportFragment;
+    }
+
     private void releaseRecognizer() {
         if (null != mSpeechRecognizer) {
             mSearchBar.setSpeechRecognizer(null);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index 150a3fd..2da193b 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -492,37 +492,42 @@
     }
 
     void refreshRoute() {
+        final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+        final boolean isRemote = !route.isDefaultOrBluetooth() && route.matchesSelector(mSelector);
+        final boolean isConnecting = isRemote && route.isConnecting();
+        boolean needsRefresh = false;
+        if (mRemoteActive != isRemote) {
+            mRemoteActive = isRemote;
+            needsRefresh = true;
+        }
+        if (mIsConnecting != isConnecting) {
+            mIsConnecting = isConnecting;
+            needsRefresh = true;
+        }
+
+        if (needsRefresh) {
+            updateContentDescription();
+            refreshDrawableState();
+        }
         if (mAttachedToWindow) {
-            final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
-            final boolean isRemote = !route.isDefaultOrBluetooth()
-                    && route.matchesSelector(mSelector);
-            final boolean isConnecting = isRemote && route.isConnecting();
-
-            boolean needsRefresh = false;
-            if (mRemoteActive != isRemote) {
-                mRemoteActive = isRemote;
-                needsRefresh = true;
-            }
-            if (mIsConnecting != isConnecting) {
-                mIsConnecting = isConnecting;
-                needsRefresh = true;
-            }
-
-            if (needsRefresh) {
-                updateContentDescription();
-                refreshDrawableState();
-                if (mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
-                    AnimationDrawable curDrawable =
-                            (AnimationDrawable) mRemoteIndicator.getCurrent();
-                    if (!curDrawable.isRunning()) {
-                        curDrawable.start();
-                    }
-                }
-            }
-
             setEnabled(mRouter.isRouteAvailable(mSelector,
                     MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
         }
+        if (mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
+            AnimationDrawable curDrawable = (AnimationDrawable) mRemoteIndicator.getCurrent();
+            if (mAttachedToWindow) {
+                if ((needsRefresh || isConnecting) && !curDrawable.isRunning()) {
+                    curDrawable.start();
+                }
+            } else if (isRemote && !isConnecting) {
+                // When the route is already connected before the view is attached, show the last
+                // frame of the connected animation immediately.
+                if (curDrawable.isRunning()) {
+                    curDrawable.stop();
+                }
+                curDrawable.selectDrawable(curDrawable.getNumberOfFrames() - 1);
+            }
+        }
     }
 
     private void updateContentDescription() {