From 841d1d5aabc33024b2e03276085b0d1dbdb92b23 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 22 May 2017 09:50:38 -0700 Subject: [PATCH] Show shadow when entity header starts scrolling. - 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 --- res/xml/suggestion_ordering.xml | 2 +- .../AccountDetailDashboardFragment.java | 2 +- .../AccountHeaderPreferenceController.java | 12 ++- .../applications/AppInfoWithHeader.java | 1 + .../applications/InstalledAppDetails.java | 11 ++- .../applications/ProcessStatsDetail.java | 21 ++-- .../settings/datausage/AppDataUsage.java | 1 + .../fuelgauge/AdvancedPowerUsageDetail.java | 1 + .../notification/AppNotificationSettings.java | 1 + .../ChannelNotificationSettings.java | 1 + .../widget/ActionBarShadowController.java | 97 +++++++++++++++++++ .../widget/EntityHeaderController.java | 36 ++++--- ...AccountHeaderPreferenceControllerTest.java | 9 +- .../settings/datausage/AppDataUsageTest.java | 4 + .../AdvancedPowerUsageDetailTest.java | 4 + .../shadow/ShadowEntityHeaderController.java | 4 +- .../widget/ActionBarShadowControllerTest.java | 89 +++++++++++++++++ .../widget/EntityHeaderControllerTest.java | 38 ++++---- 18 files changed, 277 insertions(+), 57 deletions(-) create mode 100644 src/com/android/settings/widget/ActionBarShadowController.java create mode 100644 tests/robotests/src/com/android/settings/widget/ActionBarShadowControllerTest.java diff --git a/res/xml/suggestion_ordering.xml b/res/xml/suggestion_ordering.xml index f0388a3e237..e21fe5b82c1 100644 --- a/res/xml/suggestion_ordering.xml +++ b/res/xml/suggestion_ordering.xml @@ -18,7 +18,7 @@ diff --git a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java index 5aee39f5300..843b7fce98c 100644 --- a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java @@ -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; } diff --git a/src/com/android/settings/accounts/AccountHeaderPreferenceController.java b/src/com/android/settings/accounts/AccountHeaderPreferenceController.java index 4730e9e7d7c..d0ce58d9f5c 100644 --- a/src/com/android/settings/accounts/AccountHeaderPreferenceController.java +++ b/src/com/android/settings/accounts/AccountHeaderPreferenceController.java @@ -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 */); diff --git a/src/com/android/settings/applications/AppInfoWithHeader.java b/src/com/android/settings/applications/AppInfoWithHeader.java index bbcec3c0f8a..95877e9622d 100644 --- a/src/com/android/settings/applications/AppInfoWithHeader.java +++ b/src/com/android/settings/applications/AppInfoWithHeader.java @@ -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)) diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 3021f7560c2..0eaad8e8243 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -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)); } diff --git a/src/com/android/settings/applications/ProcessStatsDetail.java b/src/com/android/settings/applications/ProcessStatsDetail.java index 9f229afa1f4..b9c3826c92b 100644 --- a/src/com/android/settings/applications/ProcessStatsDetail.java +++ b/src/com/android/settings/applications/ProcessStatsDetail.java @@ -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); } diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index a47e1357533..af6306d3ab8 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -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, diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 18223410831..9bab3ce4cee 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -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); diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 92ad3f1e814..e7be62f996a 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -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) diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java index 85a56baa609..f7bf1ca702e 100644 --- a/src/com/android/settings/notification/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelNotificationSettings.java @@ -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 index 00000000000..75bdf0e52be --- /dev/null +++ b/src/com/android/settings/widget/ActionBarShadowController.java @@ -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); + } + } + } + +} diff --git a/src/com/android/settings/widget/EntityHeaderController.java b/src/com/android/settings/widget/EntityHeaderController.java index d7ba35ae48e..70d3ce53f3c 100644 --- a/src/com/android/settings/widget/EntityHeaderController.java +++ b/src/com/android/settings/widget/EntityHeaderController.java @@ -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); diff --git a/tests/robotests/src/com/android/settings/accounts/AccountHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/AccountHeaderPreferenceControllerTest.java index 0668379c93c..64c2e9e19b8 100644 --- a/tests/robotests/src/com/android/settings/accounts/AccountHeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AccountHeaderPreferenceControllerTest.java @@ -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(); diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java index 2d0f0318188..8cbe9479569 100644 --- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java +++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java @@ -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)) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java index 7b2d8cc63ac..c5fcbf540aa 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java @@ -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; @@ -122,6 +124,8 @@ public class AdvancedPowerUsageDetailTest { doReturn(mBundle).when(mFragment).getArguments(); 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) diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEntityHeaderController.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEntityHeaderController.java index bccb2972d4e..4ec48d35dd2 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEntityHeaderController.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEntityHeaderController.java @@ -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 index 00000000000..b8f78206c0b --- /dev/null +++ b/tests/robotests/src/com/android/settings/widget/ActionBarShadowControllerTest.java @@ -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 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()); + } + +} diff --git a/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java b/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java index e6c742a72a2..e386282437f 100644 --- a/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java @@ -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();