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
This commit is contained in:
Fan Zhang
2017-05-22 09:50:38 -07:00
parent 7983dba5f6
commit 841d1d5aab
18 changed files with 277 additions and 57 deletions

View File

@@ -18,7 +18,7 @@
<step category="com.android.settings.suggested.category.DEFERRED_SETUP" <step category="com.android.settings.suggested.category.DEFERRED_SETUP"
exclusive="true" /> exclusive="true" />
<step category="com.android.settings.suggested.category.FIRST_IMPRESSION" <step category="com.android.settings.suggested.category.FIRST_IMPRESSION"
exclusiveExpireDays="7" exclusiveExpireDays="14"
exclusive="true" exclusive="true"
multiple="true" /> multiple="true" />
<step category="com.android.settings.suggested.category.LOCK_SCREEN" /> <step category="com.android.settings.suggested.category.LOCK_SCREEN" />

View File

@@ -109,7 +109,7 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
mRemoveAccountController = new RemoveAccountPreferenceController(context, this); mRemoveAccountController = new RemoveAccountPreferenceController(context, this);
controllers.add(mRemoveAccountController); controllers.add(mRemoveAccountController);
controllers.add(new AccountHeaderPreferenceController( controllers.add(new AccountHeaderPreferenceController(
context, getActivity(), this, getArguments())); context, getLifecycle(), getActivity(), this, getArguments()));
return controllers; return controllers;
} }

View File

@@ -18,10 +18,10 @@ package com.android.settings.accounts;
import android.accounts.Account; import android.accounts.Account;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.PreferenceScreen; import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R; 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.core.PreferenceController;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.accounts.AuthenticatorHelper; 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_ACCOUNT;
import static com.android.settings.accounts.AccountDetailDashboardFragment.KEY_USER_HANDLE; 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 static final String KEY_ACCOUNT_HEADER = "account_header";
private final Activity mActivity; private final Activity mActivity;
private final Fragment mHost; private final PreferenceFragment mHost;
private final Account mAccount; private final Account mAccount;
private final UserHandle mUserHandle; private final UserHandle mUserHandle;
private final Lifecycle mLifecycle;
public AccountHeaderPreferenceController(Context context, Activity activity, Fragment host, public AccountHeaderPreferenceController(Context context, Lifecycle lifecycle,
Bundle args) { Activity activity, PreferenceFragment host, Bundle args) {
super(context); super(context);
mActivity = activity; mActivity = activity;
mHost = host; mHost = host;
mLifecycle = lifecycle;
if (args != null && args.containsKey(KEY_ACCOUNT)) { if (args != null && args.containsKey(KEY_ACCOUNT)) {
mAccount = args.getParcelable(KEY_ACCOUNT); mAccount = args.getParcelable(KEY_ACCOUNT);
} else { } else {
@@ -80,6 +83,7 @@ public class AccountHeaderPreferenceController extends PreferenceController {
EntityHeaderController EntityHeaderController
.newInstance(mActivity, mHost, headerPreference.findViewById(R.id.entity_header)) .newInstance(mActivity, mHost, headerPreference.findViewById(R.id.entity_header))
.setRecyclerView(mHost.getListView(), mLifecycle)
.setLabel(mAccount.name) .setLabel(mAccount.name)
.setIcon(helper.getDrawableForType(mContext, mAccount.type)) .setIcon(helper.getDrawableForType(mContext, mAccount.type))
.done(mActivity, true /* rebindButtons */); .done(mActivity, true /* rebindButtons */);

View File

@@ -43,6 +43,7 @@ public abstract class AppInfoWithHeader extends AppInfoBase {
final Activity activity = getActivity(); final Activity activity = getActivity();
final Preference pref = EntityHeaderController final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */) .newInstance(activity, this, null /* header */)
.setRecyclerView(getListView(), getLifecycle())
.setIcon(IconDrawableFactory.newInstance(activity) .setIcon(IconDrawableFactory.newInstance(activity)
.getBadgedIcon(mPackageInfo.applicationInfo)) .getBadgedIcon(mPackageInfo.applicationInfo))
.setLabel(mPackageInfo.applicationInfo.loadLabel(mPm)) .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))

View File

@@ -408,6 +408,7 @@ public class InstalledAppDetails extends AppInfoBase
mHeader = (LayoutPreference) findPreference(KEY_HEADER); mHeader = (LayoutPreference) findPreference(KEY_HEADER);
mActionButtons = (LayoutPreference) findPreference(KEY_ACTION_BUTTONS); mActionButtons = (LayoutPreference) findPreference(KEY_ACTION_BUTTONS);
EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header)) EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header))
.setRecyclerView(getListView(), getLifecycle())
.setPackageName(mPackageName) .setPackageName(mPackageName)
.setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, .setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
EntityHeaderController.ActionType.ACTION_NONE) EntityHeaderController.ActionType.ACTION_NONE)
@@ -585,11 +586,11 @@ public class InstalledAppDetails extends AppInfoBase
final CharSequence summary = final CharSequence summary =
isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info)); isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info));
EntityHeaderController.newInstance(activity, this, appSnippet) EntityHeaderController.newInstance(activity, this, appSnippet)
.setLabel(mAppEntry) .setLabel(mAppEntry)
.setIcon(mAppEntry) .setIcon(mAppEntry)
.setSummary(summary) .setSummary(summary)
.setIsInstantApp(isInstantApp) .setIsInstantApp(isInstantApp)
.done(activity, false /* rebindActions */); .done(activity, false /* rebindActions */);
mVersionPreference.setSummary(getString(R.string.version_text, pkgInfo.versionName)); mVersionPreference.setSummary(getString(R.string.version_text, pkgInfo.versionName));
} }

View File

@@ -128,16 +128,17 @@ public class ProcessStatsDetail extends SettingsPreferenceFragment {
final Activity activity = getActivity(); final Activity activity = getActivity();
final Preference pref = EntityHeaderController final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* appHeader */) .newInstance(activity, this, null /* appHeader */)
.setIcon(mApp.mUiTargetApp != null .setRecyclerView(getListView(), getLifecycle())
? IconDrawableFactory.newInstance(activity).getBadgedIcon(mApp.mUiTargetApp) .setIcon(mApp.mUiTargetApp != null
: new ColorDrawable(0)) ? IconDrawableFactory.newInstance(activity).getBadgedIcon(mApp.mUiTargetApp)
.setLabel(mApp.mUiLabel) : new ColorDrawable(0))
.setPackageName(mApp.mPackage) .setLabel(mApp.mUiLabel)
.setUid(mApp.mUiTargetApp != null .setPackageName(mApp.mPackage)
? mApp.mUiTargetApp.uid .setUid(mApp.mUiTargetApp != null
: UserHandle.USER_NULL) ? mApp.mUiTargetApp.uid
.setButtonActions(ActionType.ACTION_APP_INFO, ActionType.ACTION_NONE) : UserHandle.USER_NULL)
.done(activity, getPrefContext()); .setButtonActions(ActionType.ACTION_APP_INFO, ActionType.ACTION_NONE)
.done(activity, getPrefContext());
getPreferenceScreen().addPreference(pref); getPreferenceScreen().addPreference(pref);
} }

View File

@@ -327,6 +327,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
final Activity activity = getActivity(); final Activity activity = getActivity();
final Preference pref = EntityHeaderController final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */) .newInstance(activity, this, null /* header */)
.setRecyclerView(getListView(), getLifecycle())
.setButtonActions(showInfoButton .setButtonActions(showInfoButton
? EntityHeaderController.ActionType.ACTION_APP_INFO ? EntityHeaderController.ActionType.ACTION_APP_INFO
: EntityHeaderController.ActionType.ACTION_NONE, : EntityHeaderController.ActionType.ACTION_NONE,

View File

@@ -184,6 +184,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
final Bundle bundle = getArguments(); final Bundle bundle = getArguments();
EntityHeaderController controller = EntityHeaderController EntityHeaderController controller = EntityHeaderController
.newInstance(context, this, appSnippet) .newInstance(context, this, appSnippet)
.setRecyclerView(getListView(), getLifecycle())
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE); EntityHeaderController.ActionType.ACTION_NONE);

View File

@@ -132,6 +132,7 @@ public class AppNotificationSettings extends NotificationSettingsBase {
final Activity activity = getActivity(); final Activity activity = getActivity();
final Preference pref = EntityHeaderController final Preference pref = EntityHeaderController
.newInstance(activity, this /* fragment */, null /* header */) .newInstance(activity, this /* fragment */, null /* header */)
.setRecyclerView(getListView(), getLifecycle())
.setIcon(mAppRow.icon) .setIcon(mAppRow.icon)
.setLabel(mAppRow.label) .setLabel(mAppRow.label)
.setPackageName(mAppRow.pkg) .setPackageName(mAppRow.pkg)

View File

@@ -112,6 +112,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
final Activity activity = getActivity(); final Activity activity = getActivity();
final Preference pref = EntityHeaderController final Preference pref = EntityHeaderController
.newInstance(activity, this /* fragment */, null /* header */) .newInstance(activity, this /* fragment */, null /* header */)
.setRecyclerView(getListView(), getLifecycle())
.setIcon(mAppRow.icon) .setIcon(mAppRow.icon)
.setLabel(mChannel.getName()) .setLabel(mChannel.getName())
.setSummary(mAppRow.label) .setSummary(mAppRow.label)

View File

@@ -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);
}
}
}
}

View File

@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.os.UserHandle; import android.os.UserHandle;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -46,6 +47,7 @@ import com.android.settings.applications.InstalledAppDetails;
import com.android.settings.applications.LayoutPreference; import com.android.settings.applications.LayoutPreference;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -68,11 +70,13 @@ public class EntityHeaderController {
private static final String TAG = "AppDetailFeature"; private static final String TAG = "AppDetailFeature";
private final Context mContext; private final Context mAppContext;
private final Activity mActivity;
private final Fragment mFragment; private final Fragment mFragment;
private final int mMetricsCategory; private final int mMetricsCategory;
private final View mHeader; private final View mHeader;
private Lifecycle mLifecycle;
private RecyclerView mRecyclerView;
private Drawable mIcon; private Drawable mIcon;
private CharSequence mLabel; private CharSequence mLabel;
private CharSequence mSummary; private CharSequence mSummary;
@@ -93,15 +97,16 @@ public class EntityHeaderController {
* @param fragment The fragment that header will be placed in. * @param fragment The fragment that header will be placed in.
* @param header Optional: header view if it's already created. * @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) { View header) {
return new EntityHeaderController(context.getApplicationContext(), fragment, header); return new EntityHeaderController(activity, fragment, header);
} }
private EntityHeaderController(Context context, Fragment fragment, View header) { private EntityHeaderController(Activity activity, Fragment fragment, View header) {
mContext = context; mActivity = activity;
mAppContext = activity.getApplicationContext();
mFragment = fragment; mFragment = fragment;
mMetricsCategory = FeatureFactory.getFactory(context).getMetricsFeatureProvider() mMetricsCategory = FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider()
.getMetricsCategory(fragment); .getMetricsCategory(fragment);
if (header != null) { if (header != null) {
mHeader = header; 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) { public EntityHeaderController setIcon(Drawable icon) {
if (icon != null) { if (icon != null) {
mIcon = icon.getConstantState().newDrawable(mContext.getResources()); mIcon = icon.getConstantState().newDrawable(mAppContext.getResources());
} }
return this; return this;
} }
public EntityHeaderController setIcon(ApplicationsState.AppEntry appEntry) { public EntityHeaderController setIcon(ApplicationsState.AppEntry appEntry) {
if (appEntry.icon != null) { if (appEntry.icon != null) {
mIcon = appEntry.icon.getConstantState().newDrawable(mContext.getResources()); mIcon = appEntry.icon.getConstantState().newDrawable(mAppContext.getResources());
} }
return this; return this;
} }
@@ -233,6 +244,9 @@ public class EntityHeaderController {
actionBar.setBackgroundDrawable( actionBar.setBackgroundDrawable(
new ColorDrawable(Utils.getColorAttr(activity, android.R.attr.colorSecondary))); new ColorDrawable(Utils.getColorAttr(activity, android.R.attr.colorSecondary)));
actionBar.setElevation(0); actionBar.setElevation(0);
if (mRecyclerView != null && mLifecycle != null) {
ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
}
return this; return this;
} }
@@ -257,7 +271,7 @@ public class EntityHeaderController {
button.setVisibility(View.GONE); button.setVisibility(View.GONE);
} else { } else {
button.setContentDescription( 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.setImageResource(com.android.settings.R.drawable.ic_info);
button.setOnClickListener(new View.OnClickListener() { button.setOnClickListener(new View.OnClickListener() {
@Override @Override
@@ -311,7 +325,7 @@ public class EntityHeaderController {
} }
private Intent resolveIntent(Intent i) { private Intent resolveIntent(Intent i) {
ResolveInfo result = mContext.getPackageManager().resolveActivity(i, 0); ResolveInfo result = mAppContext.getPackageManager().resolveActivity(i, 0);
if (result != null) { if (result != null) {
return new Intent(i.getAction()) return new Intent(i.getAction())
.setClassName(result.activityInfo.packageName, result.activityInfo.name); .setClassName(result.activityInfo.packageName, result.activityInfo.name);

View File

@@ -18,10 +18,10 @@ package com.android.settings.accounts;
import android.accounts.Account; import android.accounts.Account;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.PreferenceScreen; import android.support.v7.preference.PreferenceScreen;
import android.widget.TextView; import android.widget.TextView;
@@ -31,6 +31,7 @@ import com.android.settings.TestConfig;
import com.android.settings.applications.LayoutPreference; import com.android.settings.applications.LayoutPreference;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.accounts.AuthenticatorHelper;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -57,7 +58,7 @@ public class AccountHeaderPreferenceControllerTest {
@Mock @Mock
private Activity mActivity; private Activity mActivity;
@Mock @Mock
private Fragment mFragment; private PreferenceFragment mFragment;
@Mock @Mock
private PreferenceScreen mScreen; private PreferenceScreen mScreen;
@@ -76,7 +77,7 @@ public class AccountHeaderPreferenceControllerTest {
@Test @Test
public void isAvailable_noArgs_shouldReturnNull() { public void isAvailable_noArgs_shouldReturnNull() {
mController = new AccountHeaderPreferenceController(RuntimeEnvironment.application, mController = new AccountHeaderPreferenceController(RuntimeEnvironment.application,
mActivity, mFragment, null /* args */); new Lifecycle(), mActivity, mFragment, null /* args */);
assertThat(mController.isAvailable()).isFalse(); assertThat(mController.isAvailable()).isFalse();
} }
@@ -89,7 +90,7 @@ public class AccountHeaderPreferenceControllerTest {
args.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT, account); args.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT, account);
args.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE, UserHandle.CURRENT); args.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE, UserHandle.CURRENT);
mController = new AccountHeaderPreferenceController(RuntimeEnvironment.application, mController = new AccountHeaderPreferenceController(RuntimeEnvironment.application,
mActivity, mFragment, args); new Lifecycle(), mActivity, mFragment, args);
assertThat(mController.isAvailable()).isTrue(); assertThat(mController.isAvailable()).isTrue();

View File

@@ -42,11 +42,13 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
@@ -74,6 +76,8 @@ public class AppDataUsageTest {
@Test @Test
public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() { public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() {
ShadowEntityHeaderController.setUseMock(mHeaderController); ShadowEntityHeaderController.setUseMock(mHeaderController);
when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController);
mFragment = spy(new AppDataUsage()); mFragment = spy(new AppDataUsage());
doReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)) doReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS))

View File

@@ -23,6 +23,7 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.BatteryStats; import android.os.BatteryStats;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import com.android.internal.os.BatterySipper; import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper; 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.AppUtils;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.instantapps.InstantAppDataProvider; import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@@ -122,6 +124,8 @@ public class AdvancedPowerUsageDetailTest {
doReturn(mBundle).when(mFragment).getArguments(); doReturn(mBundle).when(mFragment).getArguments();
ShadowEntityHeaderController.setUseMock(mEntityHeaderController); ShadowEntityHeaderController.setUseMock(mEntityHeaderController);
doReturn(mEntityHeaderController).when(mEntityHeaderController)
.setRecyclerView(any(RecyclerView.class), any(Lifecycle.class));
doReturn(mEntityHeaderController).when(mEntityHeaderController) doReturn(mEntityHeaderController).when(mEntityHeaderController)
.setButtonActions(anyInt(), anyInt()); .setButtonActions(anyInt(), anyInt());
doReturn(mEntityHeaderController).when(mEntityHeaderController) doReturn(mEntityHeaderController).when(mEntityHeaderController)

View File

@@ -16,8 +16,8 @@
package com.android.settings.testutils.shadow; package com.android.settings.testutils.shadow;
import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context;
import android.view.View; import android.view.View;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
@@ -41,7 +41,7 @@ public class ShadowEntityHeaderController {
} }
@Implementation @Implementation
public static EntityHeaderController newInstance(Context context, Fragment fragment, public static EntityHeaderController newInstance(Activity activity, Fragment fragment,
View header) { View header) {
return sMockController; return sMockController;
} }

View File

@@ -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());
}
}

View File

@@ -66,7 +66,6 @@ public class EntityHeaderControllerTest {
@Mock @Mock
private Fragment mFragment; private Fragment mFragment;
private FakeFeatureFactory mFeatureFactory;
private Context mShadowContext; private Context mShadowContext;
private LayoutInflater mLayoutInflater; private LayoutInflater mLayoutInflater;
private PackageInfo mInfo; private PackageInfo mInfo;
@@ -76,8 +75,8 @@ public class EntityHeaderControllerTest {
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest(mContext); FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mShadowContext = RuntimeEnvironment.application; mShadowContext = RuntimeEnvironment.application;
when(mActivity.getApplicationContext()).thenReturn(mShadowContext);
when(mContext.getApplicationContext()).thenReturn(mContext); when(mContext.getApplicationContext()).thenReturn(mContext);
when(mFragment.getContext()).thenReturn(mShadowContext); when(mFragment.getContext()).thenReturn(mShadowContext);
mLayoutInflater = LayoutInflater.from(mShadowContext); mLayoutInflater = LayoutInflater.from(mShadowContext);
@@ -87,7 +86,7 @@ public class EntityHeaderControllerTest {
@Test @Test
public void testBuildView_constructedWithoutView_shouldCreateNewView() { public void testBuildView_constructedWithoutView_shouldCreateNewView() {
mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null); mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
View view = mController.done(mActivity); View view = mController.done(mActivity);
assertThat(view).isNotNull(); assertThat(view).isNotNull();
@@ -95,7 +94,7 @@ public class EntityHeaderControllerTest {
@Test @Test
public void testBuildView_withContext_shouldBuildPreference() { public void testBuildView_withContext_shouldBuildPreference() {
mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null); mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
Preference preference = mController.done(mActivity, mShadowContext); Preference preference = mController.done(mActivity, mShadowContext);
assertThat(preference instanceof LayoutPreference).isTrue(); assertThat(preference instanceof LayoutPreference).isTrue();
@@ -104,7 +103,7 @@ public class EntityHeaderControllerTest {
@Test @Test
public void testBuildView_constructedWithView_shouldReturnSameView() { public void testBuildView_constructedWithView_shouldReturnSameView() {
View inputView = mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); 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); View view = mController.done(mActivity);
assertThat(view).isSameAs(inputView); assertThat(view).isSameAs(inputView);
@@ -118,7 +117,7 @@ public class EntityHeaderControllerTest {
final TextView label = header.findViewById(R.id.entity_header_title); final TextView label = header.findViewById(R.id.entity_header_title);
final TextView version = header.findViewById(R.id.entity_header_summary); 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.setLabel(testString);
mController.setSummary(testString); mController.setSummary(testString);
mController.setIcon(mShadowContext.getDrawable(R.drawable.ic_add)); mController.setIcon(mShadowContext.getDrawable(R.drawable.ic_add));
@@ -136,10 +135,11 @@ public class EntityHeaderControllerTest {
info.activityInfo.name = "321"; info.activityInfo.name = "321";
final View appLinks = mLayoutInflater final View appLinks = mLayoutInflater
.inflate(R.layout.settings_entity_header, null /* root */); .inflate(R.layout.settings_entity_header, null /* root */);
when(mActivity.getApplicationContext()).thenReturn(mContext);
when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt())) when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt()))
.thenReturn(info); .thenReturn(info);
mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks); mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
mController.setButtonActions( mController.setButtonActions(
EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
EntityHeaderController.ActionType.ACTION_NONE); EntityHeaderController.ActionType.ACTION_NONE);
@@ -164,7 +164,7 @@ public class EntityHeaderControllerTest {
when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt())) when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt()))
.thenReturn(null); .thenReturn(null);
mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks); mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
mController.setButtonActions( mController.setButtonActions(
EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
EntityHeaderController.ActionType.ACTION_NONE); EntityHeaderController.ActionType.ACTION_NONE);
@@ -181,7 +181,7 @@ public class EntityHeaderControllerTest {
final View appLinks = mLayoutInflater final View appLinks = mLayoutInflater
.inflate(R.layout.settings_entity_header, null /* root */); .inflate(R.layout.settings_entity_header, null /* root */);
mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks); mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
mController.setPackageName(null) mController.setPackageName(null)
.setButtonActions( .setButtonActions(
EntityHeaderController.ActionType.ACTION_APP_INFO, EntityHeaderController.ActionType.ACTION_APP_INFO,
@@ -200,7 +200,7 @@ public class EntityHeaderControllerTest {
.inflate(R.layout.settings_entity_header, null /* root */); .inflate(R.layout.settings_entity_header, null /* root */);
when(mFragment.getActivity()).thenReturn(mock(Activity.class)); when(mFragment.getActivity()).thenReturn(mock(Activity.class));
mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks); mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
mController.setPackageName("123") mController.setPackageName("123")
.setUid(UserHandle.USER_SYSTEM) .setUid(UserHandle.USER_SYSTEM)
.setButtonActions( .setButtonActions(
@@ -221,7 +221,7 @@ public class EntityHeaderControllerTest {
when(mFragment.getActivity()).thenReturn(mock(Activity.class)); when(mFragment.getActivity()).thenReturn(mock(Activity.class));
when(mContext.getString(eq(R.string.application_info_label))).thenReturn("App Info"); 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") mController.setPackageName("123")
.setUid(UserHandle.USER_SYSTEM) .setUid(UserHandle.USER_SYSTEM)
.setButtonActions( .setButtonActions(
@@ -229,8 +229,8 @@ public class EntityHeaderControllerTest {
EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE); EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE);
mController.done(mActivity); mController.done(mActivity);
assertThat(appLinks.findViewById(android.R.id.button1).getContentDescription()) assertThat(appLinks.findViewById(android.R.id.button1).getContentDescription().toString())
.isEqualTo("App Info"); .isEqualTo("App info");
} }
@Test @Test
@@ -238,7 +238,7 @@ public class EntityHeaderControllerTest {
final View appLinks = mLayoutInflater final View appLinks = mLayoutInflater
.inflate(R.layout.settings_entity_header, null /* root */); .inflate(R.layout.settings_entity_header, null /* root */);
mController = EntityHeaderController.newInstance(mContext, mFragment, appLinks); mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
mController.setAppNotifPrefIntent(new Intent()) mController.setAppNotifPrefIntent(new Intent())
.setButtonActions( .setButtonActions(
EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE, EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE,
@@ -257,7 +257,7 @@ public class EntityHeaderControllerTest {
public void instantApps_normalAppsDontGetLabel() { public void instantApps_normalAppsDontGetLabel() {
final View header = mLayoutInflater.inflate( final View header = mLayoutInflater.inflate(
R.layout.settings_entity_header, null /* root */); R.layout.settings_entity_header, null /* root */);
mController = EntityHeaderController.newInstance(mContext, mFragment, header); mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
mController.done(mActivity); mController.done(mActivity);
assertThat(header.findViewById(R.id.install_type).getVisibility()) assertThat(header.findViewById(R.id.install_type).getVisibility())
@@ -269,7 +269,7 @@ public class EntityHeaderControllerTest {
public void instantApps_expectedHeaderItem() { public void instantApps_expectedHeaderItem() {
final View header = mLayoutInflater.inflate( final View header = mLayoutInflater.inflate(
R.layout.settings_entity_header, null /* root */); R.layout.settings_entity_header, null /* root */);
mController = EntityHeaderController.newInstance(mContext, mFragment, header); mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
mController.setIsInstantApp(true); mController.setIsInstantApp(true);
mController.done(mActivity); mController.done(mActivity);
TextView label = header.findViewById(R.id.install_type); TextView label = header.findViewById(R.id.install_type);
@@ -283,7 +283,7 @@ public class EntityHeaderControllerTest {
@Test @Test
public void styleActionBar_invalidObjects_shouldNotCrash() { public void styleActionBar_invalidObjects_shouldNotCrash() {
mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null); mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
mController.styleActionBar(null); mController.styleActionBar(null);
when(mActivity.getActionBar()).thenReturn(null); when(mActivity.getActionBar()).thenReturn(null);
@@ -296,7 +296,7 @@ public class EntityHeaderControllerTest {
public void styleActionBar_setElevationAndBackground() { public void styleActionBar_setElevationAndBackground() {
final ActionBar actionBar = mActivity.getActionBar(); final ActionBar actionBar = mActivity.getActionBar();
mController = EntityHeaderController.newInstance(mShadowContext, mFragment, null); mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
mController.styleActionBar(mActivity); mController.styleActionBar(mActivity);
verify(actionBar).setElevation(0); verify(actionBar).setElevation(0);
@@ -307,7 +307,7 @@ public class EntityHeaderControllerTest {
@Test @Test
public void initAppHeaderController_appHeaderNull_useFragmentContext() { 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 // Fragment.getContext() is invoked to inflate the view
verify(mFragment).getContext(); verify(mFragment).getContext();