Merge "Fix start/end margin for CarListDialog's dividers." into oc-mr1-jetpack-dev
diff --git a/car/res/layout/car_paged_recycler_view.xml b/car/res/layout/car_paged_recycler_view.xml
index e7b1f61..d1312bf 100644
--- a/car/res/layout/car_paged_recycler_view.xml
+++ b/car/res/layout/car_paged_recycler_view.xml
@@ -30,7 +30,7 @@
         android:id="@+id/paged_scroll_view"
         android:layout_width="@dimen/car_margin"
         android:layout_height="match_parent"
-        android:paddingBottom="@dimen/car_scroll_bar_padding"
-        android:paddingTop="@dimen/car_scroll_bar_padding"
+        android:paddingBottom="@dimen/car_padding_4"
+        android:paddingTop="@dimen/car_padding_4"
         android:visibility="invisible" />
 </FrameLayout>
diff --git a/car/res/layout/car_paged_scrollbar_buttons.xml b/car/res/layout/car_paged_scrollbar_buttons.xml
index b32877f..b126b48 100644
--- a/car/res/layout/car_paged_scrollbar_buttons.xml
+++ b/car/res/layout/car_paged_scrollbar_buttons.xml
@@ -36,8 +36,8 @@
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
-        android:layout_marginBottom="@dimen/car_scroll_bar_thumb_margin"
-        android:layout_marginTop="@dimen/car_scroll_bar_thumb_margin" >
+        android:layout_marginBottom="@dimen/car_padding_2"
+        android:layout_marginTop="@dimen/car_padding_2" >
 
         <View
             android:id="@+id/scrollbar_thumb"
diff --git a/car/res/values-h668dp/dimens.xml b/car/res/values-h668dp/dimens.xml
index 4c7aae6..ab0f120 100644
--- a/car/res/values-h668dp/dimens.xml
+++ b/car/res/values-h668dp/dimens.xml
@@ -34,12 +34,6 @@
     <!-- Slide Up Menu -->
     <dimen name="car_slide_up_menu_initial_height">116dp</dimen>
 
-    <!-- Scroll Bar -->
-    <dimen name="car_scroll_bar_padding">@dimen/car_padding_4</dimen>
-
-    <!-- Scroll Bar Thumb -->
-    <dimen name="car_scroll_bar_thumb_margin">@dimen/car_padding_2</dimen>
-
     <!-- Scroll Bar Buttons -->
     <dimen name="car_scroll_bar_button_size">76dp</dimen>
 
diff --git a/car/res/values/dimens.xml b/car/res/values/dimens.xml
index 8312800..4a5010f 100644
--- a/car/res/values/dimens.xml
+++ b/car/res/values/dimens.xml
@@ -122,12 +122,8 @@
     <dimen name="car_seekbar_thumb_size">20dp</dimen>
     <dimen name="car_seekbar_thumb_stroke">1dp</dimen>
 
-    <!-- Scroll Bar -->
-    <dimen name="car_scroll_bar_padding">@dimen/car_padding_2</dimen>
-
     <!-- Scroll Bar Thumb -->
     <dimen name="car_scroll_bar_thumb_width">6dp</dimen>
-    <dimen name="car_scroll_bar_thumb_margin">@dimen/car_padding_1</dimen>
 
     <!-- Scroll Bar and Alpha Jump Buttons -->
     <dimen name="car_scroll_bar_button_size">56dp</dimen>
diff --git a/car/src/androidTest/AndroidManifest.xml b/car/src/androidTest/AndroidManifest.xml
index 79ac201..24b98bc 100644
--- a/car/src/androidTest/AndroidManifest.xml
+++ b/car/src/androidTest/AndroidManifest.xml
@@ -21,9 +21,13 @@
     <application android:supportsRtl="true">
         <activity android:name="androidx.car.drawer.CarDrawerTestActivity"
                   android:theme="@style/Theme.Car.Light.NoActionBar.Drawer" />
-        <activity android:name="androidx.car.widget.ColumnCardViewTestActivity"/>
-        <activity android:name="androidx.car.widget.PagedListViewSavedStateActivity"/>
-        <activity android:name="androidx.car.widget.PagedListViewTestActivity"/>
-        <activity android:name="androidx.car.widget.DividerVisibilityManagerTestActivity"/>
+        <activity android:name="androidx.car.widget.ColumnCardViewTestActivity"
+                  android:theme="@style/Theme.Car.Light.NoActionBar" />
+        <activity android:name="androidx.car.widget.PagedListViewSavedStateActivity"
+                  android:theme="@style/Theme.Car.Light.NoActionBar" />
+        <activity android:name="androidx.car.widget.PagedListViewTestActivity"
+                  android:theme="@style/Theme.Car.Light.NoActionBar" />
+        <activity android:name="androidx.car.widget.DividerVisibilityManagerTestActivity"
+                  android:theme="@style/Theme.Car.Light.NoActionBar" />
     </application>
 </manifest>
diff --git a/car/src/androidTest/java/androidx/car/drawer/CarDrawerTest.java b/car/src/androidTest/java/androidx/car/drawer/CarDrawerTest.java
index 81ef980..711808f 100644
--- a/car/src/androidTest/java/androidx/car/drawer/CarDrawerTest.java
+++ b/car/src/androidTest/java/androidx/car/drawer/CarDrawerTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assume.assumeThat;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.espresso.contrib.DrawerActions;
@@ -32,6 +33,7 @@
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
@@ -51,8 +53,14 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public final class CarDrawerTest {
+    // Note that launchActivity is passed "false" here because we only want to create the
+    // Activity after we checked that the test is being run on an auto device. Otherwise, this will
+    // cause an error due to classes not being found.
     @Rule
-    public ActivityTestRule<CarDrawerTestActivity> mActivityRule;
+    public ActivityTestRule<CarDrawerTestActivity> mActivityRule = new ActivityTestRule<>(
+            CarDrawerTestActivity.class,
+            false /* initialTouchMode */,
+            false /* launchActivity */);
 
     private CarDrawerTestActivity mActivity;
     private PagedListView mDrawerList;
@@ -67,19 +75,21 @@
     public void setUp() {
         Assume.assumeTrue(isAutoDevice());
 
-        // Inflate the activity here rather than initialization because it needs to happen after
-        // the isAutoDevice() check. Otherwise, errors will be thrown about "android.car.Car"
-        // classes not being found.
-        mActivityRule = new ActivityTestRule<>(CarDrawerTestActivity.class);
-
+        // Retrieve the activity after the isAutoDevice() check because this class depends on
+        // car-related classes (android.car.Car). These classes will not be available on non-auto
+        // devices.
+        mActivityRule.launchActivity(new Intent());
         mActivity = mActivityRule.getActivity();
-        try {
-            mActivityRule.runOnUiThread(() -> {
-                mDrawerList = mActivity.findViewById(R.id.drawer_list);
-            });
-        } catch (Throwable throwable) {
-            throwable.printStackTrace();
-            throw new RuntimeException(throwable);
+
+        mDrawerList = mActivity.findViewById(R.id.drawer_list);
+    }
+
+    @After
+    public void tearDown() {
+        // The Activity is only launched if the test was run on an auto device. If it's been
+        // launched, then explicitly finish it here since it was also explicitly launched.
+        if (isAutoDevice()) {
+            mActivityRule.finishActivity();
         }
     }
 
diff --git a/car/src/main/java/androidx/car/widget/TextListItem.java b/car/src/main/java/androidx/car/widget/TextListItem.java
index d4509a9..8cae7ac 100644
--- a/car/src/main/java/androidx/car/widget/TextListItem.java
+++ b/car/src/main/java/androidx/car/widget/TextListItem.java
@@ -356,8 +356,8 @@
 
         if (mIsBodyPrimary) {
             mBinders.add(vh -> {
-                vh.getTitle().setTextAppearance(getTitleTextAppearance());
-                vh.getBody().setTextAppearance(getBodyTextAppearance());
+                vh.getTitle().setTextAppearance(getBodyTextAppearance());
+                vh.getBody().setTextAppearance(getTitleTextAppearance());
             });
         } else {
             mBinders.add(vh -> {
diff --git a/fragment/src/androidTest/java/android/support/v4/app/LoaderInfoTest.java b/fragment/src/androidTest/java/android/support/v4/app/LoaderInfoTest.java
index 799b6ab..7ea8014 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/LoaderInfoTest.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/LoaderInfoTest.java
@@ -18,7 +18,13 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.LifecycleRegistry;
+import android.content.Context;
 import android.os.SystemClock;
 import android.support.annotation.Nullable;
 import android.support.test.annotation.UiThreadTest;
@@ -29,6 +35,7 @@
 import android.support.v4.content.AsyncTaskLoader;
 import android.support.v4.content.Loader;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,6 +47,18 @@
 @SmallTest
 public class LoaderInfoTest {
 
+    private LifecycleOwner mOwner;
+    private LifecycleRegistry mRegistry;
+
+    @Before
+    public void setup() {
+        mOwner = mock(LifecycleOwner.class);
+        mRegistry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(mRegistry);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
     @Rule
     public ActivityTestRule<LoaderActivity> mActivityRule =
             new ActivityTestRule<>(LoaderActivity.class);
@@ -136,6 +155,61 @@
 
     @UiThreadTest
     @Test
+    public void testMarkForRedelivery() throws Throwable {
+        LoaderTest.DummyLoaderCallbacks loaderCallback =
+                new LoaderTest.DummyLoaderCallbacks(mock(Context.class));
+        Loader<Boolean> loader = loaderCallback.onCreateLoader(0, null);
+        LoaderManagerImpl.LoaderInfo<Boolean> loaderInfo = new LoaderManagerImpl.LoaderInfo<>(
+                0, null, loader);
+        loaderInfo.setCallback(mOwner, loaderCallback);
+        assertTrue("onLoadFinished should be called after setCallback",
+                loaderCallback.mOnLoadFinished);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        loaderCallback.mOnLoadFinished = false;
+        loaderInfo.markForRedelivery();
+        assertFalse("onLoadFinished should not be called when stopped after markForRedelivery",
+                loaderCallback.mOnLoadFinished);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        assertTrue("onLoadFinished should be called after markForRedelivery",
+                loaderCallback.mOnLoadFinished);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testMarkForRedelivery_replace() throws Throwable {
+        LoaderTest.DummyLoaderCallbacks initialCallback =
+                new LoaderTest.DummyLoaderCallbacks(mock(Context.class));
+        Loader<Boolean> loader = initialCallback.onCreateLoader(0, null);
+        LoaderManagerImpl.LoaderInfo<Boolean> loaderInfo = new LoaderManagerImpl.LoaderInfo<>(
+                0, null, loader);
+        loaderInfo.setCallback(mOwner, initialCallback);
+        assertTrue("onLoadFinished for initial should be called after setCallback initial",
+                initialCallback.mOnLoadFinished);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        initialCallback.mOnLoadFinished = false;
+        loaderInfo.markForRedelivery();
+        assertFalse("onLoadFinished should not be called when stopped after markForRedelivery",
+                initialCallback.mOnLoadFinished);
+
+        // Replace the callback
+        final LoaderTest.DummyLoaderCallbacks replacementCallback =
+                new LoaderTest.DummyLoaderCallbacks(mock(Context.class));
+        loaderInfo.setCallback(mOwner, replacementCallback);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        assertFalse("onLoadFinished for initial should not be called "
+                        + "after setCallback replacement",
+                initialCallback.mOnLoadFinished);
+        assertTrue("onLoadFinished for replacement should be called "
+                        + " after setCallback replacement",
+                replacementCallback.mOnLoadFinished);
+    }
+
+    @UiThreadTest
+    @Test
     public void testDestroy() throws Throwable {
         final LoaderTest.DummyLoaderCallbacks loaderCallback =
                 new LoaderTest.DummyLoaderCallbacks(mActivityRule.getActivity());
diff --git a/fragment/src/androidTest/java/android/support/v4/app/ViewModelTest.java b/fragment/src/androidTest/java/android/support/v4/app/ViewModelTest.java
index 3c17e26..7c6e8fb 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/ViewModelTest.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/ViewModelTest.java
@@ -18,6 +18,7 @@
 
 import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
 import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
@@ -188,6 +189,39 @@
     }
 
     @Test
+    public void testFragmentOnClearedWhenFinished() throws Throwable {
+        final ViewModelActivity activity = mActivityRule.getActivity();
+        final ViewModelFragment fragment = getFragment(activity,
+                ViewModelActivity.FRAGMENT_TAG_1);
+        final CountDownLatch latch = new CountDownLatch(1);
+        final LifecycleObserver observer = new LifecycleObserver() {
+            @SuppressWarnings("unused")
+            @OnLifecycleEvent(ON_DESTROY)
+            void onDestroy() {
+                activity.getWindow().getDecorView().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            assertThat(fragment.fragmentModel.mCleared, is(true));
+                        } finally {
+                            latch.countDown();
+                        }
+                    }
+                });
+            }
+        };
+
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                activity.getLifecycle().addObserver(observer);
+            }
+        });
+        activity.finish();
+        assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
     public void testFragmentOnCleared() throws Throwable {
         final ViewModelActivity activity = mActivityRule.getActivity();
         final CountDownLatch latch = new CountDownLatch(1);
diff --git a/fragment/src/main/java/android/support/v4/app/Fragment.java b/fragment/src/main/java/android/support/v4/app/Fragment.java
index b15c49f..5d5caab 100644
--- a/fragment/src/main/java/android/support/v4/app/Fragment.java
+++ b/fragment/src/main/java/android/support/v4/app/Fragment.java
@@ -1642,7 +1642,8 @@
     @CallSuper
     public void onDestroy() {
         mCalled = true;
-        if (mViewModelStore != null && !mHost.mFragmentManager.isStateSaved()) {
+        // Use mStateSaved instead of isStateSaved() since we're past onStop()
+        if (mViewModelStore != null && !mHost.mFragmentManager.mStateSaved) {
             mViewModelStore.clear();
         }
     }
diff --git a/fragment/src/main/java/android/support/v4/app/FragmentManager.java b/fragment/src/main/java/android/support/v4/app/FragmentManager.java
index 141d402..0ab1497 100644
--- a/fragment/src/main/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/src/main/java/android/support/v4/app/FragmentManager.java
@@ -677,6 +677,7 @@
 
     boolean mNeedMenuInvalidate;
     boolean mStateSaved;
+    boolean mStopped;
     boolean mDestroyed;
     String mNoTransactionsBecause;
     boolean mHavePendingDeferredStart;
@@ -1081,6 +1082,7 @@
         }
         writer.print(prefix); writer.print("  mCurState="); writer.print(mCurState);
                 writer.print(" mStateSaved="); writer.print(mStateSaved);
+                writer.print(" mStopped="); writer.print(mStopped);
                 writer.print(" mDestroyed="); writer.println(mDestroyed);
         if (mNeedMenuInvalidate) {
             writer.print(prefix); writer.print("  mNeedMenuInvalidate=");
@@ -2042,7 +2044,7 @@
     }
 
     private void checkStateLoss() {
-        if (mStateSaved) {
+        if (isStateSaved()) {
             throw new IllegalStateException(
                     "Can not perform this action after onSaveInstanceState");
         }
@@ -2054,7 +2056,10 @@
 
     @Override
     public boolean isStateSaved() {
-        return mStateSaved;
+        // See saveAllState() for the explanation of this.  We do this for
+        // all platform versions, to keep our behavior more consistent between
+        // them.
+        return mStateSaved || mStopped;
     }
 
     /**
@@ -3168,6 +3173,7 @@
     public void noteStateNotSaved() {
         mSavedNonConfig = null;
         mStateSaved = false;
+        mStopped = false;
         final int addedCount = mAdded.size();
         for (int i = 0; i < addedCount; i++) {
             Fragment fragment = mAdded.get(i);
@@ -3179,21 +3185,25 @@
 
     public void dispatchCreate() {
         mStateSaved = false;
+        mStopped = false;
         dispatchStateChange(Fragment.CREATED);
     }
 
     public void dispatchActivityCreated() {
         mStateSaved = false;
+        mStopped = false;
         dispatchStateChange(Fragment.ACTIVITY_CREATED);
     }
 
     public void dispatchStart() {
         mStateSaved = false;
+        mStopped = false;
         dispatchStateChange(Fragment.STARTED);
     }
 
     public void dispatchResume() {
         mStateSaved = false;
+        mStopped = false;
         dispatchStateChange(Fragment.RESUMED);
     }
 
@@ -3202,11 +3212,7 @@
     }
 
     public void dispatchStop() {
-        // See saveAllState() for the explanation of this.  We do this for
-        // all platform versions, to keep our behavior more consistent between
-        // them.
-        mStateSaved = true;
-
+        mStopped = true;
         dispatchStateChange(Fragment.STOPPED);
     }
 
@@ -4038,7 +4044,7 @@
             boolean more = super.getTransformation(currentTime, t);
             if (!more) {
                 mEnded = true;
-                mParent.post(this);
+                OneShotPreDrawListener.add(mParent, this);
             }
             return true;
         }
@@ -4052,7 +4058,7 @@
             boolean more = super.getTransformation(currentTime, outTransformation, scale);
             if (!more) {
                 mEnded = true;
-                mParent.post(this);
+                OneShotPreDrawListener.add(mParent, this);
             }
             return true;
         }
diff --git a/fragment/src/main/java/android/support/v4/app/LoaderManagerImpl.java b/fragment/src/main/java/android/support/v4/app/LoaderManagerImpl.java
index 17687ff..3021005 100644
--- a/fragment/src/main/java/android/support/v4/app/LoaderManagerImpl.java
+++ b/fragment/src/main/java/android/support/v4/app/LoaderManagerImpl.java
@@ -110,7 +110,8 @@
                 // Removing and re-adding the observer ensures that the
                 // observer is called again, even if they had already
                 // received the current data
-                removeObserver(observer);
+                // Use super.removeObserver to avoid nulling out mLifecycleOwner & mObserver
+                super.removeObserver(observer);
                 observe(lifecycleOwner, observer);
             }
         }