OSDN Git Service

Show shadow when entity header starts scrolling.
authorFan Zhang <zhfan@google.com>
Mon, 22 May 2017 16:50:38 +0000 (09:50 -0700)
committerFan Zhang <zhfan@google.com>
Tue, 23 May 2017 00:16:24 +0000 (17:16 -0700)
- Add a controller to manage add/remove onScrollChangedListener to
  recyclerviews.
- When recyclerview on each screen is scrolled to top, set actionbar
  elevation to 0, otherwise set it to non-zero.
- When screen is moved to background, detach the listener.
- Use the controller in entity header.

Change-Id: Iecf194d885098c98c392810f62893ae9189f3936
Fix: 37670670
Test: make RunSettingsRoboTests

18 files changed:
res/xml/suggestion_ordering.xml
src/com/android/settings/accounts/AccountDetailDashboardFragment.java
src/com/android/settings/accounts/AccountHeaderPreferenceController.java
src/com/android/settings/applications/AppInfoWithHeader.java
src/com/android/settings/applications/InstalledAppDetails.java
src/com/android/settings/applications/ProcessStatsDetail.java
src/com/android/settings/datausage/AppDataUsage.java
src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
src/com/android/settings/notification/AppNotificationSettings.java
src/com/android/settings/notification/ChannelNotificationSettings.java
src/com/android/settings/widget/ActionBarShadowController.java [new file with mode: 0644]
src/com/android/settings/widget/EntityHeaderController.java
tests/robotests/src/com/android/settings/accounts/AccountHeaderPreferenceControllerTest.java
tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
tests/robotests/src/com/android/settings/testutils/shadow/ShadowEntityHeaderController.java
tests/robotests/src/com/android/settings/widget/ActionBarShadowControllerTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java

index f0388a3..e21fe5b 100644 (file)
@@ -18,7 +18,7 @@
     <step category="com.android.settings.suggested.category.DEFERRED_SETUP"
           exclusive="true" />
     <step category="com.android.settings.suggested.category.FIRST_IMPRESSION"
-          exclusiveExpireDays="7"
+          exclusiveExpireDays="14"
           exclusive="true"
           multiple="true" />
     <step category="com.android.settings.suggested.category.LOCK_SCREEN" />
index 5aee39f..843b7fc 100644 (file)
@@ -109,7 +109,7 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
         mRemoveAccountController = new RemoveAccountPreferenceController(context, this);
         controllers.add(mRemoveAccountController);
         controllers.add(new AccountHeaderPreferenceController(
-                context, getActivity(), this, getArguments()));
+                context, getLifecycle(), getActivity(), this, getArguments()));
         return controllers;
     }
 
index 4730e9e..d0ce58d 100644 (file)
@@ -18,10 +18,10 @@ package com.android.settings.accounts;
 
 import android.accounts.Account;
 import android.app.Activity;
-import android.app.Fragment;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.support.v14.preference.PreferenceFragment;
 import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settings.R;
@@ -29,6 +29,7 @@ import com.android.settings.applications.LayoutPreference;
 import com.android.settings.core.PreferenceController;
 import com.android.settings.widget.EntityHeaderController;
 import com.android.settingslib.accounts.AuthenticatorHelper;
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import static com.android.settings.accounts.AccountDetailDashboardFragment.KEY_ACCOUNT;
 import static com.android.settings.accounts.AccountDetailDashboardFragment.KEY_USER_HANDLE;
@@ -38,15 +39,17 @@ public class AccountHeaderPreferenceController extends PreferenceController {
     private static final String KEY_ACCOUNT_HEADER = "account_header";
 
     private final Activity mActivity;
-    private final Fragment mHost;
+    private final PreferenceFragment mHost;
     private final Account mAccount;
     private final UserHandle mUserHandle;
+    private final Lifecycle mLifecycle;
 
-    public AccountHeaderPreferenceController(Context context, Activity activity, Fragment host,
-            Bundle args) {
+    public AccountHeaderPreferenceController(Context context, Lifecycle lifecycle,
+            Activity activity, PreferenceFragment host, Bundle args) {
         super(context);
         mActivity = activity;
         mHost = host;
+        mLifecycle = lifecycle;
         if (args != null && args.containsKey(KEY_ACCOUNT)) {
             mAccount = args.getParcelable(KEY_ACCOUNT);
         } else {
@@ -80,6 +83,7 @@ public class AccountHeaderPreferenceController extends PreferenceController {
 
         EntityHeaderController
                 .newInstance(mActivity, mHost, headerPreference.findViewById(R.id.entity_header))
+                .setRecyclerView(mHost.getListView(), mLifecycle)
                 .setLabel(mAccount.name)
                 .setIcon(helper.getDrawableForType(mContext, mAccount.type))
                 .done(mActivity, true /* rebindButtons */);
index bbcec3c..95877e9 100644 (file)
@@ -43,6 +43,7 @@ public abstract class AppInfoWithHeader extends AppInfoBase {
         final Activity activity = getActivity();
         final Preference pref = EntityHeaderController
                 .newInstance(activity, this, null /* header */)
+                .setRecyclerView(getListView(), getLifecycle())
                 .setIcon(IconDrawableFactory.newInstance(activity)
                         .getBadgedIcon(mPackageInfo.applicationInfo))
                 .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
index 3021f75..0eaad8e 100755 (executable)
@@ -408,6 +408,7 @@ public class InstalledAppDetails extends AppInfoBase
         mHeader = (LayoutPreference) findPreference(KEY_HEADER);
         mActionButtons = (LayoutPreference) findPreference(KEY_ACTION_BUTTONS);
         EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header))
+                .setRecyclerView(getListView(), getLifecycle())
                 .setPackageName(mPackageName)
                 .setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
                         EntityHeaderController.ActionType.ACTION_NONE)
@@ -585,11 +586,11 @@ public class InstalledAppDetails extends AppInfoBase
         final CharSequence summary =
                 isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info));
         EntityHeaderController.newInstance(activity, this, appSnippet)
-            .setLabel(mAppEntry)
-            .setIcon(mAppEntry)
-            .setSummary(summary)
-            .setIsInstantApp(isInstantApp)
-            .done(activity, false /* rebindActions */);
+                .setLabel(mAppEntry)
+                .setIcon(mAppEntry)
+                .setSummary(summary)
+                .setIsInstantApp(isInstantApp)
+                .done(activity, false /* rebindActions */);
         mVersionPreference.setSummary(getString(R.string.version_text, pkgInfo.versionName));
     }
 
index 9f229af..b9c3826 100644 (file)
@@ -128,16 +128,17 @@ public class ProcessStatsDetail extends SettingsPreferenceFragment {
         final Activity activity = getActivity();
         final Preference pref = EntityHeaderController
                 .newInstance(activity, this, null /* appHeader */)
-            .setIcon(mApp.mUiTargetApp != null
-                ? IconDrawableFactory.newInstance(activity).getBadgedIcon(mApp.mUiTargetApp)
-                : new ColorDrawable(0))
-            .setLabel(mApp.mUiLabel)
-            .setPackageName(mApp.mPackage)
-            .setUid(mApp.mUiTargetApp != null
-                ? mApp.mUiTargetApp.uid
-                : UserHandle.USER_NULL)
-            .setButtonActions(ActionType.ACTION_APP_INFO, ActionType.ACTION_NONE)
-            .done(activity, getPrefContext());
+                .setRecyclerView(getListView(), getLifecycle())
+                .setIcon(mApp.mUiTargetApp != null
+                        ? IconDrawableFactory.newInstance(activity).getBadgedIcon(mApp.mUiTargetApp)
+                        : new ColorDrawable(0))
+                .setLabel(mApp.mUiLabel)
+                .setPackageName(mApp.mPackage)
+                .setUid(mApp.mUiTargetApp != null
+                        ? mApp.mUiTargetApp.uid
+                        : UserHandle.USER_NULL)
+                .setButtonActions(ActionType.ACTION_APP_INFO, ActionType.ACTION_NONE)
+                .done(activity, getPrefContext());
         getPreferenceScreen().addPreference(pref);
     }
 
index a47e135..af6306d 100644 (file)
@@ -327,6 +327,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
         final Activity activity = getActivity();
         final Preference pref = EntityHeaderController
                 .newInstance(activity, this, null /* header */)
+                .setRecyclerView(getListView(), getLifecycle())
                 .setButtonActions(showInfoButton
                                 ? EntityHeaderController.ActionType.ACTION_APP_INFO
                                 : EntityHeaderController.ActionType.ACTION_NONE,
index 1822341..9bab3ce 100644 (file)
@@ -184,6 +184,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
         final Bundle bundle = getArguments();
         EntityHeaderController controller = EntityHeaderController
                 .newInstance(context, this, appSnippet)
+                .setRecyclerView(getListView(), getLifecycle())
                 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
                         EntityHeaderController.ActionType.ACTION_NONE);
 
index 92ad3f1..e7be62f 100644 (file)
@@ -132,6 +132,7 @@ public class AppNotificationSettings extends NotificationSettingsBase {
         final Activity activity = getActivity();
         final Preference pref = EntityHeaderController
                 .newInstance(activity, this /* fragment */, null /* header */)
+                .setRecyclerView(getListView(), getLifecycle())
                 .setIcon(mAppRow.icon)
                 .setLabel(mAppRow.label)
                 .setPackageName(mAppRow.pkg)
index 85a56ba..f7bf1ca 100644 (file)
@@ -112,6 +112,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
         final Activity activity = getActivity();
         final Preference pref = EntityHeaderController
                 .newInstance(activity, this /* fragment */, null /* header */)
+                .setRecyclerView(getListView(), getLifecycle())
                 .setIcon(mAppRow.icon)
                 .setLabel(mChannel.getName())
                 .setSummary(mAppRow.label)
diff --git a/src/com/android/settings/widget/ActionBarShadowController.java b/src/com/android/settings/widget/ActionBarShadowController.java
new file mode 100644 (file)
index 0000000..75bdf0e
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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 com.android.settings.widget;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+public class ActionBarShadowController implements LifecycleObserver, OnStart, OnStop {
+
+    private ScrollChangeWatcher mScrollChangeWatcher;
+    private RecyclerView mRecyclerView;
+    private boolean isScrollWatcherAttached;
+
+    public static ActionBarShadowController attachToRecyclerView(Activity activity,
+            Lifecycle lifecycle, RecyclerView recyclerView) {
+        return new ActionBarShadowController(activity, lifecycle, recyclerView);
+    }
+
+    private ActionBarShadowController(Activity activity, Lifecycle lifecycle,
+            RecyclerView recyclerView) {
+        mScrollChangeWatcher = new ScrollChangeWatcher(activity);
+        mRecyclerView = recyclerView;
+        attachScrollWatcher();
+        lifecycle.addObserver(this);
+    }
+
+    @Override
+    public void onStop() {
+        detachScrollWatcher();
+    }
+
+    private void detachScrollWatcher() {
+        mRecyclerView.removeOnScrollListener(mScrollChangeWatcher);
+        isScrollWatcherAttached = false;
+    }
+
+    @Override
+    public void onStart() {
+        attachScrollWatcher();
+    }
+
+    private void attachScrollWatcher() {
+        if (!isScrollWatcherAttached) {
+            isScrollWatcherAttached = true;
+            mRecyclerView.addOnScrollListener(mScrollChangeWatcher);
+            mScrollChangeWatcher.updateDropShadow(mRecyclerView);
+        }
+    }
+
+    /**
+     * Update the drop shadow as the scrollable entity is scrolled.
+     */
+    private final class ScrollChangeWatcher extends RecyclerView.OnScrollListener {
+
+        private Activity mActivity;
+
+        public ScrollChangeWatcher(Activity activity) {
+            mActivity = activity;
+        }
+
+        // RecyclerView scrolled.
+        @Override
+        public void onScrolled(RecyclerView view, int dx, int dy) {
+            updateDropShadow(view);
+        }
+
+        public void updateDropShadow(View view) {
+            final boolean shouldShowShadow = view.canScrollVertically(-1);
+            final ActionBar actionBar = mActivity.getActionBar();
+            if (actionBar != null) {
+                actionBar.setElevation(shouldShowShadow ? 8 : 0);
+            }
+        }
+    }
+
+}
index d7ba35a..70d3ce5 100644 (file)
@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.support.annotation.IntDef;
 import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -46,6 +47,7 @@ import com.android.settings.applications.InstalledAppDetails;
 import com.android.settings.applications.LayoutPreference;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -68,11 +70,13 @@ public class EntityHeaderController {
 
     private static final String TAG = "AppDetailFeature";
 
-    private final Context mContext;
+    private final Context mAppContext;
+    private final Activity mActivity;
     private final Fragment mFragment;
     private final int mMetricsCategory;
     private final View mHeader;
-
+    private Lifecycle mLifecycle;
+    private RecyclerView mRecyclerView;
     private Drawable mIcon;
     private CharSequence mLabel;
     private CharSequence mSummary;
@@ -93,15 +97,16 @@ public class EntityHeaderController {
      * @param fragment The fragment that header will be placed in.
      * @param header   Optional: header view if it's already created.
      */
-    public static EntityHeaderController newInstance(Context context, Fragment fragment,
+    public static EntityHeaderController newInstance(Activity activity, Fragment fragment,
             View header) {
-        return new EntityHeaderController(context.getApplicationContext(), fragment, header);
+        return new EntityHeaderController(activity, fragment, header);
     }
 
-    private EntityHeaderController(Context context, Fragment fragment, View header) {
-        mContext = context;
+    private EntityHeaderController(Activity activity, Fragment fragment, View header) {
+        mActivity = activity;
+        mAppContext = activity.getApplicationContext();
         mFragment = fragment;
-        mMetricsCategory = FeatureFactory.getFactory(context).getMetricsFeatureProvider()
+        mMetricsCategory = FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider()
                 .getMetricsCategory(fragment);
         if (header != null) {
             mHeader = header;
@@ -111,16 +116,22 @@ public class EntityHeaderController {
         }
     }
 
+    public EntityHeaderController setRecyclerView(RecyclerView recyclerView, Lifecycle lifecycle) {
+        mRecyclerView = recyclerView;
+        mLifecycle = lifecycle;
+        return this;
+    }
+
     public EntityHeaderController setIcon(Drawable icon) {
         if (icon != null) {
-            mIcon = icon.getConstantState().newDrawable(mContext.getResources());
+            mIcon = icon.getConstantState().newDrawable(mAppContext.getResources());
         }
         return this;
     }
 
     public EntityHeaderController setIcon(ApplicationsState.AppEntry appEntry) {
         if (appEntry.icon != null) {
-            mIcon = appEntry.icon.getConstantState().newDrawable(mContext.getResources());
+            mIcon = appEntry.icon.getConstantState().newDrawable(mAppContext.getResources());
         }
         return this;
     }
@@ -233,6 +244,9 @@ public class EntityHeaderController {
         actionBar.setBackgroundDrawable(
                 new ColorDrawable(Utils.getColorAttr(activity, android.R.attr.colorSecondary)));
         actionBar.setElevation(0);
+        if (mRecyclerView != null && mLifecycle != null) {
+            ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
+        }
 
         return this;
     }
@@ -257,7 +271,7 @@ public class EntityHeaderController {
                     button.setVisibility(View.GONE);
                 } else {
                     button.setContentDescription(
-                            mContext.getString(R.string.application_info_label));
+                            mAppContext.getString(R.string.application_info_label));
                     button.setImageResource(com.android.settings.R.drawable.ic_info);
                     button.setOnClickListener(new View.OnClickListener() {
                         @Override
@@ -311,7 +325,7 @@ public class EntityHeaderController {
     }
 
     private Intent resolveIntent(Intent i) {
-        ResolveInfo result = mContext.getPackageManager().resolveActivity(i, 0);
+        ResolveInfo result = mAppContext.getPackageManager().resolveActivity(i, 0);
         if (result != null) {
             return new Intent(i.getAction())
                     .setClassName(result.activityInfo.packageName, result.activityInfo.name);
index 0668379..64c2e9e 100644 (file)
@@ -18,10 +18,10 @@ package com.android.settings.accounts;
 
 import android.accounts.Account;
 import android.app.Activity;
-import android.app.Fragment;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.support.v14.preference.PreferenceFragment;
 import android.support.v7.preference.PreferenceScreen;
 import android.widget.TextView;
 
@@ -31,6 +31,7 @@ import com.android.settings.TestConfig;
 import com.android.settings.applications.LayoutPreference;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settingslib.accounts.AuthenticatorHelper;
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -57,7 +58,7 @@ public class AccountHeaderPreferenceControllerTest {
     @Mock
     private Activity mActivity;
     @Mock
-    private Fragment mFragment;
+    private PreferenceFragment mFragment;
     @Mock
     private PreferenceScreen mScreen;
 
@@ -76,7 +77,7 @@ public class AccountHeaderPreferenceControllerTest {
     @Test
     public void isAvailable_noArgs_shouldReturnNull() {
         mController = new AccountHeaderPreferenceController(RuntimeEnvironment.application,
-                mActivity, mFragment, null /* args */);
+                new Lifecycle(), mActivity, mFragment, null /* args */);
 
         assertThat(mController.isAvailable()).isFalse();
     }
@@ -89,7 +90,7 @@ public class AccountHeaderPreferenceControllerTest {
         args.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT, account);
         args.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE, UserHandle.CURRENT);
         mController = new AccountHeaderPreferenceController(RuntimeEnvironment.application,
-                mActivity, mFragment, args);
+                new Lifecycle(), mActivity, mFragment, args);
 
         assertThat(mController.isAvailable()).isTrue();
 
index 2d0f031..8cbe947 100644 (file)
@@ -42,11 +42,13 @@ import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.util.ReflectionHelpers;
 
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
@@ -74,6 +76,8 @@ public class AppDataUsageTest {
     @Test
     public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() {
         ShadowEntityHeaderController.setUseMock(mHeaderController);
+        when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController);
+
         mFragment = spy(new AppDataUsage());
 
         doReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS))
index 7b2d8cc..c5fcbf5 100644 (file)
@@ -23,6 +23,7 @@ import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.BatteryStats;
 import android.os.Bundle;
+import android.support.v7.widget.RecyclerView;
 
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
@@ -36,6 +37,7 @@ import com.android.settings.widget.EntityHeaderController;
 import com.android.settingslib.applications.AppUtils;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.After;
 import org.junit.Before;
@@ -123,6 +125,8 @@ public class AdvancedPowerUsageDetailTest {
 
         ShadowEntityHeaderController.setUseMock(mEntityHeaderController);
         doReturn(mEntityHeaderController).when(mEntityHeaderController)
+                .setRecyclerView(any(RecyclerView.class), any(Lifecycle.class));
+        doReturn(mEntityHeaderController).when(mEntityHeaderController)
                 .setButtonActions(anyInt(), anyInt());
         doReturn(mEntityHeaderController).when(mEntityHeaderController)
                 .setIcon(any(Drawable.class));
index bccb297..4ec48d3 100644 (file)
@@ -16,8 +16,8 @@
 
 package com.android.settings.testutils.shadow;
 
+import android.app.Activity;
 import android.app.Fragment;
-import android.content.Context;
 import android.view.View;
 
 import com.android.settings.widget.EntityHeaderController;
@@ -41,7 +41,7 @@ public class ShadowEntityHeaderController {
     }
 
     @Implementation
-    public static EntityHeaderController newInstance(Context context, Fragment fragment,
+    public static EntityHeaderController newInstance(Activity activity, Fragment fragment,
             View header) {
         return sMockController;
     }
diff --git a/tests/robotests/src/com/android/settings/widget/ActionBarShadowControllerTest.java b/tests/robotests/src/com/android/settings/widget/ActionBarShadowControllerTest.java
new file mode 100644 (file)
index 0000000..b8f7820
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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 com.android.settings.widget;
+
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.support.v7.widget.RecyclerView;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ActionBarShadowControllerTest {
+
+    @Mock
+    private RecyclerView mRecyclerView;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ActionBar mActionBar;
+    private Lifecycle mLifecycle;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getActionBar()).thenReturn(mActionBar);
+        mLifecycle = new Lifecycle();
+    }
+
+    @Test
+    public void attachToRecyclerView_shouldAddScrollWatcherAndUpdateActionBar() {
+        when(mRecyclerView.canScrollVertically(-1)).thenReturn(false);
+
+        ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
+
+        verify(mActionBar).setElevation(0);
+    }
+
+
+    @Test
+    public void attachToRecyclerView_lifecycleChange_shouldAttachDetach() {
+        ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
+
+        List<LifecycleObserver> observers = ReflectionHelpers.getField(mLifecycle, "mObservers");
+        assertThat(observers).hasSize(1);
+        verify(mRecyclerView).addOnScrollListener(any());
+
+        mLifecycle.onStop();
+        verify(mRecyclerView).removeOnScrollListener(any());
+
+        mLifecycle.onStart();
+        verify(mRecyclerView, times(2)).addOnScrollListener(any());
+    }
+
+}
index e6c742a..e386282 100644 (file)
@@ -66,7 +66,6 @@ public class EntityHeaderControllerTest {
     @Mock
     private Fragment mFragment;
 
-    private FakeFeatureFactory mFeatureFactory;
     private Context mShadowContext;
     private LayoutInflater mLayoutInflater;
     private PackageInfo mInfo;
@@ -76,8 +75,8 @@ public class EntityHeaderControllerTest {
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         FakeFeatureFactory.setupForTest(mContext);
-        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mShadowContext = RuntimeEnvironment.application;
+        when(mActivity.getApplicationContext()).thenReturn(mShadowContext);
         when(mContext.getApplicationContext()).thenReturn(mContext);
         when(mFragment.getContext()).thenReturn(mShadowContext);
         mLayoutInflater = LayoutInflater.from(mShadowContext);
@@ -87,7 +86,7 @@ public class EntityHeaderControllerTest {
 
     @Test
     public void testBuildView_constructedWithoutView_shouldCreateNewView() {
-        mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
         View view = mController.done(mActivity);
 
         assertThat(view).isNotNull();
@@ -95,7 +94,7 @@ public class EntityHeaderControllerTest {
 
     @Test
     public void testBuildView_withContext_shouldBuildPreference() {
-        mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
         Preference preference = mController.done(mActivity, mShadowContext);
 
         assertThat(preference instanceof LayoutPreference).isTrue();
@@ -104,7 +103,7 @@ public class EntityHeaderControllerTest {
     @Test
     public void testBuildView_constructedWithView_shouldReturnSameView() {
         View inputView = mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */);
-        mController = EntityHeaderController.newInstance(mShadowContext, mFragment, inputView);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, inputView);
         View view = mController.done(mActivity);
 
         assertThat(view).isSameAs(inputView);
@@ -118,7 +117,7 @@ public class EntityHeaderControllerTest {
         final TextView label = header.findViewById(R.id.entity_header_title);
         final TextView version = header.findViewById(R.id.entity_header_summary);
 
-        mController = EntityHeaderController.newInstance(mShadowContext, mFragment, header);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
         mController.setLabel(testString);
         mController.setSummary(testString);
         mController.setIcon(mShadowContext.getDrawable(R.drawable.ic_add));
@@ -136,10 +135,11 @@ public class EntityHeaderControllerTest {
         info.activityInfo.name = "321";
         final View appLinks = mLayoutInflater
                 .inflate(R.layout.settings_entity_header, null /* root */);
+        when(mActivity.getApplicationContext()).thenReturn(mContext);
         when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt()))
                 .thenReturn(info);
 
-        mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
         mController.setButtonActions(
                 EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
                 EntityHeaderController.ActionType.ACTION_NONE);
@@ -164,7 +164,7 @@ public class EntityHeaderControllerTest {
         when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt()))
                 .thenReturn(null);
 
-        mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
         mController.setButtonActions(
                 EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
                 EntityHeaderController.ActionType.ACTION_NONE);
@@ -181,7 +181,7 @@ public class EntityHeaderControllerTest {
         final View appLinks = mLayoutInflater
                 .inflate(R.layout.settings_entity_header, null /* root */);
 
-        mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
         mController.setPackageName(null)
                 .setButtonActions(
                         EntityHeaderController.ActionType.ACTION_APP_INFO,
@@ -200,7 +200,7 @@ public class EntityHeaderControllerTest {
                 .inflate(R.layout.settings_entity_header, null /* root */);
         when(mFragment.getActivity()).thenReturn(mock(Activity.class));
 
-        mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
         mController.setPackageName("123")
                 .setUid(UserHandle.USER_SYSTEM)
                 .setButtonActions(
@@ -221,7 +221,7 @@ public class EntityHeaderControllerTest {
         when(mFragment.getActivity()).thenReturn(mock(Activity.class));
         when(mContext.getString(eq(R.string.application_info_label))).thenReturn("App Info");
 
-        mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
         mController.setPackageName("123")
                 .setUid(UserHandle.USER_SYSTEM)
                 .setButtonActions(
@@ -229,8 +229,8 @@ public class EntityHeaderControllerTest {
                         EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE);
         mController.done(mActivity);
 
-        assertThat(appLinks.findViewById(android.R.id.button1).getContentDescription())
-                .isEqualTo("App Info");
+        assertThat(appLinks.findViewById(android.R.id.button1).getContentDescription().toString())
+                .isEqualTo("App info");
     }
 
     @Test
@@ -238,7 +238,7 @@ public class EntityHeaderControllerTest {
         final View appLinks = mLayoutInflater
                 .inflate(R.layout.settings_entity_header, null /* root */);
 
-        mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
         mController.setAppNotifPrefIntent(new Intent())
                 .setButtonActions(
                         EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE,
@@ -257,7 +257,7 @@ public class EntityHeaderControllerTest {
     public void instantApps_normalAppsDontGetLabel() {
         final View header = mLayoutInflater.inflate(
                 R.layout.settings_entity_header, null /* root */);
-        mController = EntityHeaderController.newInstance(mContext, mFragment, header);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
         mController.done(mActivity);
 
         assertThat(header.findViewById(R.id.install_type).getVisibility())
@@ -269,7 +269,7 @@ public class EntityHeaderControllerTest {
     public void instantApps_expectedHeaderItem() {
         final View header = mLayoutInflater.inflate(
                 R.layout.settings_entity_header, null /* root */);
-        mController = EntityHeaderController.newInstance(mContext, mFragment, header);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
         mController.setIsInstantApp(true);
         mController.done(mActivity);
         TextView label = header.findViewById(R.id.install_type);
@@ -283,7 +283,7 @@ public class EntityHeaderControllerTest {
 
     @Test
     public void styleActionBar_invalidObjects_shouldNotCrash() {
-        mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
         mController.styleActionBar(null);
 
         when(mActivity.getActionBar()).thenReturn(null);
@@ -296,7 +296,7 @@ public class EntityHeaderControllerTest {
     public void styleActionBar_setElevationAndBackground() {
         final ActionBar actionBar = mActivity.getActionBar();
 
-        mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
         mController.styleActionBar(mActivity);
 
         verify(actionBar).setElevation(0);
@@ -307,7 +307,7 @@ public class EntityHeaderControllerTest {
 
     @Test
     public void initAppHeaderController_appHeaderNull_useFragmentContext() {
-        mController = EntityHeaderController.newInstance(mContext, mFragment, null);
+        mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
 
         // Fragment.getContext() is invoked to inflate the view
         verify(mFragment).getContext();